The Elixir Pipe Operator
8 points by azhenley
8 points by azhenley
I’m not much into using adjectives like ‘beautiful’. This is technology, I prefer to focus on practicality than art. The pipe operator is indeed very practical because it reduces the sequential transformation of a parameter through a. Series of transformations, to the bare minimum syntax needed. Hence maximizing developer productivity via code economy. I never got it why most languages don’t provide something like this, as everyone needs it.
fearing a term such as beauty is denying the human experience and your comment suggests not only that you wish to be more like a computer but that you expect other programmers to also be more like computers and to that I say no, do not neg other people’s humanity, our humanity is, well, beautiful
‘Beautiful’ actually has a strong legacy in the Erlang world:
Make it work, then make it beautiful, then if you really, really have to, make it fast. 90 percent of the time, if you make it beautiful, it will already be fast. So really, just make it beautiful!
-Joe Armstrong
I think most languages, that don’t feature pipe operator, rarely thought about it because, for a long time, OOP dominated the language market. In OOP languages you have method chaining which is a very similar concept to a pipe operator:
numbers
.filter(even?)
.map(square);
What pipe operator/threading macro really provides is decoupling operation from implementation. In OOP languages, your objects have to implement map
, filter
, and other such methods for method chaining to work. If an object’s type changes mid-chain, the resulting object type also has to implement any methods further down the chain. With pipe operators, it’s not necessary, because in the end it’s just ordinary functions that are being called:
numbers
|> filter(even?)
|> map(square)
filter
and map
here are not methods of some class, but ordinary functions. Code reuse doesn’t depend on objects inheriting some interface that provides all of the necessary methods in the chain, but the chained functions now have to be generic or highly specified. Clojure beautifully solves this by providing a generic sequence abstraction, where every collection can be efficiently represented as a sequence, and map
and filter
work on sequences, transforming their arguments into a sequence.
(->> numbers
(filter even?)
(map square))
;; same as (map square (filter even? numbers))
So the key difference, at least for me, is that with method chaining, generic methods have to be parts of the object, while with threading or piping, they can be just generic functions. With piping/threading ordinary functions can appear in the chain, but with method chaining, methods have to be provided for such functions to be used. Though OOP languages usually provide methods for everything, ordinary functions are a rarity there.
What pipe operator/threading macro really provides is decoupling operation from implementation.
This is an gigantic understatement. Requiring/expecting people to implement some interface in every single object type is not realistic and ultimately unnecessary.
You are focusing on collections of things and interfaces for irritable types. Many language provide this to varying levels. The pipe operator is not limited to this. It works with any type and any single parameter function. It’s just syntax sugar for passing a parameter to a function and pass its result to the next one.
The pipe operator is not limited to this. It works with any type and any single parameter function. It’s just syntax sugar for passing a parameter to a function and pass its result to the next one.
Yes, I know that, as shown in the Clojure example at the end. I was focused on collections more because I was speaking of method chaining, and why OOP-focused languages, where everything is a method, might not have it.
Of course, you can use pipe operator/threading macro with any function, that’s what they’re for.
I didn’t know about this concept and I love it. I’m not saying I want to have it in the language I’m using (I don’t know yet), but I appreciate the idea in its own right, it is interesting and seems useful.
I’m wondering.. Is there a collection of PL ideas / concepts out there that spans languages?
What’s cool is when you can define it in the language, with, say, let (|>) a b = b a
- then you can just as easily implement its friend a $ b
and use both to write whatever looks the most natural, parenthesis-free.
Then you could remove the initial value from the pipeline and now you have a function composition operator.
Then you might think, well, what’s the point in having an operator at all? Surely just having the things next to each other is enough? … and end up with Forth.
I realized I should look at pldb.io, and indeed it has a kernel of it. It is just not as well populated as I would hope it to be: