]

The Clay Framework

Sean Woods

The Clay Framework is an attempt to allow prototype based programming within TclOO.
Without resorting to event more heroic means

What is clay used for?

Clay introduces a Tcl-ish take on prototype based programming.
  • The meta-class does virtually nothing, except know how to bolt on more functions through mixins.
  • As the object reads it's configuration it uses mixins to add new behavior.
  • The object then sets out to accomplish its task.

Applications

Dynamic web content 
A Tcl based make system 
Gaming/Chatbots/AI 

Applications

Dynamic web contentHttpd / Toadhttpd
A Tcl based make systemPractcl
Gaming/Chatbots/AIThe Epic of Gilgamesh (Under Development)

Clay in Httpd

httpd::reply only provides basic MIME parsing and socket wrangling.
  • Parts of that socket wrangling are swappable to implement proxies.
  • Custom content replaces the content method.
  • Large sites can swap out the header/footer methods to provide sitewide navigation, inline style sheets, Bootstrap/jQuery integration

Clay in Httpd

httpd::server utilizes mixins:
  • Controlled through the plugin method
  • Used to swap out dispatch engines, logging mechanisms, and security lockdowns.

Rest API Example



HTTPD uri direct * restapi {} {
  # Have the reply do its own dispatching
  set URI [split [my request get REQUEST_URI] /]
  lassign [split $URI] base method uuid
  set class [find_class $method]
  if {$class eq {}} {
    # Errors will turned into the appropriate HTTP reply
    tailcall my error 404 {Not Found} {}
  }
  # Mix in a new behavior
  my clay mixinmap rest $class
  my reply set Content-Type application/json
  # Pass off control to a method brought in by the mixin
  tailcall my RestContent
}

Clay in Practcl

Practcl is a Tcl based make system. It can generate Tclkits, binary libaries, pure-tcl modules, and large collections of pure-tcl packages.

Clay in Practcl

When implementing a build system, it helps to be able to refer to a generic build product, and then get more elaborate as we know more about it. Especially if we need to detect information from the compiler or environment during the course of compilation.

set affinepath [file dirname [file normalize [info script]]]

namespace eval ::odielib {}
my define set initfunc OdieAffine_Init
my add [file join $affinepath const.tcl]

foreach file [lsort -dictionary [glob [file join $affinepath *.tcl]]] {
  if {[file tail $file] in {const.tcl cmatrixforms.tcl}} continue
  my add $file
}
# Run cmatrixforms at the end
my add [file join $affinepath cmatrixforms.tcl]
my add [file join $affinepath hardcoded_h_file.h]
my add [file join $affinepath hardcoded_c_file.c]

Clay in Fun Stuff

When implementing a Role Playing Game, many configuration options require a modification of behavior. Those options are orthoganal, and often interact with one another.

Clay in Fun Stuff

PlayerYes
SpeciesHuman
GenderMale
ClassWizard

Clay in Fun Stuff

PlayerYes::gilgamesh::core/avatar
SpeciesHuman::gilgamesh::species/human
GenderMale::gilgamesh::gender/male
ClassWizard::gilgamesh::class/wizard

Clay in Fun Stuff



# Create a database record
GAME object create {
  uuid PLAYER
  name Hypnotoad
  core avatar
  species human
  gender male
  class  wizard
}
# Implement the object from that database record
set obj [GAME object wake PLAYER]

Developing in Clay

Clay injects a method into oo::class and oo::object called clay.

The clay method manages all of the interactions with the framework.

ALL OF THEM

Structured Data

The principle function of the clay method is to implement a dictionary tree that is stored internally in the object.

And in the class

And all of the mixins

And all of the ancestors of the class and the mixins

Structured Data



::oo::class create foo {}
foo clay set this/means something
set obj [foo new]
$obj clay get this/means
> something

# Can use file system style paths, or dict style paths
$obj clay get this means
> something

Structured Data



::oo::class create foo {}
foo clay set this means something
::oo::class create bar {
  superclass foo
}
bar clay set this is madness

set obj [bar new]
$obj clay get this means
> something
$obj clay get this is
> madness
$obj clay get this
> means something is madness

Object can overwrite class data



$obj clay set this is Sanity
$obj clay get this is
> Sanity

And ... a bug



$obj clay get this
> means something is madness

###
# SHOULD HAVE BEEN
###
> means something is Sanity

This stuff isn't easy

Managing mixins and a stuctured data is EASY.

Doing it properly... well.... my tests are getting longer than the implementation.

Delegation



sqlite3 ::DB ~/db/mydatabase.sqlite

oo::class create ::mydb {
  method dump_record {table rowid} {
    # <db> goes out to the database
    my <db> eval "select * from $table where rowid=:rowid" record {}
    return [array get record]
  }
}
$obj clay delegate db ::DB
$obj clay mixin ::mydb
set data [$obj dump_record object PLAYER]

Mixin Events



# Script right before a class is removed
::mydb clay set mixin unmap-script {puts "Db...gone"}
$obj clay mixin

# Script as a class is mixed in
::mydb clay set mixin map-script {puts "Db...present"}
# Script as another class is mixed in
::foo clay set mixin react-script {puts "I saw what you did there..."}

$obj clay mixin ::mydb
> Db...Present
> I saw what you did there...

Introspection



$obj clay provenance this/means
> ::foo
$obj clay provenance this/is
> ::bar
$obj ancestors
::bar ::foo ::oo::class

Option Passing



$obj clay set this/is Sanity
$obj clay get this/is
> Sanity
$obj clay provenance this/is
> self

Option Passing



$obj clay set config {
  color blue
  flavor blueberry
}
$obj clay get config color
> blue

Clay Dialect

Populating clay can get a bit... verbose. To make clay feel more like a part of oo, Clay includes an oo::dialect.


clay::define myclass {
  clay set is keyword yes

  # Variable you want initialized
  Variable running 0

  # Dict for which the initial value is a merge of all
  # of the classes in the object
  Dict state {
    running 0
    completed 0
  }
}

Clay Dialect

Method Ensembles


clay::define myclass {
  Ensemble state::get args {
    my variable state
    tailcall dict get $state {*}$args
  }
  Ensemble state::set args {
    my variable state
    dict set state {*}$args
  }
}

Clay Dialect

Method Ensembles


$obj mixin ::myclass
$obj state get running
0
$obj state get notexists
> error "Unknown method notexists, valid: get set"

Clay Dialect

Method Ensembles follow interitance


clay::define ::myotherclass {
  superclass ::myclass
  Ensemble state::notexists args {
    return "It's here"
  }
}
$obj state set running 1
$obj mixin ::myotherclass
$obj state get running
> 1
$obj state get notexists
> It's here

...End of Prepared Slides

Live demos or hasty retreat.