Lambdas are a powerful programming construct used in many languages. These functions allow developers to write code that is more concise, efficient, and easier to maintain, making Lambdas an essential part of any developer's toolkit.
In this article, we'll explore how you can use Lambda functions in Ruby to simplify your code and make it more powerful.
What Is a Lambda Function?
Lambdas are expressions whose value is a function. For this reason, they are also called anonymous functions. The really nice part about this is that we can reference them using a variable in our code, just like a string, integer, or any other object.
Everything in Ruby is an object, and Lambdas are no exception. Specifically, a Lambda is an instance of the Ruby Proc class. They behave the same way as a Proc, except that a Lambda validates the number of parameters. Its use case is targeted towards specific functions, albeit those without a proper name. In fact, the Ruby Proc class has a lambda?
method which returns true if argument handling is rigid (as is the case for a Proc generated by a Lambda).
Side-note: Because of its popularity, it is also important to clarify that AWS Lambda is a different concept to a Lambda in Ruby. AWS Lambda is a cloud-computing service that implements functions without managing the underlying server infrastructure. You can simply write the function in any number of programming languages, including Ruby.
How to Define and Invoke a Lambda Function in Ruby
The canonical form of a Lambda expression is as follows:
More commonly in Ruby, you will see the ->
shorthand notation used to define a Lambda expression.
If you simply puts a_lambda
, here is what you get.
Lambdas can use a block definition notation as well.
The Lambda is invoked as shown below, and the output is as expected:
The mechanism to invoke a Lambda is the same as it is with a Proc. However, it is different from the way in which traditional functions are called. Note that, if you simply reference a_lambda
in your code, it will not invoke the function. This is equivalent to referencing a variable but doing nothing with it.
Lambdas can also accept parameters. The following Lambda returns the square of a number:
The call method can take parameters to match your Lambda signature:
This outputs the following:
Ruby offers other syntax options for invoking a Lambda. All of the following statements are equivalent:
The last few notation alternatives seem quite obtuse, but they work nonetheless.
Now consider that we want to calculate the square of a set of numbers. We might typically write Ruby code such as this:
In this code, what is the expression passed to the map function? It is a block that accepts a single parameter. Thus, it is equivalent to an anonymous function or Lambda. We can rewrite our code as follows:
This outputs the following:
Notice the ampersand that precedes the Lambda expression passed to map
. Why is this needed? Well, the ampersand tells Ruby that this is the special block parameter. If we omit this, Ruby thinks that square_lambda
is a 'regular' parameter. Using that approach, we would get the following error.
One of the main benefits of a Lambda is the convenience and ease of using the function only once. We could also define a traditional method here, but if this is the only place it would be used, a Lambda function provides a cleaner solution for such a 'disposable' function.
Another primary benefit of Lambdas is that they provide a portable mechanism to execute code anywhere in your program. This supports some interesting use cases that we'll explore next.
Lambda Use Cases in Ruby
There are a number of use cases that work well as Lambda functions in Ruby.
First Class Functions
Lambda functions can be passed to other functions for use. This concept is referred to as a first-class function. Consider a use case where you need to execute code at a later point in time, once a certain condition is met.
Callbacks
Callbacks are another related use case for which Lambdas are useful. Lambdas can be used to implement logic invoked at certain points in the application lifecycle, or a specific object’s lifecycle.
A good example of this pattern is found in MongoDB's Mongoid framework. You can set a :default
at the field level, and Lambdas allow you to defer execution until runtime.
Consider a document with a last modified timestamp. The first implementation runs when the class is loaded. However, execution of the Lambda is deferred until the document is created — the outcome you likely want in your application.
The Visitor Pattern
The visitor pattern in Ruby is another use case that can take advantage of the flexibility of Lambdas. If the visitor logic is not known a priori or otherwise fits the Lambda use cases, visitors can be implemented as Lambda functions.
Consider the following code snippet for a visitor to a hierarchy of nodes representing mathematical expressions.
This implementation uses Lambda functions to avoid defining separate methods for each element subclass. Instead, the Visitor
class defines a single visit method that can handle any element subclass by using a Lambda function to call the appropriate method.
This may be a bit over-engineered, but if you consider cases where the visitor logic is not predefined, Lambdas become much more useful.
Functional Programming Paradigms
Lambdas in Ruby enable developers to adopt a functional programming paradigm by creating anonymous functions that can be used as arguments in other functions. This approach allows for creating complex functionality by combining smaller, reusable functions. The use of Lambdas in Ruby code results in concise, readable code. They also leverage key functional programming concepts, such as closures and higher-order functions.
Lambdas can support functional programming features such as currying if you want to take them to that level. Currying permits functions to be partially applied and reused in diverse contexts.
Metaprogramming
Lambdas are used in metaprogramming to generate code dynamically. Consider the following code that adds a method:
Lambdas as Closures
A powerful capability of Lambdas is their ability to create a closure. This encapsulates the function as well as the runtime environment, including variables from the surrounding scope. Variables are closed (or bound) to their values in a Lambda. This is why it is called a closed expression or closure. The execution context is stored in an instance of a Ruby Binding object.
Consider the following example that creates a Lambda which implements an incremental counter:
The Impact of Lambdas on Performance
Performance can be a consideration if you have extremely tight operational requirements. In a tight loop, for example, you will see that Lambdas perform a bit slower than other Ruby code. It is easy to understand why. A Proc object is created to implement a Lambda (as opposed to the runtime engine delegating control to a method).
In the vast majority of use cases, you will likely not notice much difference in performance (considering that computers can execute thousands of instructions in the time it takes to perform a single database query). Nonetheless, this is something to keep in mind.
Wrapping Up
In this post, we explored some of the fundamentals of Lambdas and Lambda use cases for Ruby.
Lambdas provide a powerful fundamental programming construct for Ruby developers. They can be convenient one-time use functions, implementation mechanisms for callbacks, and used for several other use cases. Lambdas can also come in handy on projects that adhere to functional programming paradigms.
Happy coding!
P.S. If you'd like to read Ruby Magic posts as soon as they get off the press, subscribe to our Ruby Magic newsletter and never miss a single post!