skip to Main Content

I’m learning Elixir and I just got to the part about closures. When a language has closures, the first thing I usually do is try to make the closure algorithm. In JavaScript it looks something like this:

let counter = function() {
    let count = 0;
    return function() {
        count += 1;
        return count;
    };
}();

Then, each time that counter is called, it will return a new number in sequence.

counter(); //  returns 1
counter(); //  returns 2
counter(); //  returns 3
counter(); //  etc.

Is it possible to make this in Elixir? The main problem seems to be that count would be immutable in Elixir. I could make it a one-element list, but that sounds like a Bad Idea™. What would be the Elixir way of dealing with this purely hypothetical situation?

2

Answers


  1. The main problem seems to be that count would be immutable in Elixir.

    iex(1)> count = 1
    1
    
    iex(2)> IO.puts count
    1
    :ok
    
    iex(3)> count = 2
    2
    
    iex(4)> IO.puts count
    2
    :ok
    

    In elixir, values are immutable, but variables can me made to point to different places in memory where other values are stored. For example, when the line count = 2 executes, 2 is stored in memory somewhere, then count is bound to that new memory location. After that, no variables are bound to the memory location of 1, so that memory is ready for garbage collection.

    Elixir has closures because a function does carry with it the bindings in the environment in which the function was defined, but the bindings are to specific memory locations, and the values at those memory locations are immutable:

    defmodule A do
      def counter do
        count = 0 
    
        fn -> 
          count = count + 1
          count
        end
    
      end
    
    end
    

    In iex:

    iex(6)> c("a.ex")          
    [A]
    
    iex(7)> counter = A.counter
    #Function<0.55300721/0 in A.counter/0>
    
    iex(8)> counter.()         
    1
    
    iex(9)> counter.()
    1
    
    iex(10)> counter.()
    1
    

    I could make it a one-element list, but that sounds like a Bad Idea™.

    …and it wouldn’t work. Lists are an example of how immutable values in memory are advantageous. When you add a value to the head of a list, elixir creates a whole new list somewhere else in memory. But, instead of copying the old list to the memory location of the new list, elixir knows that the old list is guaranteed to be immutable, so elixir can just use a pointer to the old list. The new memory location consists of the new elements of the list plus a pointer to the old list–no copying required. In the case of a closure, the binding would be to the location of the original list in memory, and any new lists created from the original list would reside elsewhere in memory.

    What would be the Elixir way of dealing with this purely hypothetical situation?

    In elixir/erlang, you can use something called a GenServer to preserve state between function calls:

    defmodule Counter do
      use GenServer
    
      #Client interface:
    
      def start_counter(starting_count) do
         GenServer.start_link(__MODULE__, starting_count)
      end
    
      def get_count(pid) do
        GenServer.call(pid, :increment)
      end
      
    
      # GenServer callback functions:
    
      @impl true
      def init(starting_count) do
        {:ok, starting_count}
      end
    
      @impl true
      def handle_call(:increment, _from, current_count) do
        {:reply, current_count, current_count+1} 
      end
    
    end
    

    When you write:

    GenServer.call(pid, :increment)
    

    elixir looks for a callback function named handle_call() whose first argument matches :increment and executes it, passing in the state as the third argument. You define handle_call() to do what you want, then you send a reply back to the calling process and set the new state.

    In iex:

    iex(1)> c("a.ex")                            
    [Counter]
    
    iex(2)> {:ok, pid} = Counter.start_counter(1)
    {:ok, #PID<0.119.0>}
    
    iex(3)> Counter.get_count(pid)               
    1
    
    iex(4)> Counter.get_count(pid)
    2
    
    iex(5)> Counter.get_count(pid)
    3
    
    iex(6)> Counter.get_count(pid)
    4
    
    Login or Signup to reply.
  2. If you need state, use a process. In Elixir, the idiomatic ways to do that are with Agent and GenServer.

    The example from the Agent docs does exactly as you asked:

    defmodule Counter do
      use Agent
    
      def increment do
        Agent.update(__MODULE__, &(&1 + 1))
      end
    
      # ...
    end
    

    Usage:

    Counter.start_link(0)
    #=> {:ok, #PID<0.123.0>}
    
    Counter.value()
    #=> 0
    
    Counter.increment()
    #=> :ok
    
    Counter.increment()
    #=> :ok
    
    Counter.value()
    #=> 2
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search