Version 21 of ensemble extend

Updated 2015-12-27 08:53:26 by Napier

Here's a simple bit of code to extend any ensemble-like command by means of tcl8.5's namespace ensemble command. CMcC 6Mar2006

Larry Smith: stacking does a similar job.

 package provide extend 1.0
 package require Tcl 8.5
 
 # extend a command with a new subcommand
 proc extend {cmd body} {
    if {![namespace exists ${cmd}]} {
        set wrapper [string map [list %C $cmd %B $body] {
            namespace eval %C {}
            rename %C %C::%C
            namespace eval %C {
                proc _unknown {junk subc args} {
                    return [list %C::%C $subc]
                }
                namespace ensemble create -unknown %C::_unknown
            }
        }]
    }

    append wrapper [string map [list %C $cmd %B $body] {
        namespace eval %C {
            %B
            namespace export -clear *
        }
    }]
    uplevel 1 $wrapper
 }

Here's the file command extended with newer and newerthan subcommands:

 extend file {
    proc newer {a b} {
       return [expr {[file mtime $a] > [file mtime $b]}]
    }

    proc newerthan {mtime path} {
       return [expr {[file exists $path] && ([file mtime $path] > $mtime)}]
    }
 }

Here's the dict command extended with the modify subcommand:

 # extra useful dict commands
 extend dict {
    proc modify {var args} {
       upvar 1 $var dvar
       foreach {name val} $args {
          dict set dvar $name $val
       }
    }
 }

LV So, this seems like a nice bit of functionality. Would it be useful enough to include either in Tcl itself or at least Tcllib?

Napier I really like ES6 Javascripts capabilities to work with objects such as "const { key1, key2 } = myObject", so I decided to give myself similar functionality with a "dict pull" command. One thing I am not sure of, is if setting an empty string is the proper thing to do when a value doesn't exist. I would like to handle it similar to javascript, but tcl doesn't have a "null" option which could be used to default to false

I know this is somewhat similar to dict update or dict with but the syntax is a bit simpler and it's designed for it's exact purpose, except that it only unpacks the requested keys and will create the variables so they may be used without info exists in cases that is too verbose.

The resulting operation with extend:

set tempDict [dict create foo fooVal bar barVal]
dict pull $tempDict foo bar rawr
puts $foo        ; # % fooVal
puts $bar        ; # % barVal
puts $rawr       ; # % ""

and the code:

extend dict {
        proc pull {var args} {
            foreach val $args {
                upvar 1 $val $val
                if {[dict exists $var $val]} {
                    set $val [dict get $var $val]
                } else {
                    set $val {}
                }
            }
        }
 }

Here is what I am using to incorporate what others have done with gets? (getnull) and modify:

extend dict {
    proc isDict {var} { 
        if { [catch {dict keys ${dvar}}] } {return 0} else {return 1} 
    }
    
    proc get? {tempDict args} {
        if {[dict exists $tempDict {*}$args]} {
            return [dict get $tempDict {*}$args]
        }
    }
    
    proc modify {var args} {
       upvar 1 $var dvar
       foreach {name val} $args {
          dict set dvar $name $val
       }
    }
    
    proc pull {tempDict args} {
        foreach val $args {
            upvar 1 $val $val
            if {[dict exists $tempDict $val]} {
                set $val [dict get $tempDict $val]
            } else {
                set $val {}
            }
        }
    }
    
    proc push {var args} {
        upvar 1 $var d
        foreach key $args {
            upvar 1 $key isKey
            dict set d $key $isKey
        }
    }
}

quick hacks ?


AMG: See also my [dict getnull] example in [dict get].


samoc: oclib.tcl[L1 ] has a similar "extend_proc" [L2 ] command.


In a comp.lang.tcl posting dated Fri, 04 Apr 2014 09:25:30 DKF posted an example of using the ensemble's -unknown parameter to lazily apply extensions. A version of extend using this technique:

proc extend {ens script} {
    namespace eval $ens [concat {
        proc _unknown {ens cmd args} {
            if {$cmd in [namespace eval ::${ens} {::info commands}]} {
                set map [namespace ensemble configure $ens -map]
                dict set map $cmd ::${ens}::$cmd
                namespace ensemble configure $ens -map $map
            }
            return "" ;# back to namespace ensemble dispatch
                      ;# which will error appropriately if the cmd doesn't exist
        }
    }   \; $script]
    namespace ensemble configure $ens -unknown ${ens}::_unknown
}

Note that new extensions defined in this way will not appear in the ensemble's map until they are used, so the default error message is misleading.