This article discusses thunks and writing non-strict code in Ela.
Unlike many other programming languages, Ela combines strict and lazy evaluation. A compiler can rewrite order
of your statements and defer evaluation of certain expressions. However,
the main evaluation strategy for Ela is strict, and Ela prefers eager evaluation wherever possible, using
laziness as a "fallback strategy".
Of course, sometimes you might prefer a different behavior and enforce additional laziness of a program. In order
to allow this Ela provies a support for explicit laziness. You can mark a particular section of code as thunk
in order to defer its evaluation.
This is done using built-in syntax like so:
x = & 2+2
An evaluation of an expression with a leading ampersand is deferred. Also such an expression
is evaluated just once (and after that its value is memoized).
An expression, which evaluation is deferred, is called a thunk
. Thunks are calculated only when their value
is actually needed. For example, standard prelude function
(addition) is strict - therefore if you apply
this function to a thunk, a thunk will be immediately evaluated.
However, one can write a function with a non-strict semantics. For example, a simple tuple construction function,
defined like so:
x => y = (x,y)
is non-strict, as soon as it doesn't evaluate its arguments. If you pass thunks to this function, it will yield
a tuple with thunks.
Some standard prelude functions are also non-strict. For example, list construction function
:: is non-strict.
It doesn't evaluate its arguments, including the tail of a list. If you pass a thunk as a list tail, it will
construct a lazy list:
xs = 1::2::
xs' = 1::(& 2::)
is also lazy by its second argument. When used with lists, it can construct lazy lists
as well as strict lists.
A lazy list in the example above is not very useful, however one can easily writen a function that generates an
infinite list like so:
inf x = & x :: inf (x+1)
take 10 <| inf 0
is defined in
module and takes first 10 elements from a list. A lazy list is evaluated on demand only.
In example above we have taken first 10 elements from this list - and only ten elements are evaluated.
One can define other lazy functions in a similar manner. For example, this how a lazy
function (which takes a function and a
list and maps this functions to the elements of a list) can be defined:
map _  = 
map f (x::xs) = f x :: (& map f xs)
And now we can safely map our infinite list using this function:
xs = map (*2) (inf 0)
take 10 <| xs
One can force an evaluation of a thunk using prelude
force function like so:
t = & 2+2
This function always forces thunks recursively:
force (& 2 + (& 2+2))
Also thunks are automatically forced when used in sequence expressions (created using
or using a so called "banged" pattern, e.g.:
fun !a = a
which is an equivalent to:
fun a = force a