Version 1 of Predicates: on being and having

Updated 2003-09-16 17:01:37

Richard Suchenwirth - Tcl offers many predicates (command invocations that return 0 or 1) which serve especially for introspection, but they are distributed over a number of commands with slightly different styles, which the user has to know. Here is an experiment at generalizing these predicates, as well as some other predicate-like invocations, into a single is command which is always called in the same style,

   is (property) (object)

where you only have to remember the property name, and specify an object. For a tidied-up Tcl, such a command (or command set) might be conceived as part of the core, but one of the beauties of Tcl is that you can shape the language pretty much to your likings and experiment whether the change would be worthwile. So here's a Tcl implementation which should be fairly self-explanatory. First however comes a value-added switch which produces an accurate error, or rather usage, message, without us having to keep track of what keywords we have assigned so far:

 proc subcmd {cmd choices} {
        set words [list]
        foreach {word -} $choices {lappend words $word}
        lappend choices default [list error \
        "$cmd? Should be one of [join $words {, }]"]
        uplevel 1 switch -- $cmd $choices
 }
 proc is {predicate thing} {
        subcmd $predicate {
          array                {uplevel array exists [list $thing]}
          ateof                {eof $thing}
          command        {has [info command $thing] $thing}
          directory         {file isdirectory $thing}
          emailaddress  {regexp {^[A-Za-z0-9._-]+@[[A-Za-z0-9.-]+$} $thing}
          empty                {expr {$thing==""}}
          even                {expr 1-abs([incr thing 0])%2}
          existent         {info exists $thing}
          existentfile         {file exists $thing}
          global        {has [info globals $thing] $thing]}
          integer         {expr ![catch {incr thing}]}
          list                {expr ![catch {llength $thing}]}
          mapped        {winfo ismapped $thing}
          numeric         {expr ![catch {expr $thing+0}]}
          odd                {expr abs([incr thing 0])%2}
          proc          {has [info proc $thing] $thing}
          readable         {file readable $thing}
          writable         {file writable $thing}
        }
 }

And, while we're at it:

 proc isn't {predicate thing} {
    uplevel expr !\[is $predicate $thing\] 
 }

For usage examples, we proceed to the has command, which abstracts membership of an element in a list, a key in an array, or a file in a directory:

 proc has {set element} {
        if [is directory $set] {
                is existentfile [file join $set $element]
        } elseif [uplevel is array [list $set]] {
                uplevel is existent [set set]($element)
        } else {
                expr {[lsearch -exact $set $element] >= 0}
        }
 }

Isn't Tcl great? See also: Salt and Sugar - Arts and crafts of Tcl-Tk programming


DKF provided performance improvements: [llength [info...] is more efficient than [info ...]]!="". Thank you! He also hinted at the [string is ... -strict] commands in newer Tcl versions, but I still must support 8.0.5 ;-(


Category Concept