What's up with the & in Ruby and some background on procs
Tags: ruby, beginners, learning
Published: 2020-10-22 02:29:59


Scrolling through API docs and Stack Overflow threads on Ruby inevitably reveals a number of unexplained `&`s floating around. Being new to Ruby and engineering in general, I thought I'd dig into what the ampersand, known as the **safe operator**, is and does.

The safe operator calls `.to_proc` on whatever it proceeds. But what, praytell, is a proc and what does it mean to become one? Before we get into `&`, we have to briefly fall down the rabbit hole of the `Proc` class. 

### Procs

On the surface, this isn't too terribly complicated. An instance of the `Proc` class is simply an executable block of code that can be stored in a variable to be called later or even passed as an argument to a method. A block of code is a grouping of statements called by a method. It is surrounded by `{ }` or the `do end` keywords; think about what comes after `.each`. 
```ruby
cube = Proc.new { |i| i**3 }
cube.call(2)
# => 8
``` 
Note the `.call` method needed to call the proc instance which can also be accomplished with the shorthand `cube.(2)` or `cube[2]`. 

As mentioned, procs can also be used to pass a block as an argument of a method.
```ruby
def i_take_a_proc(proc_arg)
  proc_arg.call
end
proc_arg = Proc.new { puts "I'm a block being passed as an argument!" }
i_take_a_proc(proc_arg)
# => I'm a block being passed as an argument!
```
Now we can take this new (micro)superpower and apply the safe operator. Remember the safe operator simply calls `.to_proc` on its operand.

```ruby
def make_proc(&block)
  block
end
hello = make_proc { |i| puts "#{i} says hello!" }
hello.call("The Moon")
# => The Moon says hello!

```
When Ruby sees the `&`, it expects a proc to follow. Since we provide a block, Ruby will try to coerce it into a proc by calling `.to_proc` on the block.

### Special Behaviors of Procs

Now that we have a handle on what procs are, let's talk about some of the properties that make them such beautiful and unique snowflakes. First off, procs (and blocks) are a type of **closure**, meaning that they remember the environment in which they are created. They essentially form a snapshot of the variables in their scope and will use that snapshot when they are called even if those variables have been reassigned.
```ruby
def run_proc(say_hi)
  hi = "Hello"
  say_hi.call
end
hi = "Hola"
say_hi = Proc.new { puts hi }
run_proc(say_hi)
# => Hola
```
This will puts `Hola` to the screen **NOT** `Hello` because that was the value of the `hi` variable that our new proc had access to when it was created and - because procs are closures - it holds onto that assignment of the variable.

Another interesting feature of procs is how they handle an explicit `return`. Rather than simply returning from their own block they will return from the surrounding context as well.  
```ruby
proc_that_returns = Proc.new { return "I'm Free!" }
proc_that_returns.call
```
Running this code outside of a method will produce a `LocalJumpError` because it is trying to return out of the top-most context. 

Finally, procs are surprisingly laidback objects in the Ruby world. They are comfortable ignoring nil values and are not picky about the number of arguments they are given.

```ruby
proc_with_two_args = Proc.new { |x, y| x.even? }
proc_with_two_args.call(6) 
=> true 
```
This proc expects two arguments: `|x, y|`. Even though it only receives one, no `ArgumentError` is raised! 

### Lambdas, Briefly

It's hard to dive into procs without talking about lambdas, but I'm here to talk about the safe operator so I'll make it quick. A lambda is a type of proc with slightly different behaviors. Namely, they **do not** return out of the surrounding scope with an explicit return, and they are particular about the number of arguments they accept. That's all!
```ruby
lambda_with_two_args = lambda { |x, y| x.even? }
lambda_with_two_args.call(6) 
=> ArgumentError
```
### The Safe Operator, Finally

So, what does this all mean for our friend the safe operator, `&`? Well, using what we now know, we can employ it to the benefit of some nice syntactic sugar. 

This is most commonly seen when `&` is called on a symbol in the argument of an enumerator.
```ruby
animals = ["dog", "platypus", "scarab"]
animals.map(&:length)
=> [3, 8, 6] 
#This is equivalent to: animals.map {|animal| animal.length}
```
This is a neat trick, but what's going on here? Well, the safe operator indicates that Ruby expects a proc to follow it. Instead, it finds a symbol (`:length`) so it calls `to_proc` on the symbol, resulting in something like this:
```ruby
{ |a| a.send(self) }
``` 
In this case, `self` is the symbol `:length`, so Ruby calls the `length` method on the object that is calling `(&:length)`. To be fair, this is likely a gross oversimplification of what is actually going on, but it helps clear things up a bit.

Another bit of syntactic wizardry the safe operator offers is a kind of failsafe. Because the safe operator is comfortable with missing arguments and nil data, it can be used when nil data may be present and you don't want it to break your code.
```ruby
array = [1, 2, 3, nil, 5]
array.map { |e| e.even? }
=> NoMethodError
array.map { |e| e&.even? }
[false, true, false, nil, false] 
```
This is a bit cleaner than writing lengthy `if` statements to check if all your data exist before using it. That said, it should be used **carefully** if having nil values in your data will be a major problem later. It can also be used when chaining method calls together. The safe operator will return `nil` when it finds the first nil object.
```ruby
 #No safe operator :(
if genre && genre.artist && genre.artist.album
...
end
#With safe operator
if genre&.artist&.album
...
end
```

###Wrap up

Safe operators and procs may not change your life or even your code, but they can clean things up a bit. At the very least, they make it look like you know what you're doing (maybe not always a good thing).