I’ve been really loving Elixir, and one of the things I’ve loved the most is the pipeline operator
|>. You see it in cases like this:
Basically what it does is pass the return value from a previous function or expression as the first argument in the next function. Pretty cool, huh?
I’ve been finding that there are some functional paradigms that have been really helpful in increasing the testibility and reducing the complexity of my Ruby code, and as a sort of experiment I wanted to see if I could implement this operator in Ruby!
Before I get into my actual implementation, I want to say that what I came up with is a terrible idea. It involves monkey patching the
Object class, and that is just asking for weird behavior in any app of significant size or complexity. So, DON’T DO THIS! But, hopefully this thought experiment will get you thinking about other ways in which functional paradigms might help you write better OO code.
First Lesson - Ruby doesn’t like
I should have expected this, but when I try and define a method
|>, the Ruby interpreter has some issues with me.
If we can’t use the actual
|> operator, I decided to just use a method called
pipeline. There’s already a
pipe method defined in
IO (which came as no surprise), but
pipeline isn’t yet defined as an instance method in the Ruby standard library or in Ruby Core, so I figured I’d go with that. It’s defined as a class method in the
Open3 module, but I didn’t think that my implementation would clash too bad.
So, here’s what I ended up with. It’s actually much simpler than I had imagined, but I don’t yet have all of the functionality of Elixir’s pipeline operator included in here. It’s kind of a middleground at the moment, but it does rely on passing around functions, which is really what I wanted to get at here.
For a less stupid way of implementing this behavior, you could have the following module that you include in whichever classes you want to have this kind of functional behavior like so:
What this allows us to do is create
UnboundMethod objects, and then chain them together, passing around a certain piece of data and doing some sort of transformation on it as we define in our function. Here are a couple of examples:
I really wish I could write that as:
but I have a feeling the minute I start doing custom patches to the Ruby interpreter then I’m going to get into some real trouble, so I’m just going to live with what I have a the moment!
Why did I choose to have
UnboundMethods as the methods that we’re passing around? Well, it seems like Ruby has already settled on that as the main way that functions are passed around. There’s lots of use
to_proc calls when you use the symbol to proc syntax (i.e.
array.reduce(:+)), and in the last year or so I’ve seen a lot more usage of the
&method helper to generate an
Method object. And
UnboundMethod just seems cool, so I threw that in for good measure.
So, there ya go! Now of course chainable methods are nothing new in Ruby, but I think there is an important distinction between this and the way many of them are frequently implemented. Most of the time when you see chains of methods you’re thinking about stuff using the
Enumerable module, and that’s really designed to operate on collections of data like arrays and hashes. This
pipeline method is much more generic, without any sort of implicit ideas of enumerating over a collection of objects.