Understanding Pipes in Elm

This is the seventh post in a series of posts about Elm for JavaScript developers. If you're a JavaScript developer, and you want to know more about Elm, stay tuned to this series of posts or sign up for DailyDrip, and check out all the episodes we have in our elm topic.

Here is the previous post in this series:

Today we will see how pipes work in Elm. For me, coming from JavaScript, the pipe operator looks like something difficult and sometimes weird. We will see at the end of this text that the pipe is only a matter of syntax, and it's here to make our life easier.

Motivation

Why should we use pipes? Using a pure functional language, we only have functions. It means we don't have an object containing actions (methods) and attributes. We can't really pass an object containing all the methods for a given function.

If I have a function, I need to pass all the necessary things for this function. Also, sometimes I will need to use this result in another function.

Let's see an example. I will read the function from left to right. I could say: Here I have a string, and this calls the filter method, after this the isNotSpace, and then toUpper and finally it reverses the string.

String.filter isNotSpace (String.toUpper (String.reverse string))

What is the first function to be executed? It is the function furthest to the right. It is String.reverse. It is not filter. This is different from what I just read.

If I write the function composition this way, using parentheses and nesting function calls, it can be difficult to read. But this is really common on object-oriented languages.

When it comes to object-oriented languages, we are often applying a lot of methods to an object.

Happily, there is a design pattern to help us when we are coding in a object-oriented language. The Method chaining design pattern.

We can see an example in Ruby.

class Person

  def name(value)
    @name = value
    self
  end

  def age(value)
    @age = value
    self
  end

  def introduce
    puts "Hello, my name is #{@name} and I am #{@age} years old."
  end

end

person = Person.new
person.name("Peter").age(21).introduce
# => Hello, my name is Peter and I am 21 years old.

Here we are concatenating the methods name, age and introduce.

And now when I read it, from left to right, it sounds a lot better and it reads pleasantly.

The pipes

In Elm we have the pipe operator (<| or |>). The pipes can be |> (pipe forward) and <|(pipe backward). It represents how the data is being passed.

We can see in the Elm lang examples, an interesting example.

Forward Pipe (|>)

This is the example we saw above.

weirdReversal1 string =
  String.filter isNotSpace (String.toUpper (String.reverse string))

Let's see how we could read this in Elm.

weirdReversal2 string =
  string
    |> String.reverse
    |> String.toUpper
    |> String.filter isNotSpace

Now, if I read this we can see it looks like a chain method. It has an clear line of thinking. Just reading this I can tell which function should be executed first. I could also easily apply a new function in the middle of two other functions.

In our example, we are passing the data to the function String.reverse.

string
    |> String.reverse

Backward Pipe (<|)

The backward pipe is an operator we can use to avoid parentheses in function calls.

For example, let's say we have aFunction, receiving an argument that in this case is the result of a toString call.

aFunction (toString 1)

We could re-write this as:

aFunction <|  toString 1

The data flow is flowing from the right to the left. It looks like we are feeding the function aFunction, and this would be more readable.

Summary

We saw how pipes can help us avoid parenthesis, and how they can simplify our life in functional programming. Now, when we see a pipe operator in some language, you can imagine it is just as syntactic sugar for avoiding parenthesis and have an easy way to chain function calls.