Closures in Erlang

-module(closures).
-export([main/0]).

int_seq() ->
    I = 0,
    fun() ->
        NewI = I + 1,
        put(i, NewI),
        NewI
    end.

main() ->
    NextInt = int_seq(),
    io:format("~p~n", [NextInt()]),
    io:format("~p~n", [NextInt()]),
    io:format("~p~n", [NextInt()]),
    
    NewInts = int_seq(),
    io:format("~p~n", [NewInts()]).

Erlang supports anonymous functions, which can form closures. Anonymous functions are useful when you want to define a function inline without having to name it.

The function int_seq returns another function, which we define anonymously in the body of int_seq. The returned function closes over the variable I to form a closure.

In the main function, we call int_seq, assigning the result (a function) to NextInt. This function value captures its own I value, which will be updated each time we call NextInt.

We can see the effect of the closure by calling NextInt a few times:

NextInt = int_seq(),
io:format("~p~n", [NextInt()]),
io:format("~p~n", [NextInt()]),
io:format("~p~n", [NextInt()]),

To confirm that the state is unique to that particular function, we create and test a new one:

NewInts = int_seq(),
io:format("~p~n", [NewInts()]).

To run the program:

$ erlc closures.erl
$ erl -noshell -s closures main -s init stop
1
2
3
1

In Erlang, we use the process dictionary (put/2 and get/1) to maintain state within the closure, as Erlang variables are immutable. This approach mimics the behavior of the original example while adhering to Erlang’s functional paradigm.

The last feature of functions we’ll look at for now is recursion, which is a fundamental concept in functional programming languages like Erlang.