Comparing iteration in JavaScript and Elixir

In JavaScript (and languages like it), it is common to see code like this:

// JavaScript let array = [ - 1 , 2 , - 3 , 4 ] let sum = 0 for ( let i = 0 ; i < array . length ; i ++ ) { if ( array [ i ] < 0 ) continue sum = sum + array [ i ] } assert ( sum == 6 )

This code is doing a pretty simple task. It calculates the sum of all elements in a which are positive. However, it’s difficult to read at a glance, and this style of iteration is error prone.

An idiomatic translation of this code to Elixir would be:

# Elixir sum = [ - 1 , 2 , - 3 , 4 ] |> Enum . reject ( fn elem -> elem < 0 end ) |> Enum . sum () assert sum == 6

The Elixir version is more declarative, and certainly easier to read. (To be fair, JavaScript now includes functions like Array.prototype.filter and Array.prototype.reduce that allow you to use a similar style.)

However, a new user might initially try something familiar from other languages and run into some problems:

# Elixir sum = 0 Enum . each [ - 1 , 2 , - 3 , 4 ], fn elem -> unless elem < 0 do sum = sum + elem end end assert sum == 6 # Failure: sum == 0

Why doesn’t this work?

In languages like JavaScript, variables which are captured by a closure (or from enclosing scopes, like in the first example above) are captured by reference:

// JavaScript let a = 1 let get = () => a let set = ( value ) => a = value set ( 2 ) assert ( get () == 2 )

However, in Elixir, variables are captured by value. When we refer to sum in the anonymous function we are passing to Enum.each/2 , we are referring to the value that sum had at the time we created the function:

# Elixir a = 1 get = fn -> a end set = fn value -> a = value end set . ( 2 ) assert get . () == 1 a = 2 assert get . () == 1

Additionally, in Elixir, even when we “reassign” a variable, we are not actually changing the original variable. Code like this:

# Elixir a = 1 a = 2 assert a == 2

is actually equivalent to the following code:

% Erlang A1 = 1 , A2 = 2 , assert ( A2 =:= 2 ).

So, in our example before, set is not actually assigning to the original a . It’s assigning to a “new” a , and the original a has not changed.

To each their own…

Given everything we just discussed, this code may be somewhat shocking:

# Elixir use MutableEach sum = 0 each elem <- [ - 1 , 2 , - 3 , 4 ], mutable: { sum } do if elem < 0 , do : continue sum = sum + elem end assert sum == 6

“How is this possible in Elixir? I thought we couldn’t refer to variables by reference!”, you might say. Well, thanks to the power of macros, almost anything is possible in Elixir.

Introducing: MutableEach , a library which implements “mutable” iteration in Elixir.

Ultimately, the example above expands to something that looks somewhat like:

sum = 0 { sum } = Enum . reduce_while [ - 1 , 2 , - 3 , 4 ], { sum }, fn elem , { sum } -> try do if elem < 0 , do : throw { :mutable_each_continue , { sum }} sum = sum + elem { :cont , { sum }} catch { :mutable_each_continue , mutable } -> { :cont , mutable } { :mutable_each_break , mutable } -> { :halt , mutable } end end assert sum == 6

MutableEach mostly relies on Enum.reduce_while/3 and throw . Under the hood, there is still no actual mutability. The variables that are declared as “mutable” are simply provided as an accumulator within reduce_while , automatically returned at the end of each iteration, and then exported back into the original vars after the reduce is complete.

continue and break are implemented as macros which throw a tuple containing an atom representing the type of interrupt ( continue or break ) and the “mutable” variables. The function generated and passed to reduce_while contains a catch clause that returns {:cont, values} or {:halt, values} depending on the type of interrupt that was thrown.

Just another great example of how powerful and flexible Elixir is, thanks to macros.

Don’t try this at home

I probably shouldn’t have to say this, but this was simply an experiment, and MutableEach should not be used in your Elixir code. Simply using Enum.reduce_while/3 (no throw needed, probably!) directly in your own code is more explicit, and explicit is better than implicit.

If you enjoyed this blog, follow me on Twitter here!