DEV Community

Cover image for Ruby blocks made easy, part II, curry and procs as arguments
Leandro Proença
Leandro Proença

Posted on

Ruby blocks made easy, part II, curry and procs as arguments

In the previous post, we learned that methods can be transformed into procs to be evaluated later.

One thing worth to mention is that, the method itself can be used like a proc: every Method structure has a method called call:

Time.now # => 2021-04-10 17:22:05

Time.method(:now).call # => 2021-04-10 17:22:06
Time.method(:now).to_proc.call # => 2021-04-10 17:22:07

# checking the classes
Time.method(:now).class # => Method
Time.method(:now).to_proc.class # => Proc (lambda)
Enter fullscreen mode Exit fullscreen mode

Given that, the method structure is good enough to be used in later evaluations. In theory, it is NOT a Proc. But in practice, it behaves like proc lambdas and will evaluate the expression later.

We can also evaluate methods later with arguments:

def multiply(a, b)
  a + b
end

method(:multiply).call(2, 4) # => 8
Enter fullscreen mode Exit fullscreen mode

Proc with arguments (curry)

Sometimes it's useful to define a proc with some static arguments, which can be evaluated later along with the dynamic arguments. These arguments can be "curried" to the proc using the method .curry:

# creates a proc with no curried arguments
#   and calls using two dynamic arguments
method(:multiply).call(2, 4) # => 8

# creates a proc with the first argument curried
#   and calls using one remaining dynamic argument
method(:multiply).curry[2].call(4) # => 8
method(:multiply).curry[3].call(5) # => 15

# checking the class
method(:multiply).class # => Proc
method(:multiply).curry[4].class # => It's also a Proc
Enter fullscreen mode Exit fullscreen mode

This feature is powerful because it allows us to write more expressive code:

multiply_by_2 = method(:multiply).curry[2]
multiply_by_3 = method(:multiply).curry[3]

multiply_by_2.call(4) # => 8
multiply_by_3.call(6) # => 18
Enter fullscreen mode Exit fullscreen mode

Passing a lambda method as argument to another method

Lambda methods can be passed as arguments to methods like any other valid expression.

Let's suppose we want a method which takes a list of numbers and applies some calculation method to each number, returning a new calculated list:

def map_numbers(numbers, calculation)
  new_list = []
  for number in numbers
    # `calculation` is a proc, but we don't care about
    #    its logic: we simply evaluate whatever the
    #    calculation is to the number
    new_list << calculation.call(number)
  end
  new_list
end
Enter fullscreen mode Exit fullscreen mode

Now, we want to use the method map_numbers to take a list of numbers and return a new list with each number multiplied by 2:

# declaring the list
numbers = [1, 2, 3]

# declaring the "calculation" proc, using the "2" curried
#    as we've seen in the previous example
multiply_by_2 = method(:multiply).curry[2]

map_numbers(numbers, multiply_by_2) # => [2, 4, 6]
Enter fullscreen mode Exit fullscreen mode

We can do even better, in a single line, applying other calculations as well:

# multiplying by 2
map_numbers([1, 2, 3], method(:multiply).curry[2])

# multiplying by 4
map_numbers([1, 2, 3], method(:multiply).curry[4])

# multiplying by 42
map_numbers([1, 2, 3], method(:multiply).curry[42])
Enter fullscreen mode Exit fullscreen mode

We could go beyond, supposing we'd have more calculation methods:

map_numbers([1, 2, 5], method(:sum_by).curry[2])
map_numbers([2, 4, 9], method(:square_of).curry[3])
Enter fullscreen mode Exit fullscreen mode

Conclusion

We learned that procs can be passed as arguments to another methods and, optionally, can use curried arguments, making our code appear more declarative.

In the next and last post of this series, we will unblock more fundamentals and introduce blocks.

Top comments (0)