Thursday, August 6, 2009

An example of lazy evaluation for generating lists

One thing i've learnt from Erlang is only that its very expressive despite its annoyances but a good outcome of this expressiveness is possibly the availability of lazy(delayed) evaluation. In the example of passing lists to functions, the lists are evaluated fully before being passed to functions.

The big deal here is that you could potentially craft a function that's computationally expensive but the user of this function might not need all the results all at once. Example would be using these sorts of functions to obtain database records and to display them on a webpage but every good UI designer knows that its ineffective to display all 15,000 records at 1 time and you could design a web page that displays 10 - 20 records at a time and have "next" buttons/links to display subsequent pages.

So, naturally we can craft functions such that we can have lazy evaluation. A simple example is as follows where the function next/1 behaves like a generator function but only delayed; another way to think of this concept is possibly python generators. In my example below, its not hard to imagine that next/1 could be your computationally expensive operation.

1 -module(lazy_eval).
2 -export([next/1, test/2]).
3
4 next(Num) ->
5 fun() -> [Num|next(Num+1)] end.
6
7 test(Seq, Fun) when Seq =:= 100 -> [Seq];
8 test(Seq, Fun) when Seq < 100 ->
9 [Val|Func] = Fun(),
10 % io:format("~p ~p~n", [Val, Func()]),
11 [Val | test(Seq + 1, Func) ].

A sample output (with debugging statements):

6> lazy_eval:test(1, lazy_eval:next(1)).
1 [2|#Fun<lazy_eval.0.5801025>]
2 [3|#Fun<lazy_eval.0.5801025>]
3 [4|#Fun<lazy_eval.0.5801025>]
4 [5|#Fun<lazy_eval.0.5801025>]
...
99 [100|#Fun<lazy_eval.0.5801025>]
[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,
23,24,25,26,27,28,29|...]
Have fun!

No comments: