Ruby: Enable Multi-Arg method block shorthand

In Ruby, there’s a shorthand to call a function block on each element of in a array, with & notation.

For example:

## Long version
[1, 2].map { |n| n.to_s }


## Shorthand
[1, 2].map(&:to_s)

# returns ["1", "2"]

The above code calls the method to_s on each element of the array.

However, when the method being called involves another argument, there isn’t a shorthand to do that.

For example:

def mul(a, b)
  a * b
end

## Long version
[1, 2].map { |n| mul(n, 2) }

# XXXXXXXXXXXXXX
# NO SHORTHAND
# XXXXXXXXXXXXXX

The above code intends to multiply each number in the array with 2. However, ruby doesn’t provide a shorthand for the above function.

In this post, we will use some of ruby’s features to enable a shorthand syntax for multi-arg method blocks

Currying

A simple solution is to use Currying. We can create a function that returns a function that takes one argument.

def mul(a, b)
  a * b
end

def curried_mul(b)
  -> (a) { a * b }
end

## Long version
[1, 2].map { |n| mul(n, 2) }


## Shorthand
[1, 2].map(&curried_mul(2))

The above solution is (kind of) provides a way to add shorthand for multi-arg function. However, it involves creating a curried function. Let’s explore ways to do this without having to create the curried function.

Open Symbol class

In order to interpret a Symbol object as a lambda with a function argument, we need to override call to take a list of arguments.

## Long version
[1, 2].map { |n| n * 2 }

class Symbol
  def call(*argv)
    ->(yielder) { yielder.send(self, *argv) }
  end
end

## Shorthand
[1, 2].map(&:*.(2))

This allows us to instantiate block of function as lambda and call it lazily with a yielder, which is each element of the array and call self which is the symbol (method defined on yielder) with the list of arguments.

This doesn’t work with Object#method though. There’s another way for us to accomplish that.

Open Method class

In order for Object#method to work, we want to defined a method with on Method class which allows us to call a Method object with certain arguments.

def mul(a, b)
  a * b
end

## Long version
[1, 2].map { |n| mul(n, 2) }

class Method
  def with(*argv)
    ->(passed) { self.call(*([passed] + argv)) }
  end
end

## Shorthand
puts [1, 2].map(&method(:mul).with(2))

This allows us to call a Method object with a list of arguments.

Final Thoughts

With ruby’s dynamic and metaprogramming features, we can define ways to call a multi-arg function with a shorthand. This was just a fun exercise to demonstrate some of those features.

Have fun Metaprogramming!

NOTE: This is just for fun. I don’t recommend adding this code to a production app