# All About Ruby Procs and Lambdas

It has been a while that I wanted to *really* know what procs and lambdas are in Ruby. 
So as an excuse to learn it myself, here is an explanation for you, dear reader :) 

## What are procs and lambdas
To answer this question, we first need to clear a couple of confusions. Those stem from the fact that you will see the term `proc` used interchangeably for tw<zo concepts, and you will also see `lambda` used to mean a different concept than `proc` (which is technically not untrue).

So, what can the term `proc` refer to: 

1. The general concept of proc. According to the [ruby-doc](https://ruby-doc.org/core-3.0.0/Proc.html)
> A proc object is an encapsulation of a block of code, which can be stored in a local variable, passed to a method or another proc, and can be called.
2. procs that are specifically non-lambda procs, it is also in this context that `lambda` is used to mean a different thing than `proc`.

To be precise lambdas are a type of proc, which can come in two flavours: 

- Lambda procs => a.k.a lambdas
- Non-lambda procs => a.k.a procs

I hope this clears some confusion around these concepts. It also allows us to set the stage for our exploration.

![Untitled Diagram.drawio(2).png](https://cdn.hashnode.com/res/hashnode/image/upload/v1647362586289/l18cC48GL.png)

## Practical differences between Lambda and Non-Lambda procs
Lambda and non-lambda procs differ in two concrete things: 
1. How they handle `break` and `return` keywords
2. How they handle too many (or not enough) arguments

### How non-lambda procs handle `break`, `return` and arguments

A non-lambda proc will `break` and `return` from inside the method it has been called in. And will throw a LocalJumpError if it `break`s or `return`s outside of a method.

```Ruby
some_proc = proc do
  p 'I am groot'
  return
end

some_proc.call
p 'I will never get to be printed'

=> 'I am groot'
=> LocalJumpError (unexpected return)

def some_method
  some_proc = proc do 
    p 'I am groot' 
   return
end 
some_proc.call

p 'I will never be printed'
end

some_method

p 'but I will'

=> 'I am groot'
=> nil
=> 'but I will'
```

As for arguments, a non-lambda proc is not strict regarding the number of arguments.
- If you give it too few, it will assign as many as it can and set the rest to nil:

```Ruby
showcaser =  proc {|arg1, arg2| p "I am arg1: #{arg1} | and I arg2: #{arg2}" }

showcaser.call("groot")

=> "I am arg1: groot | and I arg2: "
```

- if you give it too many, it will ignore the arguments at the end

```Ruby
showcaser =  proc {|arg1, arg2| p "I am arg1: #{arg1} | and I arg2: #{arg2}" }

showcaser.call("groot", "black widow", "starlord" )

=> "I am arg1: groot | and I arg2: black widow"
```


### How lambda procs handle `break`, `return` and arguments

A lambda proc will `break` and `return` from inside the proc it has been called in.

```Ruby
some_proc = lambda do
  p 'I am groot'
  return
end

some_proc.call
p 'I will actually get to be printed'

=> 'I am groot'
=> nil
=> 'I will actually get to be printed'

def some_method
  some_proc = lambda do 
    p 'I am groot' 
   return
  end 
  some_proc.call

  p 'I will actually get to be printed'
end

some_method

p 'I will also'

=> 'I am groot'
=> 'I will actually get to be printed'
=> 'I will also'
```

As for arguments, a non-lambda proc is strict regarding the number of arguments.
- If you give it too few, it will throw an argument error:

```Ruby
showcaser =  lambda {|arg1, arg2| p "I am arg1: #{arg1} | and I arg2: #{arg2}" }

showcaser.call("groot")

=> ArgumentError (wrong number of arguments (given 1, expected 2))

```

- if you give it too many, it will again throw an argument error

```Ruby
showcaser =  lambda {|arg1, arg2| p "I am arg1: #{arg1} | and I arg2: #{arg2}" }

showcaser.call("groot", "black widow", "starlord" )

=> ArgumentError (wrong number of arguments (given 3, expected 2))

```
![Untitled Diagram.drawio(4).png](https://cdn.hashnode.com/res/hashnode/image/upload/v1647373069845/Ei3USN7RG.png)

## What do all Procs have in common?
All procs do share (obviously) some characteristics: 
- They remember the context they were called in: 

```Ruby
# This example comes straight from the doc as I found it very explicit

def gen_times(factor)
  Proc.new {|n| n*factor } 
end

times3 = gen_times(3)
times5 = gen_times(5)

times3.call(12)               #=> 36
times5.call(5)                #=> 25
times3.call(times5.call(4))   #=> 60
```

- They both serve to store a piece of code to be executed later on the program.
![Untitled Diagram.drawio(5).png](https://cdn.hashnode.com/res/hashnode/image/upload/v1647373407431/or1F6_qqL.png)

## A cool learning

A cool learning I had from this exploration is that when you use a block of code in Ruby, you create a non-lambda proc that will get called on the method. 

An example of that is map 

```Ruby

array = [1, 2, 3]

array.map do |el| # <= proc start
  p el # <= proc body
end # <= proc end

=> 1
=> 2
=> 3
```

On the other hand, lambda procs are very useful as arguments to higher-order functions, as they behave very close to how a method would behave. 


## Conclusion

I hope this article was as instructive for you as the research has been for me. If you have 15-20 mins to spare, I do recommend that you read through the ruby-doc for [Procs](https://ruby-doc.org/core-3.0.0/Proc.html)

