Elixir — A Tincture for Functional Programming Part 1.3 Closures

Kyle Ledoux
3 min readFeb 3, 2022
a magical elixir from a well know game series

Understanding Closures

This time around I’ll be talking about closures in Elixir. The topic of closures can be tricky to get your head around if you are unfamiliar with the concept, but I’ll do my best to give a quick rundown here of what they are and how they function within Elixir.

What’s a closure?

The concepts of closures are not unique to Elixir and chances are you have encountered them in other languages whether you knew it or not.

A closure is a combination of a function’s definition and references to its surrounding state1. So, a function, when it is defined, will maintain references to the variables that are defined in its surrounding scope so that it can reference and utilize those variables during invocation. In plain English, this basically means that the function can still “see” the variables in its outer scope, even if it is invoked elsewhere in the program.

Scoping. Explain this please.

Scoping in a program commonly works in one of two ways: dynamically or lexically.

Lexical scoping is the method of scoping that Elixir and most other languages use. With lexical scoping the variables that a function has access to when it is invoked is determined by where the function is defined.

With dynamic scoping, the variables that a function has access to is determined by where the function is invoked.

So, with Elixir, we are using lexical scoping, and to understand which variables a function will have access to, we need to look at the code where it is defined, not invoked!

Bottoms up!

Closures in Elixir work on the basis of lexical scoping, so we need to know what the code looks like where the function was defined to understand what variables it can access when it is invoked. In Elixir, functions will have visibility of all variables that are defined above them, and within their outer scope. This is referred to as the function’s upward scope.

When the compiler encounters a function definition containing a reference to variable, it will scan upwards for the referenced variable and match the first occurrence that it finds.

So, the assignment closest to the function and above it, while remaining in its outer scope, will be used.

Here we can see that variables that are not in the function definition’s outer scope will not be visible in the closure (obviously):

That’s cool. But, why?

Using closures allows us to pass values to a function without having to worry about doing so explicitly during invocation. So, if the function is invoked asynchronously, for example, we can make sure that it still has visibility for variables that it might need.

Here is a contrived example where some function is called asynchronously by another function to generate a report using some status codes. But, we don’t need to worry about being able to pass in the status codes as arguments when the function is invoked because they are accessible via closure:

Closures are interesting and can come into play in interesting ways. I’m sure there is a great deal more to discover here, so I’ll keep working!

--

--

Kyle Ledoux

I’m a software engineer with a talent for distractable curiosity and a passion for questionable humor.