Lazy objects for delayed processing

The "lazy" command is now part of CritLib [L1 ]

NEM 2008-05-24: Where? I don't see it in my install.


This demo is a self-contained package which supports creating values that are evaluated lazily, i.e. on the first time they are used. More exactly, "lazy" constructs a Tcl_Obj* value with no string representation.

The first time the value is used, a previously specified command is run, the result of which is then used as string representation. The fact that such lazy evalution was applied is totally hidden, and one-shot only.

An example:

    proc readfile {filename} { return [read [open $filename]] }

    file delete myfile.txt
    set grabit [lazy readfile myfile.txt]
    exec date myfile.txt
    puts $grabit

The result will show as a timestamp, even though that information didn't exist at the time when $grabit was defined.

There are numerous uses for this "delayed processing" mechanism, but I'm not yet sure it is working reliably. There have been some unexplained interactions (even though interpreter state *is* saved and restored).

One known weakness, is that the code executed lazily may never throw an error (this makes the above example a bad one, btw). That may prove to be a big show stopper. There simply is no logical place to handle such an error condition.

A related issue, is that the delayed call stashes away a copy of its Tcl interpreter, because even though it should not alter it, execution does require having such an interpreter context to work in.

Source:


  package require lazy

  proc yell {args} { puts stderr "YELL $args"; return $args }

  proc lazy_try {} {
    catch {lazy -peek yell} err
    puts "err = $err"
    puts [lazy yell bingo]
    set a [lazy yell one two three]
    puts "peek = [lazy -peek $a]"
    puts "eval = $a"
    puts "eval = [llength $a]"

    set ::errmsg ""
    puts "thrown = [lazy error bang!]"
    puts "caught = [lazy catch {error boom!} errmsg]"
    puts "errmsg = $::errmsg"

    set ::errorInfo ""
    set a [lazy error dontlook!]
    puts "peek a = [lazy -peek $a]"
    puts "errorInfo <[join [split $::errorInfo \n] { - }]>"
    string length $a
    puts "errorInfo <[join [split $::errorInfo \n] { - }]>"
    puts "eval a = $a"
    
    set a ""
    set b [catch {lazy error yes?} a]
    puts "errorInfo <[join [split $::errorInfo \n] { - }]>"
    puts "a = $a, b = $b"

    global one
    trace add variable one {read write unset} [list yell trace]
    set one(1) un
    puts $one(1)
    unset one

    set two [lazy yell deux]
    puts "two #0"
    puts "peek [lazy -peek $two]"
    puts "two #1 $two"
    puts "two #2 $two"
  }

  lazy_try

Output (SuSE 7.1 Linux, PIII/650):


  err = not a lazy object
  YELL bingo
  bingo
  peek = yell one two three
  YELL one two three
  eval = one two three
  eval = 3
  thrown = bang!
  caught = 1
  errmsg = boom!
  peek a = error dontlook!
  errorInfo <>
  errorInfo <dontlook! -     while executing - "error dontlook!">
  eval a = dontlook!
  errorInfo <dontlook! -     while executing - "error dontlook!">
  a = yes?, b = 0
  YELL trace one 1 write
  YELL trace one 1 read
  un
  YELL trace one {} unset
  two #0
  peek yell deux
  YELL deux
  two #1 deux
  two #2 deux

See also Streams - Deferred evaluation


See also Lazy lists.


I release simple 'lazy' functionality via new Tcl_Obj type, see https://github.com/acanta/tcl-sniplet

Sample: =====

  load ./sniplet.so
  set ls { 1 2 3 }
  set ret [ lazy {ls} { puts "lazy execution"; return [ llength $ls ] } ]
  lappend ls 4
  puts "ls now=$ls"
  puts "ret=$ret"

=====

Result should be:

  ls now=1 2 3 4
  lazy execution
  ret=3