In my last post, I attempted to sarcastically / humorously introduce the plumbers package. I probably should have saved it for April 1st, but I also don’t think the idea is merit-less. I don’t think that becoming a plumbers expert, adept at large plumbing pipelines, would be a very good way to spend your time, just as I don’t think becoming a pointfree combinator ninja is very valuable (though fun!).
The use-case that this is practical / useful for is the very same that the arrow combinators are usually applied to. Let’s face it – most of the time you’re dealing with the (->) arrow, and use (***), (&&&), first, and second. Perhaps I should take a look at making plumber operators for arrows / categories – I have an inkling that they may be useful for lenses.
Anyway, with this particular arrows use case, you don’t really see long chains of arrow combinators – just one at a time, applied to one or two functions. This is the use case I see for the provided plumbers – just take two functions, and apply, bind, or pair the results after giving them the auxiliary parameters. I don’t think that this is too awful – the types of the auxiliary parameters will often make it pretty clear what’s going on. The plumbing operator, when the code reader is skimming code, indicates “combine these two functions, providing these arguments as an environment to their execution”. They can look closer at the plumber to see what’s really happening to the arguments.
The reddit discussion was interesting!
Particularly interesting is this discussion between ehird and cdsmith. I probably should have commented with my thoughts, but I figured out my opinion a few days later, and it was a little bit longwinded, so I figured a followup post was in order. Thank you, ehird, for defending the idea! Thank you cdsmith, for your well informed assessment!
One thing that’s brought up is the “implementation issues” of this idea. I’d like to note that the binary size overhead of including all of these can be mitigated by using Control.Plumbers.TH.implementPlumber. There may be some overhead from invoking the function – I should really add INLINE pragmas!
The other criticism is that “Point-free style is useful when it helps you think at a higher level of abstraction… but I can’t see how these operators lead to higher levels of abstraction.” This is a fair point, however, holding these operators to the standard of “must increase abstraction”. I would argue that points-free form does not significantly increase abstraction, or as ehird points out, “They’re an abstraction of various forms of composition and pipe plumbing. It’s not like not using point-free style lets you escape the plumbing; you just write it in another way.”
f1 = g . h f2 x = g (h x) -- Manipulating with f1: -- f1 $ 1 / 3 -- (Subst) g . h $ 1 / 3 -- (inline) g ( h ( 1 / 3 ) ) -- Manipulating with f2: -- f2 $ 1 / 3 -- (inline) (\x -> g (h x)) 1 / 3 -- (apply) g (h (1 / 3))
If we view things from a value-centric perspective, then our code during evaluation will be full of lambdas, in order to bind these values to names. If we instead view them with a function-centric perspective, we often end up being able to reason about code by direct substitution without beta reduction. I think that the plumbing operators lead to similar substitutional reasoning, and can be good when used tastefully. The question is whether the rules of plumbers (which I should probably write down in a post) are too confusing for reasoning to be effective. It’s quite possible!
The plumbers experiment led me to think about language support for such “classes of identifiers”. It’d be interesting to support using a context free grammar to specify all of the operators / names that something can generate. Then, importing this would import the infinite set of operators generated. They need to be context free, such that we can test for the intersection of identifiers when re-exporting such generators. This would be a huge change to Haskell, for not very much pay off – but interesting to think about!