Predicates: on being and having

Richard Suchenwirth 2001-03-05 - Tcl offers many predicates (command invocations that return 0 or 1) that serve especially well 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 that 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 that should be fairly self-explanatory.

First however comes a value-added switch that 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      {uplevel 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 ;-(

WHD: Very cool--but it seems really wasteful to rebuild the arguments to the switch command every time you call it. It seems like you should either cache them somewhere for next time, or generate the "is" proc programmatically.

RS In fact, this page is many years old - subcommand dispatch will from 8.5 be most convenient with ensembles. Just take it as an experiment in API design...