Version 8 of Procedures stored in arrays

Updated 2002-11-21 18:38:15

William Wragg 21-nov-2002 - While playing with Lua I discovered the joys of first class functions. This combined with the use of associative arrays was so much fun to program with that I started wanting them in other languages I use. I found a similar idea in Arrays as cached functions but this wasn't quite what I wanted. What I wanted were for procs to be stored in arrays, and then called by just getting the value of the array.

(Also see Arrays of function pointers - KPV)

This is what I came up with:

  # unique integer ID generator, at first call gives 1, then 2, 3, ...
  proc intgen {{seed 0}} {
    set self [lindex [info level 0] 0]
    proc $self "{seed [incr seed]}" [info body $self]
    set seed
  }

  # Lambda proc with caller scope life dependenecy.
  # When the caller scope dies so does the lambda proc.
  proc sproc {args body} {
    set name sproc[intgen]
    uplevel 1 set __$name -
    uplevel 1 "trace variable __$name u {rename $name {} ;#}"
    proc $name $args $body
    return $name
  }

  # Use this proc to run the sprocs stored in arrays.
  # The value from the array - sproc - is eval'ed to produce
  # the proc and name. This proc name and the args list is
  # then eval'ed again to produce the output.
  proc call {sproc args} {
    eval [eval $sproc] $args
  }

The sproc procedure is just a lambda procedure from Lambda in Tcl. To use the above do something like - note the use of the global namespace for accessing the array value:

  set s(spfac) {
    sproc x {
      expr {$x<2? 1: $x * [call $::s(spfac) [incr x -1]]}
    }
  }

Which is the same as:

  proc pfac x {
    expr {$x<2? 1: $x * [pfac [incr x -1]]}
  }

You use the sproc like:

  call $s(spfac) 30
    1409286144

Another simple example would be:

  set arr(hello) {
    sproc var {
      puts $var
    }
  }

  call $arr(hello) "hello world"
    hello world

The overall affect is almost what I wanted. The way of calling the sprocs is a bit gludgy, but all other ways I could think of used traces, which I didn't want. I wanted everything to be in the array and a few handler procs. Creating and destroying procs is not the fastest process in the world:

  puts pfac:[time {pfac 30} 1]
    pfac:81 microseconds per iteration
  puts spfac:[time {call $s(spfac) 30} 1]
    spfac:2749 microseconds per iteration

But hell it was fun figuring it all out. These could even be combined with Persistent arrays and Tequila for something interesting.


KPV My biggest problem with ideas like this and also things like Arrays of function pointers and even object inheritance is that it can make it very hard for someone new to a code base to read code and understand what's going on. Trying to determine which actual procedure gets call at a given line of code can be almost impossible to determine without actually stepping the code in a debugger.

Also, how do you debug a procedure stored in an array?


RS: If I read your code right, call creates a new lambda every time, with constant body. I think it's much easier to do it like this:

 set s(spfac) [sproc x {...}] ;# define only once
 $s(spfac) 30                 ;# call by dereferencing the array element