Lazy lists

Sarnold on 2008-05-24: We can have lazy lists in Tcl because we can build new control structures, and here is a package which mimics Python's range() function with Tcl's foreach syntax.


namespace eval ::lazy {
	namespace export lazy range
	# constructor
	proc lazy {initval shift last} {
		list $initval $shift $last
	}
	# gets the first element AND the new list
	proc _shift {list} {
		set first [lindex $list 0]
		lset list 0 [eval [lindex $list 1] [list [lindex $list 0]]]
		list $first $list
	}
	proc shift {listvar} {
		upvar $listvar list
		::foreach {c list} [_shift $list] break
		set c
	}
	# boolean true if the next element (car) is the last
	# does not modify list
	proc last {list} {
		eval [lindex $list 2] [list [lindex $list 0]]
	}
	# the new control structure : accepts a lazy list 'list'
	# otherwise it behaves like foreach except it does not handle
	# multiple list iterations
	proc foreach {vars list body} {
		set res ""
		while {1} {
			set last no
			::foreach v $vars {
				if {$last} {return $res}
				set last [last $list]
				uplevel 1 [list set $v [shift list]]
			}
			set res [uplevel 1 $body]
			if {$last} {return $res}
		}
	}
	# a range generator
	proc range {start end {step 1}} {
		set end [expr {$end - $step}]
		list $start [list ::lazy::rangeshift $step] [list ::lazy::rangelast $end]
	}
	proc rangeshift {step value} {
		incr value $step
	}
	proc rangelast {end value} {
		expr {$end <=$value}
	}
}

# test code
namespace import lazy::*
foreach step {1 2 3} {
	set l [range 0 10 $step]
	lazy::foreach x $l {puts $x}
}