[2013-11-09 - nagu]: In the process of learning TclOO (mainly about metaclasses), I attempted to implement struct::record using TclOO. Initial versions were posted on c.l.t group and comments received from experts there. Please see this thread . The implementation source code can be found below. It's fully compliant with struct::record package as it has successfully passed all the tests in Tcllib's record.test suite.
Comments are welcome...
Date | Change |
---|---|
2013-11-10 | Performance results added to this wiki |
2013-11-09 | 100% successful run of all tests in Tcllib record.test suite |
2013-11-09 | Added support for nested definitions, C++ style member access aliases using "." |
2013-11-06 | Added support for recordName instanceName ?args? syntax. Eg., Employee emp1 -name "n1" |
2013-11-05 | Changed record create to record define. Implemented record exists instance instanceName as per the original spec. |
2013-11-05 | Support for record show records added. |
2013-11-04 | Refactored the code and added a 'instance clear' method to reset an instance's values to initial values. |
2013-11-03 | Changed cget method to get 'args' as its argument. Updated demo code to use cget and configure methods. |
2013-11-01 | Initial version |
escargo 2013-10-09 - With the passage of all the tests (congratulations!), then comes the question about how this implementation performs relative to the Tcllib implementation. Any idea?
nagu 2013-10-09 - Thanks. Haven't compared the performance yet... Will let you know.
nagu 2013-10-10 - Performance Test results added in the sections below.
package require Tcl 8.6 package require TclOO oo::class create recordInst { constructor {mnames ivals srecs args} { oo::objdefine [self] [list variable {*}$mnames _ivals _srecs] my variable _ivals my variable _srecs set _ivals $ivals set _srecs $srecs foreach n $mnames { my variable $n if {[dict exists $srecs $n]} { set srec [uplevel 1 [list [dict get $srecs $n] create [namespace which [self]]::$n]] set $n $srec dict set _srecs $n $srec } else { set $n [dict get $ivals $n] set a [my _alias $n] uplevel #0 [list interp alias {} $a {} [self] access $n] } } foreach {opt v} $args { uplevel 1 [list [self] configure $opt $v] } } method access {n args} { my variable _srecs my variable $n if {$n == ""} { return [my cget] } switch -- [llength $args] { 0 { return [set $n] } 1 { return [set $n [lindex $args 0]] } 2 { lassign $args op val if {$op != "="} { return -code error "Unrecognized operator $op when trying to configure $opt" } return [set $n $val] } default { return -code error "Invalid access format when trying to configure $opt" } } } method cget {args} { my variable _srecs set result "" switch -- [llength $args] { 0 { set result [my show] } default { set count 1 foreach o $args { set n [string trimleft $o -] my variable $n if {[dict exists $_srecs $n]} { lappend result [uplevel 1 [list [namespace which [self]]::$n cget]] } else { lassign [my _cmd $n] cmd n lappend result [uplevel 1 [list $cmd access $n]] } incr count } if {$count == 1} { set result [lindex $result 0] } } } return $result } method configure {args} { if {$args == ""} { return [my show] } my variable _srecs foreach {opt val} $args { set n [string trimleft $opt -] my variable $n if {[dict exists $_srecs $n]} { uplevel 1 [list [namespace which [self]]::$n configure {*}$val] } else { lassign [my _cmd $n] cmd n uplevel 1 [list $cmd access $n $val] } } } method clear {} { my variable _ivals dict for {n val} $_ivals { my variable $n set $n $val } } method show {} { my variable _ivals my variable _srecs set result {} foreach n [dict keys $_ivals] { my variable $n if {[dict exists $_srecs $n]} { lappend result -$n [uplevel 1 [list [dict get $_srecs $n] show]] } else { lappend result -$n [set $n] } } return $result } method unknown {args} { if {$args == ""} { return [uplevel 1 [list [self] show]] } set name [lindex $args 0] set args [lrange $args 1 end] switch -glob -- $name { "config*" { uplevel 1 [list [self] configure {*}$args] } default { uplevel 1 [list [self] create $name {*}$args] } } } method _alias {n {l 2}} { set nscurrent [uplevel $l [list namespace current]] set nsself [namespace which [self]] set nsself [string range $nsself [string length $nscurrent] end] set result $nscurrent if {$result == "::"} { append result [format "%s.%s" [join [list {*}[string map {:: " "} $nsself]] "."] $n] } else { append result [format "::%s.%s" [join [list {*}[string map {:: " "} $nsself]] "."] $n] } return $result } method _cmd {n} { set nsself [namespace which [self]] set pos [string last . $n] if {$pos < 0} { return [list $nsself $n] } set parents "[string map {. "::"} [string range $n 0 [expr $pos-1]]]" set leaf [string range $n [expr $pos + 1] end] return [list [format "%s::%s" $nsself $parents] $leaf] } } oo::class create recordType { constructor {recorddefn args} { oo::objdefine [self] export createWithNamespace oo::objdefine [self] variable _recorddefn _mnames _ivals my variable _recorddefn my variable _mnames my variable _ivals my variable _instid set _instid 0 set _recorddefn [list {*}$recorddefn] set mnames [list] set ivals [dict create] set srecs [dict create] foreach member $recorddefn { switch -- [llength $member] { 1 { dict set ivals $member {} lappend mnames $member } 2 { lassign $member n v dict set ivals $n $v lappend mnames $n } 3 { lassign $member r t n if {$r != "record"} { return -code error "Unexpected keyword '$r' in row definition '$member' of [self]" } set nst [uplevel 1 [list namespace which $t]] if {$nst == [self]} { return -code error "Can not have circular records. Structure was not created." } lappend mnames $n dict set ivals $n {} dict set srecs $n $t } default { return -code error "Unsupported nested definition $member found in [self]." } } } set _mnames $mnames set _ivals $ivals set _srecs $srecs set create_method { method create {instname args} { my variable _instid if {$instname == "#auto"} { set instname "[string tolower [self]]$_instid" incr _instid } next $instname ${mnames} ${ivals} ${srecs} {*}$args } } set create_method [string map [list \${mnames} [list $mnames] \${ivals} [list $ivals] \${srecs} [list $srecs]] $create_method] oo::objdefine [self] $create_method oo::objdefine [self] { method show {} { my variable _recorddefn return $_recorddefn } method unknown {args} { if {$args == ""} { return [uplevel 1 [list [self] show]] } set name [lindex $args 0] set args [lrange $args 1 end] switch -glob -- $name { "config*" { uplevel 1 [list [self] configure {*}$args] } default { uplevel 1 [list [self] create $name {*}$args] } } } } oo::define [self] { mixin recordInst } foreach inst $args { uplevel 1 [list [self] create $inst] } } } oo::class create record { superclass oo::class mixin recordType self { forward define my create method show {what {of ""}} { switch -glob -- $what { "record*" { return [lsort [uplevel 1 [list info class instances [self]]]] } "inst*" { set ns [uplevel 1 [list namespace which $of]] return [lsort [uplevel 1 [list info class instances $ns]]] } "mem*" { set ns [uplevel 1 [list namespace which $of]] return [uplevel 1 [list $of show]] } "val*" { set ns [uplevel 1 [list namespace which $of]] return [uplevel 1 [list $of show]] } } } method exists {what obj} { set nsobj [uplevel 1 [list namespace which $obj]] if {$nsobj == ""} { return 0 } switch -glob -- $what { "inst*" { set recordtypes [uplevel 1 [list info class instances [self]]] foreach rt $recordtypes { set nstype [uplevel 1 [list namespace which $rt]] set found [uplevel 1 [list info object isa typeof $nsobj $nstype]] if {$found} { return $found } } return 0 } "record*" { return [uplevel 1 [list info object isa typeof $nsobj [self]]] } } } method delete {what obj {type ""}} { set nsobj [uplevel 1 [list namespace which $obj]] return [uplevel 1 [list $nsobj destroy]] } } } package provide Record 0.1
record define Phone { {mobile ""} {landline ""} } record define Contact { {email_id ""} {website ""} {record Phone phone} } record define Employee { {id -1} {name ""} {rollno ""} {address_id <null>} {record Contact contact} } emp1 emp1.contact.phone.mobile "testmobile" puts [record show values emp1] Employee emp2 -name "constructed name" puts [emp2 cget] puts [emp2.name] puts [emp2.contact.phone.landline] puts [record show records] puts [record show instances Employee] puts [record show instances Contact] puts [record show instances Phone] puts [record show members Employee] puts [Employee show] emp2 configure -name "configured name" -rollno "configured rollno" puts [emp2 cget] emp2 clear puts [emp2 cget] puts [record exists record Employee] puts [record exists instance emp1] puts [record delete instance emp1] puts [record exists instance emp1] puts [record delete record Employee] puts [record exists record Employee]
-id -1 -name {} -rollno {} -address_id <null> -contact {-email_id {} -website {} -phone {-mobile testmobile -landline {}}} -id -1 -name {constructed name} -rollno {} -address_id <null> -contact {-email_id {} -website {} -phone {-mobile {} -landline {}}} constructed name ::Contact ::Employee ::Phone ::emp1 ::emp2 ::emp1::contact ::emp2::contact ::emp1::contact::phone ::emp2::contact::phone {id -1} {name ""} {rollno ""} {address_id <null>} {record Contact contact} {id -1} {name ""} {rollno ""} {address_id <null>} {record Contact contact} -id -1 -name {configured name} -rollno {configured rollno} -address_id <null> -contact {-email_id {} -website {} -phone {-mobile {} -landline {}}} -id -1 -name {} -rollno {} -address_id <null> -contact {-email_id {} -website {} -phone {-mobile {} -landline {}}} 1 1 0 0
proc perf {} { catch {record delete record phones} record define phones {home work cell} catch {record delete record contact} record define contact { first middle last {record phones phlist} } catch {record delete record mycontact} record define mycontact { age sex {record contact cont} } catch {record delete record location} record define location { street city state {country USA} } loc(1) loc(5) catch { record define circular { one {record circular cir} } cir(1) } err contact cont(1) contact #auto set res [mycontact #auto] lappend res [record show values $res] set res cont(1).first Brett cont(1).phlist.cell 425-555-1212 mycontact0.cont.phlist.cell 206-555-1212 cont(1) config -middle Allen -last Schwarz mycontact0 config -cont.phlist.cell 206-555-1212 cont(1) cget -first -middle -last mycontact0 cget -cont.phlist.cell cont(1).first loc(1) cget -country loc(1) config -street somestreet -city somecity -state somestate -country somecountry cont(1) config -phlist.home 425-555-1212 cont(1) cget -phlist.home loc(1) config loc(1) cget loc(1) cont(1).phlist.cell location loc(2) -street street2 -city city2 -state state2 -country country2 loc(2).street contact cont(2) -first John -middle Q -last Doe -phlist [list home 425-555-1212 work 425-555-1222 cell 425-555-1111] eval contact cont(3) [cont(1)] cont(2).phlist.home catch {record delete record new_contact} record define new_contact [record show members contact] record show records record show members phones record show members location record show members contact record show values loc(1) record show values cont(1) record show instance location record delete instance loc(2) record delete instance cont(2) record delete record location record exists instance loc(1) record exists instance cont(1) record exists instance junk record exists record contact namespace eval myns { catch {record delete record myns::phones} record define phones {home work cell} } catch {record delete record ::myns::contact} record define ::myns::contact { first middle last {record phones phlist} } namespace eval myns { catch {record delete record location} record define location { street city state {country USA} } loc(1) loc(5) } catch { namespace eval myns { record define circular { one {record ::myns::circular cir} } cir(1) } } err set err namespace eval myns { contact cont(1) } namespace eval myns { contact #auto } myns::cont(1).first Brett myns::cont(1).phlist.cell 425-555-1212 myns::cont(1) config -middle Allen -last Schwarz myns::cont(1) cget -first -middle -last myns::loc(1) cget -country myns::loc(1) config -street somestreet -city somecity -state somestate -country somecountry myns::cont(1) config -phlist.home 425-555-1212 myns::cont(1) cget -phlist.home myns::loc(1) config myns::loc(1) cget myns::loc(1) myns::cont(1).phlist.cell namespace eval myns { location loc(2) -street street2 -city city2 -state state2 -country country2 } myns::loc(2).street namespace eval myns { contact cont(2) -first John -middle Q -last Doe -phlist [list home 425-555-1212 work 425-555-1222 cell 425-555-1111] } myns::cont(2).phlist.home record show records record show members myns::phones record show members myns::location record show members myns::contact record show values myns::loc(1) record show values myns::cont(1) record show instance myns::location record delete instance myns::loc(2) record delete instance myns::cont(2) record delete record myns::location record exists instance myns::loc(1) record exists instance myns::cont(1) record exists instance myns::junk record exists record myns::contact set res {} lappend res [contact #auto] lappend res [contact #auto] record delete instance [lindex $res end] lappend res [contact #auto] } puts [time {perf} 1000]
When tested on a HP-Mini Intel(R) Atom(TM) CPU N270 @ 1.60GHz Running 3.2.0-4-686-pae #1 SMP Debian 3.2.51-1 i686 GNU/Linux
Version | Time |
---|---|
Tcl8.6.1 TclOO | 38180.943 microseconds per iteration |
Tcl8.6.1 Tcllib | 47492.816 microseconds per iteration |
Tcl8.5.11 Tcllib | 45531.976 microseconds per iteration |