On your left: a pipeline. On your right: still not a pipe.

This is a pipe(line)

Where piping arguments down the line makes code more readable

Michel Belleville
Published in
3 min readJan 28, 2019

--

me> About that pipeline thing…

wat> That |> thing?

me> That one. We were talking about it that other time, when we discovered Maybe.

wat> Sure 😃 What would you like to know?

me> What *is* that |> thing? Is that an operator? A function? A magic glyph?

wat> Some of these things (not the last one 🧙) ; let’s see if it has a signature:

(|>) : a -> (a -> b) -> b

me> So it *is* a function! But… why the parens around the |>?

wat> That’s an interesting question. When there are parens around a function like this, it means it’s what we call an “infix” function, which is a (uselessly confusing) word that means you will usually put the function in-between its two (and only two) arguments when you use it.

me> Like (+)? 🤔

wat> Like (+). You can use it in its “natural” (infix) form: 1 + 2 + 3 which is usually less confusing than its prefix form: (+) 1 ((+) 2 3).

me> So, it means that when I’m using |>, I’ll put the first argument on its left, and the second on its right… Like that?

whateverTheFirstArgIs |> whateverTheSecondArgIs

wat> Exactly 👍 Unless you want to use it as a prefix function, in which case you’ll write it surrounded with parens, like:

(|>) whateverTheFirstArgIs whateverTheSecondArgIs

me> Right. Got it. Now what does it do?

wat> Can’t you kinda guess by the signature?

(|>) : a -> (a -> b) -> b

me> Hmm… It:
- takes something of type a
- then it takes a function that takes an a and gives back a b
- finally, it gives you back a b
I’d go as far as assume that the b it returns is the result of taking the a we were given and passing it to the a -> b transformation function, right?

wat> You’re getting good at reading a function’s signature 😁

me> Your flattery won’t make me forget that this is positively silly! 😅 I mean, if I already have an a -> b function and an a, I can cut the |> middleman and just apply the arguments to the function and get the result!

wat> Sure you can 😃 How will it look like though? Remember when you wanted to convert a String into an Int?…

me> Yes, we used String.toInt to get a Maybe Int from the String, and Maybe.map to work with the Int (if any) and eventually Maybe.withDefault to unwrap the Int result (or provide a default value in its stead)…

wat> Let’s write it without pipes, then with pipes:

 -- without pipesMaybe.withDefault 0 (Maybe.map (\n -> n + 123) (String.toInt theString))-- with pipestheString
|> String.toInt
|> Maybe.map (\n -> n + 123)
|> Maybe.withDefault 0

wat> Which version would you rather read? 😉

me> I see… 😲 using pipes makes it more obvious that we take theString, feed it to String.toInt, feed the result to Maybe.map (\n -> n + 123) and feed what comes out of it to Maybe.withDefault 0.

wat> Just like you can put water in pipes and let it flow down the line. By the way, have you met (<|), (|>)‘s bizarro twin?

(|>) : a -> (a -> b) -> b
(<|) : (a -> b) -> a -> b

me> Wait… (<|) is exactly like (|>) but the arguments are reversed? How do you use it?

wat> Like this:

Maybe.withDefault 0 <| Maybe.map (\n -> n + 123) <| String.toInt <| theString

me> So, it’s exactly like using (|>) except the “water” “flows” the other way around?

wat> We’ll make a real ducky out of you someday 🐣 It’s also exactly like using parens around the last argument of each function, only without using the parens (using (<|) as a delimiter between operations).

me> Cool.

wat> And wait until you learn about function composition with (<<) and (>>)

me> Litterally. Next time.

wat> Next time 😃

--

--