Advanced Static Types in TypeScript

This course explores the capabilities of TypeScript’s type system and shows how to use advanced static types in practice.

Watch the Course

Implementing a Custom Forward Pipe Operator for Function Chains in Swift

In essence, programs transform data. Input values are somehow manipulated and returned as output data. To keep the complexity of larger programs low, we break them up into smaller parts and abstractions, which are then composed later to form a bigger whole. Divide et impera.

As the name suggests, functional programming focuses on functions as abstractions on a low level. Most of the time, those are pure functions without side effects, meaning they don't change external state, making them safe to use and re-use.

Take the following two simple function definitions in Swift. They expect a single value of type Int which is being incremented or squared, respectively:

func increment(x: Int) -> Int {
    return x + 1
}

func square(x: Int) -> Int {
    return x * x
}

To increment and afterwards square a value, you'd used the functions like this:

let value = 5
let transformed = square(increment(value))

The code works fine, but it's not perfect. We have to read the function applications inside-out. First, increment(value) is evaluated, and the result of that expression is then passed to the square function. Yet, from left to right, we write square before increment, contradicting the application order. Let's take a look at how F# handles this problem.

The Forward Pipe Operator as Seen in F#

F# is a functional programming language that implements the so-called forward pipe operator, written as |>. The operator passes the result on the left side to the function on the right side. Here's an example in F# that implements the two functions and combines them using the operator:

// Define the two functions
let increment x = x + 1
let square x = x * x

// Transform the value
let value = 5
let transformed = value |> increment |> square

As you can see, the data flow can be expressed clearly and concisely. The functions are written in the order they're applied, not backwards or inside-out. That makes it very easy to follow along the transformation of the value as it's passed through the function chain. It feels natural.

Alright, enough marveling about how great the forward pipe operator works in F# already. Let's implement it in Swift.

Custom Operators in Swift

Swift allows you to define custom operators, which is a pretty cool thing. This is what the language guide says about allowed characters:

Custom operators can start with the characters / = - + * % < > ! & | ^ . ~, or Unicode math, symbol, arrow, dingbat, and line/box drawing characters. Their second and subsequent characters can be any of the above characters, and/or Unicode combining characters

When you define an operator, you have to specify whether it's a prefix, an infix, or a postfix operator. Prefix and postfix operators both have a single operand; the operator is written before or after it, respectively. Infix operators have two operands and are denoted in between those.

Implementing the Forward Pipe Operator in Swift

Since we want to apply the |> operator (which doesn't exist in Swift natively) to two operands, we'll be defining an infix operator. We do this by writing the following operator declaration:

infix operator |> { associativity left precedence 80 }

The associativity left keywords indicate that we want the operator to implicitly group values on its left side. This allows us to chain multiple calls to |> without ambiguity. The following two lines are therefore equivalent:

let transformed1 = value |> increment |> square
let transformed2 = ((value |> increment) |> square)

Note that the order is important: Generally speaking, squaring an incremented value is not the same as incrementing a squared value.

We also specify a very low precedence level of 80 so that other operators will be applied first, preceding our passing the result through the function chain. For a complete reference table, please refer to the Binary Expressions section in the language reference.

After we've declared the operator, we have to provide a function implementing its functionality. This one is simple:

func |> <T, U>(value: T, function: (T -> U)) -> U {
    return function(value)
}

The snippet above defines a function named |> with two generic type paramters T and U and two arguments. T is the type of the incoming value being passed to function, which accepts one parameter of type T and returns a value of type U. The return type of the entire operator function is U because that's the type of the value returned by function.

That's actually all it takes to implement the forward pipe operator in Swift. We can now call it the same way and regain the naturally readable function ordering from left to right:

let value = 5
let transformed = value |> increment |> square

If the function chain is long, spanning the calls across multiple lines of code serves readability well:

let heavilyTransformed = value
    |> increment
    |> square
    |> increment
    |> square

The transformation process can now be read from top to bottom.

Some Closing Notes

Being able to define custom operators in Swift opens up a tremendous amount of possibilities to extend the language. However, be reasonable and don't overdo it just because you can. But if an operator is easily understood and helps clarify your intent while at the same time simplifying your code, then go for it!

Learn Node