DEV Community

loading...
Cover image for Lambdas and '&' in Ruby

Lambdas and '&' in Ruby

Daniel Hintz
Junior full-stack developer with focus in JavaScript (ES6) and Ruby. Experience with React, Redux, Rails, among others.
・3 min read

places.map(&:city)

I've seen this pattern a lot in Rails and have wondered what exactly it's doing. In researching it, I learned about lambda functions and procs in Ruby, and that in turn led to last week's post about currying functions in Ruby, which relies on basic lambda functionality. Going a bit further, I started looking into the beforementioned pattern, now knowing a bit more about lambdas. At first, I thought I'd understood it, but then I realized that it was more complicated than it initially seemed. The part that was most confusing to me was whether or not the lambda call was supposed to be a symbol, so this is a quick guide for when to use a symbol in this pattern and when not to, as well as an explanation of why.

Here's the TL;DR version if you're not interested in the investigation and the why:

Use Symbol Don't Use Symbol
Method belongs to the object you are iterating on Using a Lambda you've created
Iterating on array of objects and operating on a property

Okay now let's look at how I got here.

First thing, let's create a lambda function for use throughout this guide. If you read last week's post, this will look very familiar:

half = ->(numerator) {
    if numerator % 2 === 0
        return numerator / 2
    end
    return "#{numerator} can't be halved evenly"
}

puts half.call(10)
  # => 5
puts half.call(3)
  # => "3 can't be halved evenly"
Enter fullscreen mode Exit fullscreen mode

This pattern is super common for iterators, so we'll want a couple of arrays too:

numarry = [0, 1, 2, 3, 4]
textarry = ["a", "b", "c"]
Enter fullscreen mode Exit fullscreen mode

Alright, so what now?

Say we want to half each of the integers in numarry. We should be able to use '&:' in an iterator to call our lambda like this, right?

puts numarry.map(&:half)
Enter fullscreen mode Exit fullscreen mode

Wrong! You get undefined method 'half' error!

The reason is because half is a lambda, not a symbol, so you need to change it to:

puts numarry.map(&half)
  # => [0, "1 can't be halved evenly, 1, "3 can't be halved evenly", 2]
Enter fullscreen mode Exit fullscreen mode

That makes sense (and seems obvious), so then what's with the symbol pattern at the top? I can't just convert my method to a symbol, so it's not just a matter of preference. Something else must be going on.

I tried a different method, but this time I decided to use a built-in method instead of a local one that I created:

puts textarry.map(&upcase)
Enter fullscreen mode Exit fullscreen mode

And I got an undefined local variable or method 'upcase' error! So, just for fun, I tried it as a symbol:

puts textarry.map(&:upcase)
  # => ["A", "B", "C"]
Enter fullscreen mode Exit fullscreen mode

And voila - it worked! But the question is why?

We know that everything in Ruby is an object, ultimately inheriting from the global class Object. When you define a method, it's defined with an object as its owner. You can see what the owner of a given method is by using the #owner method:

def double(num)
  num * 2
end

puts method(:double).owner
  # => Object
Enter fullscreen mode Exit fullscreen mode

but what about our lambda method at the top?

puts half.class
  # => Proc
Enter fullscreen mode Exit fullscreen mode

So our lambda is not on the same object as the Integer that we are calling it on. This gives me an idea! If we think back to the original pattern: places.map(&:city) the symbol is a property of the Place class, similar to how upcase is technically a property of the String class. Meanwhile, our poor lambda is out there on its own and not associated to anything. There's the difference! Time to test the theory. If I'm correct, then by adding a method to an existing class, I should be able to pass it as a symbol to my iterator.

# modify Integer class to include double method
class Integer
  def double
    self * 2
  end
end

puts numarry.map(&:double)
  # => [0, 2, 4, 6, 8]
Enter fullscreen mode Exit fullscreen mode

It worked! After all this investigating, I finally came to the simple guidelines below.

Use Symbol Don't Use Symbol
Method belongs to the object you are iterating on Using a Lambda you've created
Iterating on array of objects and operating on a property

As a bonus, I gained a stronger understanding of how Ruby works under the hood, which is always a good thing!

Discussion (0)