This is a pipe(line)
Where piping arguments down the line makes code more readable
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 😃