Learn With Me: Elixir - Closures (#45)
Closures are a topic I meant to cover back when we were learning about functions, but I completely forgot about it until now. So let's cover it before we move on to other topics.
Like C# and Javascript, Elixir has closures. Most of you who are reasonably proficient in Javascript are likely familiar with closures. C# has closures too, but they tend not to be used as frequently as in Javascript.
A closure refers to the value of something (like a variable or a function) in an outer scope being available in the context of the inner scope, even if that inner scope is invoked much later. In Javascript, for example, a closure can capture a variable and still reference it even if that variable has gone out of scope in the outer scope.
If you're unsure what that means, let's look at some examples, which will hopefully help make it clearer to you.
Closures in Javascript
Here's an example from Javascript with the corresponding output.
function createSomeFunction() {
let number = 3;
return () => console.log(number);
}
let someFunction = createSomeFunction();
someFunction();
Here's the output in Javascript REPL.
3
=> undefined
Even though the "number" variable has gone out of scope by the time "someFunction" was called, it's value was preserved because it was referenced in the inner scope of the function that was returned. That is called a closure.
This sort of thing is common in Javascript, which makes frequent use of closures to encapsulate data.
What happens if you change the value of a variable that was captured in a closure?
let name = "Invader Zim"
let printName = () => console.log(name);
printName();
name = "Invader Skoodge"
printName();
Invader Zim
Invader Skoodge
=> undefined
As you can see, a closure in Javascript will refer to the captured variable by reference, so if the value of the captured variable is changed between the time it is captured in the closure and the time that it is used, the updated value will be used.
Closures in C#
C# works much the same way. Here are the same code examples repeated in C#.
using System;
class MainClass {
public static void Main (string[] args) {
var someFunction = CreateSomeFunction();
someFunction();
}
private static Action CreateSomeFunction()
{
int number = 3;
return () => Console.WriteLine(number);
}
}
Here's the output.
3
Value capture in C# closures works like Javascript.
using System;
class MainClass {
public static void Main (string[] args) {
string name = "Invader Zim";
Action printName = () => Console.WriteLine(name);
printName();
name = "Invader Skoodge";
printName();
}
}
Here's the output.
Invader Zim
Invader Skoodge
Closures in Elixir
Of course, we're learning Elixir, not C# or Javascript, so let's take a look at how closures work in Elixir. We'll implement the same two examples in Elixir.
defmodule Example do
def create_some_function() do
number = 3
fn -> IO.puts(number) end
end
end
some_function = Example.create_some_function()
some_function.()
Here's the output.
3
Value capture in Elixir works a bit differently. The value of a variable in a closure is always what it was when the closure was defined.
name = "Invader Zim"
print_name = fn -> IO.puts(name) end
print_name.()
name = "Invader Skoodge"
print_name.()
Invader Zim
Invader Zim
The name
variable printed by the closure always has the same value, even though the value of the name
variable has been changed.
Why does it work this way? It looks to me that the value of the variable name
is captured by the closure rather than having any kind of reference to the variable. Since data is immutable in Elixir, the inner scope continues to refer to the original value and not the modified value, which is located somewhere else in memory.
This behavior makes sense in a functional language because that's a good way to avoid side effects. The side effect of a value captured in a closure changing later on due to some side effect can cause unexpected behavior, and functional languages focus on minimizing such behavior. This also helps prevent unexpected behavior when data is shared between multiple processes.
We can do also make use of closures in anonymous functions.
say_something = fn (something) -> (fn -> IO.puts(something) end) end
say_dog = say_something.("dog")
say_cat = say_something.("cat")
say_dog.()
say_cat.()
elixir anonymous_closures.exs
dog
cat
You can find this code in the the "lwm 45 - Closures" folder in the code examples in the Learn With Me: Elixir repository on Github.