elm christmas

The Pipe Operator

←Previous postNext post →

Dealing with nested functions

A 2 min read written by
Martin Solheim

When composing complex expressions with multiple chained function calls your code quickly becomes hard to read. It's hard to see what function is associated with which values. It also makes it difficult to add more chained function calls without making a mess of the parentheses and you might end up with this nice little message:

-- TOO MANY ARGS ----------------------------------------------------------- elm

The `repeat` function expects 2 arguments, but it got 3 instead.

16|    String.repeat 3 String.toUpper (String.append "h" "o")
       ^^^^^^^^^^^^^
Are there any missing commas? Or missing parentheses?

The compiler tells us exactly what to do, and if we add parentheses as suggested, we get our code working:

String.repeat 3 (String.toUpper (String.append "h" "o"))
-- "HOHOHO : String"

With backward function application, you can get rid of those pesky parentheses. Instead of using parentheses we can use the backward pipe infix operator. First, you type the function you want to use, then the values are then piped to the function f <| x == f x. It might seem unnecessary here, but it quickly becomes useful when we have multiple function calls or computed values, like in our string example:

-- Backward function application
String.repeat 3 <| String.toUpper <| String.append "h" "o"
-- "HOHOHO" : String

What's even cooler is forward function application. Using the forward pipe infix operator, the expression before the operator gets passed to the function following the operator: x |> f == f x. This means that you can switch the order of the function application, making it easier to follow the logic of the expression.

-- Foreward function application
String.append "h" "o"
    |> String.toUpper
    |> String.repeat 3
-- "HOHOHO" : String

In addition to removing the need for parentheses, the pipe operator lets you declare the value to be modified at the top and then apply functions to that value from top to bottom. Instead of nesting the function applications from the inside out.

-- Instead of this...
List.map addPlease (List.filter tooExpensive wishList)

-- You can write this...
wishList
    |> List.filter tooExpensive
    |> List.map addPlease
←Previous postNext post →