While the core of ATS is based on call-by-value evaluation, there is also direct support in ATS for lazy (that is, call-by-need) evaluation.
There is a special language construct $delay for delaying or suspending the evaluation of an expression (by forming a thunk), and there is also a special function lazy_force for resuming a suspended evaluation (represented by a thunk). The abstract type constructor lazy of the sort (t@ype) => type forms a (boxed) type when applied to a type. Given an expression exp of type T, the value $delay(exp) of the type lazy(T) represents the suspended evaluation of exp. Given a value V of the type lazy(T) for some type T, calling lazy_force on V resumes the suspended evaluation represented by V. If the call returns, then the returned value is of type T. The interface for the function template lazy_force is given as follows:
where the symbol !laz indicates a form of effect associated with lazy-evaluation. Note that the special prefix operator ! in ATS is overloaded with lazy_force.In prelude/SATS/stream.sats, the following types stream_con and stream are declared mutually recursively for representing lazy streams:
datatype stream_con(a:t@ype+) = | stream_nil of ((*void*)) | stream_cons of (a, stream(a)) where stream(a:t@ype) = lazy (stream_con(a))
The following code gives a standard implementation of the sieve of Eratosthenes:
// fun from(n: int): stream(int) = $delay(stream_cons(n, from(n+1))) // fun sieve ( ns: stream(int) ) :<!laz> stream(int) = $delay let // // [val-] means no warning message from the compiler // val-stream_cons(n, ns) = !ns in stream_cons(n, sieve(stream_filter_cloref<int>(ns, lam x => x mod n > 0))) end // end of [let] // end of [$delay] // val thePrimes = sieve(from(2)) //
The function template stream_filter_cloref is of the following interface:
fun{a:t@ype} stream_filter_cloref (xs: stream(a), pred: a -<cloref> bool):<!laz> stream(a) // end of [stream_filter_cloref]
Let us see another example of lazy evaluation. The follow code demonstrates an interesting approach to computing the Fibonacci numbers:
// val _0_ = $UNSAFE.cast{int64}(0) val _1_ = $UNSAFE.cast{int64}(1) // val // the following values are defined mutually recursively rec theFibs_0 : stream(int64) = $delay(stream_cons(_0_, theFibs_1)) // fib0, fib1, ... and theFibs_1 : stream(int64) = $delay(stream_cons(_1_, theFibs_2)) // fib1, fib2, ... and theFibs_2 : stream(int64) = // fib2, fib3, fib4, ... ( stream_map2_fun<int64,int64><int64>(theFibs_0, theFibs_1, lam (x, y) => x + y) ) (* end of [val/and/and] *) //
fun{ a1,a2:t0p}{b:t0p } stream_map2_fun ( xs1: stream(a1), xs2: stream(a2), f: (a1, a2) -<fun> b ) :<!laz> stream(b) // end of [stream_map2_fun]
Let us see yet another example of lazy evaluation. A Hamming number is a positive natural number whose prime factors can contain only 2, 3 and 5. The following code shows a straightforward way to generate a stream consisting of all the Hamming numbers:
// val compare_int_int = lam (x1: int, x2: int): int =<fun> compare(x1, x2) // macdef merge2 (xs1, xs2) = stream_mergeq_fun<int> (,(xs1), ,(xs2), compare_int_int) // val rec theHamming : stream(int) = $delay ( stream_cons(1, merge2(merge2(theHamming2, theHamming3), theHamming5)) ) (* end of [theHamming] *) and theHamming2 : stream(int) = stream_map_fun<int><int> (theHamming, lam x => 2 * x) and theHamming3 : stream(int) = stream_map_fun<int><int> (theHamming, lam x => 3 * x) and theHamming5 : stream(int) = stream_map_fun<int><int> (theHamming, lam x => 5 * x) //
fun{a:t0p} stream_mergeq_fun ( xs1: stream(a), xs2: stream(a), (a, a) -<fun> int ) :<!laz> stream(a) // end of [stream_mergeq_fun]
With stream-based lazy evaluation, an illusion of infinite data can be readily created. This illusion is given a simple programming interface plus automatic support for memoization, enabling a programming style that can often be both elegant and intriguing.
In general, it is difficult to estimate the time-complexity and space-complexity of a program based on lazy evaluation. This is regarded as a serious weakness. With linear stream-based lazy evalution, this weakness can essentially be removed.
Please find on-line the entirety of the code used in this chapter.