Functions, Procedures, Lambdas and Closures
This is the first part of a series of articles on functions.
Definitions
A function is one the most basic units of abstraction in a computer program. It’s a
“sequence of program instructions that perform a specific task, packaged as a unit” Wikipedia.
Wikipedia lists these synonyms for the term function: subroutine, procedure, routine, method, subprogram or a callable unit.
You’ll find varying terminology in different programming languages and software products. Here is how I like to call things based on my procedural, OOP and FP background, and context I’m working in:
- Function - a code unit that exists in a global scope or a namespace and does not belong to any object. Pure functions that take input and produce output without any side effects can be also simply called functions.
- Procedure - a code unit that produces side effects and may or may not return a value.
- Method - a code unit that is a part of an object. Functions that are part of a class (static functions as in Java) but not of an object instance are not methods in OOP sense. They could be functions or procedures.
You usually can override a method in a subclass to redefine it’s behavior, but not a standalone function, unless language specific tricks are used like shadowing or monkey patching.
In either case everything can be called a ‘function’ for simplicity, ‘procedure’ can be used to highlight side effect nature in FP context, and ‘method’ to refer to OOP function belonging to an object.
Takeaway
The main point is that this terminology is confusing and in some contexts may provide additional hints, but essentially all these terms are interchangeable.
Let’s define a function that we can work with later on:
Python:
Scala:
Now we are off to lambdas.
Lambdas - λ
“Lambda is the eleventh letter of the Greek alphabet that is λ, and capital form Λ.” (Wikipedia).
Let’s try again. Lambda is just an anonymous function. That means it’s a function for which we didn’t bother to give a name because it doesn’t deserve one. Usually lambdas are fairly short functions that can be written inline and don’t require regular function declaration.
Take a look at this example where lambda takes a value and doubles it:
Python:
Scala:
Imagine a situation where you need to pass or return behavior (function) from/to a function.
Let’s reuse our doublex
function to take a list of ints and double them:
Python:
Scala:
Ok great, but what does it have to do with lambdas?
You might have noticed that doublex
function is so simple that we don’t really want to bother declaring it.
Let’s try to achieve the same with lambdas that we’ve already written above:
Python:
Scala:
That’s it for lambdas for now. You usually use lambdas with HOF
(Higher-Order Functions) - functions that take other
functions as parameters (like our map
), or return functions as their results.
Some languages like Python restrict lambdas to fit on a single line due to syntax constraints, while others, like Scala,
allow you to use code blocks { ... }
to write longer definitions. In general you should try to avoid defining long and
complex lambdas, and here is why. If you assign a lambda to some variable then it might be possible to unit test it separately and to reuse it.
Otherwise lambdas are not unit testable nor reusable by default, which is OK since they are usually small. Large lambdas
make code less readable, so consider refactoring them into functions when they grow too big.
Closures
Now that lambdas don’t look greek to you we are ready to look at closure which is a very simple idea. Closure is a function that captures outside context / environment / variables and uses it locally. That sounds a bit cryptic you might say. Essentially, closure references a variable defined in outside scope, locally in its inner scope (body). This variable value is usually copied and stored together by the program with the “enclosing” function aka closure. Take a look at code examples and read this part again afterwards.
Python:
Example from Wikipedia
Both f
and h
contain nested functions which will become closures once x
is bound to some value and these
nested functions capture that value.
Let’s see the same example in Scala for completeness:
One thing to note that if your function references a global variable it doesn’t make it a closure. Closures capture context from outside each time they are created, while referencing global variables requires no special mechanisms as environment is always the same.
Conclusion
Closures and lambdas are essential building blocks of many modern languages and especially shine in FP languages like Scala, Haskell. They help to create many useful abstractions, but this is a topic for another article.
Next time let’s talk about types of functions or methods, or subroutines, or whatever you prefer to call them, and their arguments.