Lori Holden

An exploration of Lambda in Ruby

Lets start off by taking a look at traditional block passing in ruby.

5.times {|i| puts i}

Looks great right? Go ahead and pass that block off to another method.

Using the block/yield syntax:

def first
  yield "Hello!"
end

def second
  # first yield...?
  first {|str| yield str}
end

second {|str| puts str}

Instead of just passing the original block we are forced to create another block which yields to the original block. Of course, we could make the situation a little better by catching the block by reference.

Using the block/ref syntax:

def second(&block)
  first &block
end

second {|str| puts str}

This is certainly more DRY and visually pleasing over the first example. Do notice however that Ruby is actually converting our block into a lambda (a Proc object technically) for us now. Even worse is that Ruby handles argument and block syntax somewhat antagonistically.

Antagonistic blocks:

# Are we passing a block to our upto method, making a hash,
# or passing something to our object 5?
1.upto 5 {|i| puts i}

# SyntaxError: (irb):23: syntax error, unexpected '{', expecting $end
# 1.upto 5 {|i| puts i}
#           ^
# 	from /Users/lori/.rvm/rubies/ruby-1.9.3-p0/bin/irb:16:in `<main>'

# We could solve this with do / end
1.upto 5 do |i|
  puts i
end

# Resort to using parens
1.upto(5) {|i| puts i}

# Or just pass a lambda by reference
1.upto 5, &->(i) {puts i}

A first class lambda would gain us the ability to pass it around, manipulate it, use it as a function / method, and treat it like any other object. Unfortunately this is not entirely the case with Ruby. We have an odd mixture of blocks, lambdas, by reference conversions, and so forth. Having said that, lets see how close we can get to a first class lambda.

Our previous example using a lambda:

def first(&bl)
  bl.call "Hello!"
end

def second(&bl)
  first &bl
end

second &->(str) {puts str}

And let's have a little more fun with the lambda syntax.

FizzBuzz:

fizzbuzz = ->(i) do
  (i%15).zero? and next "FizzBuzz"
  (i%3).zero?  and next "Fizz"
  (i%5).zero?  and next "Buzz"
  i
end

puts (1..100).map(&fizzbuzz).join("\n")

Fantastic right? Instead of having an awkward mixture of blocks, yields, and arguments we just favor the syntax which gives us the strongest lambda. While it's not quite 'first class' (you can't treat it as a method) at least we are a lot closer then relying on blocks.

Certainly there is a performance impact moving away from blocks right?

Utility methods for the following benchmarks:

def by_yield(i)
  yield i
end

def by_ref(i, &block)
  block.call i
end

By Reference vs Yield:

Benchmark.bm do |b|
  b.report("built-in \w block") do
    1.upto(1_000_000) {|i|}
  end

  b.report("built-in \w lambda") do
    1.upto(1_000_000, &->(i){})
  end

  b.report("yield \w block") do
    1_000_000.times &->(i) do
      by_yield(i) {|x|}
    end
  end

  b.report("ref \w block") do
    1_000_000.times &->(i) do
      by_ref(i) {|x|}
    end
  end

  b.report("1 off yield \w block") do
    by_yield(1) {|i| }
  end

  b.report("1 off ref \w block") do
    by_ref(1) {|i| }
  end
end

# Iterating over a MILLION uses of yield vs by reference.
#                        user       system     total       real
#  built-in \w block     0.050000   0.000000   0.050000 (  0.051122)
#  built-in \w lambda    0.040000   0.000000   0.040000 (  0.043985)
#  yield \w block        0.120000   0.000000   0.120000 (  0.117864)
#  ref \w block          0.840000   0.050000   0.890000 (  0.885240)

# A single use of yield vs reference
#                        user       system     total       real
#  1 off yield \w block  0.000000   0.000000   0.000000 (  0.000003)
#  1 off ref \w block    0.000000   0.000000   0.000000 (  0.000003)

Surprisingly the impact is pretty marginal. Our largest impact actually comes from the by reference syntax for methods. Given that we have already incurred the cost of 'by reference' in our previous method definition, let's look at lambda vs block.

Lambda vs Block:

Benchmark.bm do |b|
  b.report("ref \w block") do
    1_000_000.times &->(i) do
      by_ref(i) {|x|}
    end
  end

  b.report("ref \w lambda") do
    1_000_000.times &->(i) do
      by_ref i, &->(x) {}
    end
  end

  b.report("1 off ref \w lambda") do
    by_ref 1, &->(x) {}
  end

  b.report("1 off ref \w block") do
    by_ref(1) {|i| }
  end
end

# Iterating over a MILLION uses of block vs lambda.
#                       user       system     total       real
#  ref \w block         0.830000   0.050000   0.880000 (  0.872216)
#  ref \w lambda        0.920000   0.040000   0.960000 (  0.957568)

# A single use of block vs lambda.
#                       user       system     total       real
#  1 off ref \w lambda  0.000000   0.000000   0.000000 (  0.000004)
#  1 off ref \w block   0.000000   0.000000   0.000000 (  0.000003)

Still a pretty small difference.

I do wish the whole lambda/block/closure situation in Ruby was better unified. For now, it certainly looks nicer to use blocks when working with a DSL.

Menu DSL:

menu "File" do
  item "New"
  item "Open"
  menu "Open Recent", -> do
    generate_recently_opened_list
  end
  separator
  item "Exit"
end

With a more unified syntax, we wouldn't need a special form for lambdas. Consider for example Smalltalk with it's single 'block' syntax.

example
  "Because Smalltalk is fun!"
  |numbers smaller a|
  numbers := OrderedCollection new.
  numbers add: 1; add: 2; add: 3; add: 4.

  "Gather items that are less then or equal to 2"
  smaller := numbers select: [:i| i <=2 ].

  "save off a block in variable a"
  a := [:i :x sum|
         sum := i + x
         sum
       ].

  "And return it"
  ^a