DEV Community

Cover image for An Introduction to Lambdas in Ruby
dbroemme for AppSignal

Posted on • Originally published at blog.appsignal.com

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:

a_lambda = lambda { puts "Hello world!" }
Enter fullscreen mode Exit fullscreen mode

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

a_lambda = -> { puts "Hello world!" }
Enter fullscreen mode Exit fullscreen mode

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

puts a_lambda
=> #<Proc:0x0000000108f77bb0 10.rb:6 (lambda)>
Enter fullscreen mode Exit fullscreen mode

Lambdas can use a block definition notation as well.

a_lambda = lambda do
  puts "Hello world!"
end
Enter fullscreen mode Exit fullscreen mode

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

a_lambda.call
=> Hello world!
Enter fullscreen mode Exit fullscreen mode

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:

a_lambda_with_params = lambda {|val| val**2 }
Enter fullscreen mode Exit fullscreen mode

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

three_squared = a_lambda_with_params.call(3)
puts "Three squared is #{three_squared}"
Enter fullscreen mode Exit fullscreen mode

This outputs the following:

Three squared is 9
Enter fullscreen mode Exit fullscreen mode

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

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
Enter fullscreen mode Exit fullscreen mode

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:

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

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:

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

This outputs the following:

1, 4, 9, 16, 25
Enter fullscreen mode Exit fullscreen mode

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.

in `map': wrong number of arguments (given 1, expected 0) (ArgumentError)
Enter fullscreen mode Exit fullscreen mode

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.

# 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
Enter fullscreen mode Exit fullscreen mode

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.

# 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 }
Enter fullscreen mode Exit fullscreen mode

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.

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
Enter fullscreen mode Exit fullscreen mode

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:

# 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!"
Enter fullscreen mode Exit fullscreen mode

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:

# 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
Enter fullscreen mode Exit fullscreen mode

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!

Top comments (0)