Do we need a letrec construct?

Raimo Niskanen raimo@REDACTED
Tue Oct 31 10:22:29 CET 2000


I recently ran into a problem where it would have been nice to have
something like the Lisp letrec construct in Erlang.

Therefore I will drop a few ideas to this list to see how big interest
there is for such a feature.

The problem
===========

My problem was that to write a generator function that reads a file and
generates a lazy list/stream (i.e [Term | fun/0] or fun/0), so I started
out something like this: 

mk_stream_from_file(Filename) ->
    {ok, File} = file:open(Filename),
    Stream = 
        fun() ->
            case mk_term_from_file(File) of
                {ok, Term} ->
                    [Term | Stream]; % Stream not bound
                eof ->
                    []
            end
        end
    end.

This does unfortunately not work since the variable Stream is not bound
while defining the fun, instead I had to write like this:

mk_stream_from_file(Filename) ->
    {ok, File} = file:open(Filename),
    fun_stream_from_file(File).

%% Create a fun/0 with environment.
fun_stream_from_file(File) ->
    fun() -> stream_from_file(File) end.

stream_from_file(File) ->
    case mk_term_from_file(File) of
        {ok, Term} ->
            [Term | fun_stream_from_file(File)];
        eof ->
            []
    end.

This I find less clear, it pollutes the module namespace and it creates
more funs in runtime, which is not for free. However, it works!

The problem is to write funs that are recursive, without passing the fun
as an argument to itself, and in the general case several funs might
need to call each other. 

Suggestions
===========

Here are two suggestions for extensions to Erlang. In this example I
alternate between two different mk_term_from_file/1 functions:

Suggestion 1, Lisp letrec style:

mk_stream_from_file(Filename) ->
    {ok, File} = file:open(Filename),
    letrec
        Stream = 
            fun() ->
                case mk_term_from_file(File) of
                    {ok, Term} ->
                        [Term | Stream2]; % Stream2 is actually bound!
                    eof ->
                        []
                end
            end,
        Stream2 = 
            fun() ->
                case mk_other_term_from_file(File) of
                    {ok, Term} ->
                        [Term | Stream1];
                    eof ->
                        []
                end
            end,
    end,
    Stream.

One problem with this syntax is that Stream2 (and of course Stream) is
actually bound in a source line (maybe far) above the line where it
appears. I also think that 'letrec' is a lousy out-of-context choice of
keyword, there must be a better alternative (but i have not given it
much thought yet).

Suggestion 2, new ':= 'assignment operator style:

mk_stream_from_file(Filename) ->
    {ok, File} = file:open(Filename),
    {Stream,
     Stream2} := 
        {fun() ->
             case mk_term_from_file(File) of
                 {ok, Term} ->
                     [Term | Stream2]; % Stream2 is actually bound!
                 eof ->
                     []
             end
         end,
         fun() ->
             case mk_other_term_from_file(File) of
                 {ok, Term} ->
                     [Term | Stream1];
                 eof ->
                     []
             end
         end},
    Stream.

One problem with this syntax is what limitations to put on the left hand
and right hand terms of the operator. This 'bind before definition'
assignment semantics is only reasonable for funs on the right hand side
and unbound variables on the left hand side, I think. An alternative for
the operator might be '<-' which is used today in list comprehensions.

Comments, anyone?

/ Raimo Niskanen, Erlang/OTP



More information about the erlang-questions mailing list