ruby

An Introduction to Lambdas in Ruby

Darren Broemmer

Darren Broemmer on

An Introduction to Lambdas in Ruby

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:

ruby
a_lambda = lambda { puts "Hello world!" }

More commonly in Ruby, you will see the -> shorthand notation used to define a Lambda expression.

ruby
a_lambda = -> { puts "Hello world!" }

If you simply puts a_lambda, here is what you get.

ruby
puts a_lambda => #<Proc:0x0000000108f77bb0 10.rb:6 (lambda)>

Lambdas can use a block definition notation as well.

ruby
a_lambda = lambda do puts "Hello world!" end

The Lambda is invoked as shown below, and the output is as expected:

ruby
a_lambda.call => Hello world!

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:

ruby
a_lambda_with_params = lambda {|val| val**2 }

The call method can take parameters to match your Lambda signature:

ruby
three_squared = a_lambda_with_params.call(3) puts "Three squared is #{three_squared}"

This outputs the following:

sh
Three squared is 9

Ruby offers other syntax options for invoking a Lambda. All of the following statements are equivalent:

ruby
val1 = a_lambda_with_params.call(3) val2 = a_lambda_with_params.(3) val3 = a_lambda_with_params[3] val4 = a_lambda_with_params===3

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:

ruby
numbers = [1, 2, 3, 4, 5] squares = numbers.map { |n| n**2 } puts squares.join(", ")

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:

ruby
numbers = [1, 2, 3, 4, 5] square_lambda = lambda { |n| n**2 } squares = numbers.map &square_lambda puts squares.join(", ")

This outputs the following:

sh
1, 4, 9, 16, 25

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.

sh
in `map': wrong number of arguments (given 1, expected 0) (ArgumentError)

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.

ruby
# Example: Use a lambda to defer execution of code until later def do_something_later(&a_lambda) @deferred_action = a_lambda end # Later, when the condition is met, call the deferred action if some_condition_is_met @deferred_action.call end

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.

ruby
# Static implementation at class load time field :last_modified, type: Time, default: Time.now # The implementation below defers the lambda execution until document creation time field :last_modified, type: Time, default: ->{ Time.now }

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.

ruby
class PrintVisitor < Visitor NODE_TYPES = { IntegerNode => ->(node) { puts "IntegerNode: #{node.value}" }, FloatNode => ->(node) { puts "FloatNode: #{node.value}" }, ExpressionNode => ->(node) { puts "ExpressionNode:" node.left.accept(self) node.right.accept(self) } } def visit(node) handler = NODE_TYPES[node.class] handler.call(node) end end

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:

ruby
# Example: Use a lambda for dynamic code generation def create_method(name, block) define_method(name, &block) end a_lambda = -> { puts "Hello, world!" } create_method(:hello, a_lambda) hello # Output: "Hello, world!"

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:

ruby
# Example: Use a lambda as a closure to capture runtime variables def create_counter(start) lambda { start += 1 } end counter = create_counter(0) puts counter.call # Output: 1 puts counter.call # Output: 2

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!

Darren Broemmer

Darren Broemmer

Darren enjoys inspiring through the written word and making complex things easy to understand. His interests include science and physics, and enough math to make sense of them both. He creates high-quality content and technology solutions, and tweets occasionally about it.

All articles by Darren Broemmer

Become our next author!

Find out more

AppSignal monitors your apps

AppSignal provides insights for Ruby, Rails, Elixir, Phoenix, Node.js, Express and many other frameworks and libraries. We are located in beautiful Amsterdam. We love stroopwafels. If you do too, let us know. We might send you some!

Discover AppSignal
AppSignal monitors your apps