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