Have you heard of Enumerable#every
? It lets you apply a method call to
all elements of an enumerable. Check it out…
[1,2,3].every + 3 #=> [4,5,6]
[1,2,3].every * 3 #=> [3,6,9]
words = [“hello”, “world”]
words.every.upcase!
words #=> [“HELLO”, “WORLD”]
Essentially every is a convenient and nicely readable “fluent notation”
alternative to using a map/collect block.
Now you might be wondering how this bit of magic is achieved? Here is
the
definition.
module Enumerable
def every
Functor.new do |op,*args|
map{ |a| a.send(op,*args) }
end
end
end
As you can see it works via something called a “Functor”. Generally
speaking, a “functor” is simply an object that encapsulates a function.
In
this case I have used the term as a concise reference to a specialized
form
of such to achieve Higher Order
Messaging. The basic definition of this class is:
class Functor
def initialize(&function)
@function = function
end
def method_missing(op, *args, &blk)
@function.call(op, *args, &blk)
end
end
This Functor class can be used in many many ways. Enumerable#every is
just
one of many examples. In fact, Ruby’s own Enumerator class is actually
just
a highly specialized type of Functor.
As useful as a Functor can be, it does however have two downsides. First
it
depends on #method_missing. This means public Object methods
can interfere with its usefulness. This can be worked around by making
Functor a subclass of BasicObject. Though a few public methods remain,
this
is good enough for all practical purposes. A more problematic issue is
the
fact that it requires the creation of an indeterminate object every time
the a method using a Functor is called. This is pretty inefficient,
slows
things down and requires us to be memory conscious. Caching can be used
in
some cases, but rarely is it an ideal fix.
Recently I came up with an idea that would allow these issues to be
circumvented entirely. I realized that since this is a higher order
message, then really it would be best handled as a messaging issue
–hence via the definition of a “higher order method”. In other words if
Ruby had built-in support for the concept, the intermediate object would
not be necessary and consequently interfering public methods would not
exist. To facilitate this, I came up with a potential notation. Here’s
how
it would be used to define Enumerable#every:
module Enumerable
def every => op, *args
map{ |a| a.send(op,*args) }
end
end
(Hmm… as I typed this it occurs to me that maybe =>
would be more
intuitive if it pointed the other way using <=
.) Regardless of the
actual
notation used, the idea is to allow Ruby to handle higher-order-messages
internally as a special type of method.
For reference here are a few other examples of using Functor:
*
*
*
P.S. There is also a functor gem out there, but it is not the Functor as
defined above. The functor gem came along well after the one described
here
(which is included with Ruby F.s). The gems’s implementation is
basically a kind of struct that supports multiple dispatch capabilities.
The above is much more generic and actually can even be used to create
the
functor gem’s more specialized kind with just a bit of extra code.