The Amazing Pipe Operator

Creating some Swift Elixir

While I wait for my Apple Watch to ship, I figured I'd start learning Elixir.

Elixir is a functional programming language with immutable state, an actor-based approach to concurrency, and a delightful syntax. Plus, it runs on Erlang, all of which is to say, it's a very good choice for writing high-performance, highly-concurrent and distributed software.

But, more than that, syntactically, it is very similar to Swift. With pattern matching, function overloading, tuple syntax and trailing closures, it's almost like writing Swift in a crazy Erlang world.

But Elixir has this amazing pipe operator.

[Inside [out code]]

Inside out code is what happens when you take the result of one expression, and pass it into another one, repeated a few times. In Objective-C, this is almost considered a language feature, as message passing can be chained together. However it often leads to code which is hard to read as the logical order needs to be understood inside out.

For example, lets assume we need to calculate a tax filing. For some reason we're using Objective-C, so if we're not careful, we might end up with code like this:

Filing *filing = [[Tax calculateSalesTaxForOrders:[Order forCustomers:[database findCustomers]], inYear:2015] prepareFiling];

But, of course, no sane Objective-C developer would write this, and they'll reformat it to increase readability.

NSArray *customers = [database findCustomers];
NSArray *orders =  [Order forCustomers:customers];
Tax *salesTax = [Tax calculateSalesTaxForOrders:orders, inYear: 2015];
Filing *filing = [salesTax prepareFiling];

This certainly removes the inside-out nature, but it also loses the chaining nature of the code. Additionally, it's a lot more verbose, creating intermediate variables.

Note, that in this example the result of the previous expression is the first argument to the next expression. It turns out that this is a pretty common thing to do in programming. It's especially common in functional programming which focuses on transforming data instead of mutating state. Elixir has a special operator, |> which makes it super easy to pipe the results of one function into the next.

filing = Database.find_customers
                |> Orders.for_customers
                |> sales_tax(2015)
                |> prepare_filing

So, lets try to create this in Swift.

Swift Pipe

Swift allows us to define custom operators so this bit is actually quite easy.

infix operator |> { associativity left }

The infix is because the operator must be used between left and right operands, like an ==. The associativity left is because the data is transformed left-to-right. Of course this operator definition is pretty useless by itself, we additionally need to provide an implementation for it. Because we want to use the pipe between arbitrary functions, which will have arbitrary return types, we need to write a single generic function. Likewise, we don't want to constrain our generic types, because that would limit the operator to only work with specialized functions.

The Elixir pipe operator is built into the language, so it is able to perform the neat trick of inserting a value as the first argument to another function. Let's define our functions in Swift to see how this might work. I've used protocols to make the definitions clearer.

protocol Database {
  static func findCustomers() -> [Customer]
}

protocol Order {
  static func findOrdersForCustomers(customers: [Customer]) -> [Self]
}

protocol Tax {
  static func taxForOrders(orders: [Order], inYear: Int) -> Self
  func generateFiling() -> Filing
}

So, taking these functions, can we just put them into the same structure as the Elixir code?

DB.findCustomers()
  |> Order.findOrdersForCustomers
  |> Tax.taxForOrders(???, inYear: 2015)
  |> generateFiling ???

The short answer is no. This isn't going to work as indicated on the 3rd and 4th line where I've used ???. The taxForOrders:inYear: function requires invoking with an argument. Similarly, the generateFiling doesn't have any argument. However, the first pipe on the 2nd line might be okay, and it hints at the structure of our operator's generic implementation. Essentially, we can see that the structure of each right hand operand must be in the form of a closure which receives a single argument.

We know that we've got two generic types, the input on the left hand side, and the result, and it's not hard to see that the right hand side operand must be a closure which receives the same type as the left hand side, and the function itself returns the result of this closure. Again, this is a very common pattern in functional programming. Putting this together gives us our pipe operator in Swift.

infix operator |> { associativity left }

func |> <Input, Output> (left: Input, right: (Input) -> Output) -> Output {
    return right(left)
}

Given this implementation of our pipe operator, we can refactor the protocols into functions. Functions which have an arity of 1, e.g. foo(bar: Int) -> Void are simply curried functions in Swift, e.g. foo() -> Int -> Void and can be implemented using trailing closures, e.g. foo { bar in }. Functions which have an arity of N where N is greater than 1, can be curried into N-1 arity functions which return a closure. The argument to the closure is the function's original first argument. Using this pattern, our tax filing API is now the four functions below.

func customersInDatabase(db: Database) -> [Customer] {
    // etc
}

func ordersForCustomersInDatabase(db: Database) -> [Customer] -> [Order] {
    return { customers in
        // etc
    }
}

func calculateTaxForOrdersInYear(year: Int) -> [Order] -> Tax {
    return { orders in
        // etc
    }
}

func generateFilingForTax(tax: Tax) -> Filing {
    // etc
}

The final result of our code is now:

let filing = customersInDatabase(db)
  |> ordersForCustomersInDatabase(db)
  |> calculateTaxForOrdersInYear(2015)
  |> generateFilingForTax

Embracing such operators allows us to write code which is easy to read. It encourages the use of pure functions and immutable data types. And this is what functional paradigms boil down to - transforming immutable data via pure functions.

References & Further Reading

  • Elixir - A modern functional language running on-top of Erlang.
  • Programming Elixir - The canonical Elixir book by Dave Thomas which the tax filing example is taken from.
  • Erlang - A powerful, industrial-strength tool to handle problems in concurrent, distributed environments.
  • Swift - Apple's new systems programming language.
  • Swift custom operators - Custom operators in Swift.
  • Instance methods are curried functions - Excellent article from Ole Begemann on curried functions in Swift.

Update: On CocoaPods

I started using |> in a project I'm working on. So, I made a quick µframework for it. It's on GitHub here and you can import it into your Swift project using CocoaPods:

pod 'Pipe'

I have also added curried versions of map, filter, reduce and sorted. I will add convenience functions for the usual suspects, sum, product, max, min etc in due course.

Posted on May 14
Written by Daniel Thorpe