Non-tech founder’s guide to choosing the right software development partner Download Ebook
Home>Blog>Applications of lambda in ruby

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!

Discover More Reads

Recent Projects

We take pride in creating applications that drive growth and evolution, from niche startups to international companies.

Do you have a tech idea?

Let’s talk!

By submitting this form, you agree with JetRockets’ Privacy Policy

If you prefer email, write to us at hello@jetrockets.com