Applications of Lambda in Ruby

Thoughts about lambda in Ruby.


Table of Contents

  1.  Intro
  2.  Code Clarification
  3.  Sorting
  4.  Currying
  5.  Higher-order functions
  6.  Closures
  7.  Gotcha!

Intro

When I stumbled upon lambda for the first time in some other Ruby book, my first thought was like 'Hm, interesting thing, but why should I ever use it?'. Wikipedia defines lambda as a not bound anonymous function and gives some use cases as to when lambdas are used in programming. There are a lot of articles about the differences between Proc, Block and Lambda in Ruby, but none of them (at least those I read) answered my question. It took time before I realized that it's, in fact, a nice-to-have tool, which one can use to better express ideas and even optimize code.

Code Clarification

Let's have a look at the following code.

    a = (1..10).to_a
    #lets double it
    a = a.map { |x| x*2 }

But is the example really clear? What if the block provided is a bit more complicated?

    rs = (1..10).to_a
    areas = rs.map { |r| Math::PI * r**2 }

It is still easy to read, but takes a little more effort to understand: we have to keep in mind that we have an array of some values and we map it on some procedure that calulates power of 2 of its only argument and then multiplies it by PI. This procedure looks like a circle area calculation algorithm. So areas is the array of circle areas calculated based on the array of radiuses defined above. Too much effort for a single line of code, huh? To make it clear we can use lambda.

    calculate_circle_area = -> r { Math::PI * r**2 }
    areas = rs.map(&calculate_circle_area)

Now, we first get knowledge about how to calculate the area of a circle and only then we map an array with the defined procedure. Also we can use our lambda as many times as we want.

Sorting

    a = (1..100).to_a.shuffle
    a.sort

It is simple enough, but Ruby is a very expressive language, so why not make our code clearer? Enumerable#sort has zero arguments, but accepts optional code block which defines the way sorting is performed. However, if we try to pass it a lambda - interpretor explodes with Exception!

    # let us do the same as above, but with lambda
    in_ascending_order = -> left, right { left <=> right }
    a.sort in_ascending_order
    #=> ArgumentError: wrong number of arguments (given 1, expected 0)

Ok, it passes lambda as argument, which is erroneous. Of course, we have to pass it as code block! We can easily convert lambda to block with unary & operator which just sends it a :to_proc message. Let's try again.

    a.sort &in_ascending_order

And it worked as planned, more so: we gave our code expressiveness worth of Ruby.

Currying

This one is rather tricky. As we already know, lambdas can take arguments. With currying we can provide it with only some of them.

    sum = -> a, b { a + b }
    add_three = sum.curry.(3)

Now we have add_three lambda which takes only one argument and adds it to 3. We can use it with Enumerable#map:

    a = [*1..100]
    a.map(&add_three)

If there are not enough arguments supplied to curry function it returns new lambda with bound variables. Otherwise it just evaluates lambda with given parameters. So we apply curried sum with bound 3 to array with map.

Higher-order functions

Such functions are just functions that take other functions (or Ruby code blocks) as arguments. And most of us have already used or at least seen them in code. The most frequently used ones are in Enumerable. #map, #select, #reduce and others are all higher-order functions. So, let’s lets make our own!

    class Example
      def initialize(argument)
        @value = argument
      end

      def apply(lambda)
        lambda.(@value)
      end
    end

    double = -> x { x * 2 }

    Example.new(10).apply double

Closures

Lambda in Ruby can bind variables local to the context in which it was defined. Let me clarify this with an example.

    def t(argument)
      -> { puts argument }
    end

    t('one to bind them all').call

So, we defined method #t with arity of 1, which returns a lambda which, in turn, prints an argument to $stdout. Now we can try something more useful.

    def compare_with(threshold)
      -> x { x > threshold }
    end

    # Array generation from Range using splat operator
    a = [*1..100]
    greater_than_10 = compare_with(10)
    a.select &greater_than_10

Let's be more real and apply lambda to a more sophisticated case.

    class CarBuilder
      def initialize
        @car = Struct.new(:body, :wheels, :engine).new
      end

      def add_body(body)
        sleep 1
        car.body = body
      end

      def add_wheels(wheels)
        sleep 2
        car.wheels = wheels
      end

      def add_engine(engine)
        sleep 3
        car.engine = engine
      end

      def get_result
        car
      end

      private
      attr_reader :car
    end

Think of #sleep method as of some IO. To help ourselves build car faster with Threads here is complementary class.

    class Pipeline
      def initialize
        @pipeline = []
      end

      def <<(callable, *args)
        @pipeline << Thread.new(*args, &callable)
      end

      def sync
        @pipeline.each(&:join)
        clear
      end

      def clear
        @pipeline.each(&:kill)
        @pipeline = []
      end
    end

And now we can build our car in parallel Threads with lambdas.

    c = CarBuilder.new
    p = Pipeline.new

    p << -> { c.add_engine("2,5l v8") }
    p << -> { c.add_wheels("18'") }
    p << -> { c.add_body("sedan") }

    p.sync

    puts c.get_result

Gotcha!

Closures may be dangerous. Have a look at this code.

    i = 0
    a = [*1..10].map do |x|
      i = x
      -> { i }
    end

Lambda will capture i by reference - not value. And if you now run this:

    a[0].binding.local_variable_get(:i)
    a[9].binding.local_variable_get(:i)

… the results will be same! So, watch your namespace.

This concludes some applications of lambda I found useful. Happy coding!


Eugene
Komissarov

Backend Developer at JetRockets

Explore more of JetRockets