Func-y Swift Infix Operators

If you've ever so much as glanced at F# code (or even Elm or Elixir) you've come across the forward pipe operator |>, and likely some form of composition operator like >>. These operators are common in functional languages, but are absent in our favorite object-oriented languages like Java and C#. Since many of the newer languages are featuring functional features (like first class functions) I wanted to investigate whether any of them had anything like the pipe or composition operators, and if not, if we could create them. As fate would have it, Swift, the language that just won't leave me alone, gives us this functionality!

Forward Pipe

The operator I was most interested in was the forward pipe operator |>. It's a simple operator that helps with the readability of a series of function calls. All the forward pipe is going to do is invert a series of function calls by piping the result of one function into the next:

let myNewVal = EtoF(DtoE(CtoD(BtoC(AtoB(A)))))

// can be written as

let myNewVal = 
    A
    |> AtoB
    |> BtoC
    |> CtoD
    |> DtoE
    |> EtoF

To create a new infix operator in Swift, we use the following structure: <operator-type> <operator-symbol> : <precedence-group>. We know what kind of operator type we want, infix. We know the symbol we want to use, |>. But what is our precedence group? We can find the precedence groups in the Swift developer docs for operator declarations. According to the docs, the default precedence group is just above the TernaryPrecedence group which seems like a logical place to put our new operator. It's below the precedence of the arithmetic operators (+, -, *, etc) but above the assignments (=, +=, -=, etc), so something like let n = 5 + 10 |> times10, where times10 is a function that multiplies an Int by 10, will give us a result of 150 instead of 105, or some horrible error. The result also mimics what we'd expect from the regular function call let n = times10(5 + 10).

There's one problem with the DefaultPrecedence, it has no associativity! Why is associativity important? Because in a chain of forward pipes, we want operations on the left performed before operations on the right. If we have no associativity, the compiler won't know how to evaluate something like let n = A |> AtoB |> BtoC because it won't know which operation to perform first. For our forward pipe, we will want left associativity instead of right associativity. For example, we want let n = A |> AtoB |> BtoC to evaluate like this: let n = (A |> AtoB) |> BtoC, instead of this: let n = A |> (AtoB |> BtoC).

Now that we have an idea of what we need for our operator, lets create our precedence group, operator, and its implementation:

precedencegroup ForwardPipe {
    associativity: left
    higherThan: TernaryPrecedence
    lowerThan: LogicalDisjunctionPrecedence
}

infix operator |> : ForwardPipe

public func |> <T, U>(data: T, fn: (T) -> U) -> U {
    return fn(data)
}

Our implementation is just like a stand-alone function. We use generics so that we can use it with any types we want. For those unfamiliar with generics, T and U are like placeholders for types. In our generic function, types that replace T need to match, and types that replace U need to match.

Our forward pipe takes a T and pipes it into a function that takes a T and returns a U. The return type of the forward pipe implementation is the return type of the function we pass to it, U. The signature of our function parameter is (T) -> U instead of (T) -> T because we want the ability to pipe values of one type into a function that returns values of different type. That doesn't mean we can't use a function with a signature like (Int) -> Int it just means we aren't limited to that. Lets try it out:

func add5(_ x: Int) -> Int { return x + 5 }

let n = 10 |> add5

print(n) // 15

// can be used with anonymous functions as well
let val = 3 |> { value in "You passed in a \(value)" }

print(val) // You passed in a 3

We've created a function, add5, that takes an Int and returns an Int. Then we pipe 10 into it using our beautiful new forward pipe, giving us a result of 15. We can use the pipe operator with anonymous functions as well.

Composition

Function composition is taking two functions and turning them into one. We'll use the same process to create a function composition operator that we used to create our pipe. First, precedence. We'll use the same associativity and make our precedence slightly higher than forward pipe. That way we can pipe into a composed function.

precedencegroup Composition {
    associativity: left
    higherThan: ForwardPipe
    lowerThan: LogicalDisjunctionPrecedence
}

//Single Composition sometimes seen as >>
infix operator >-> : Composition

public func >-> <T, U, V>(fn1: @escaping (T) -> U, fn2: @escaping (U) -> V) -> (T) -> V {
    return { data -> V in fn2(fn1(data)) }
}

In F#, function composition uses the >> operator. Swift uses this operator for bitwise left shift's so I decided to use a different operator. I settled on >-> because it's similar to >> but adds a single - to indicate that it composes two functions with a single input/output track. All we are doing here is taking two functions and creating a single function from them. A (T) -> U and (U) -> V turns into a (T) -> V. We need the @escaping in Swift because the functions are going to be called after the return of our function.

Lets take it for a spin:

let add15 = add5 >-> add10

print(add15(5)) // 20

We composed an add5 function with an add10 function to create a new add15 function. Pretty cool. Instead of writing three different functions, we compose two functions we already had to get the third.

Two-Track Functions

Scott Wlaschin has incredible blog posts and talks where he discusses Railway Oriented Programming. They were the best resources I've found on the information that follows. To sum it up, pipe and compose are great for functions that have a single input and single output, but not all functions are so clean. Some functions return a value that could be one type or a different type. Swift's Optional is a great example of this. An Optional can either be some type T, or None. We can use |> and >-> with optionals, but how many functions take an Optional as input? I can't think of a single one.

For one of these "two-track" types like Optional, we need a way to pipe the "Success" option to another function without ignoring a potential "Error". To do this we'll create two new operators. The pipe version is often called "bind", and the composition version is often called "kleisli".

Let's create our own "two-track" type to test this out. A common type in F# and Rust that is absent (for now) in Swift is a Result type. It looks something like this:

public enum Result<T, U> {
    case Ok(T)
    case Err(U)
}

Similar to Optional, it has a success track, Ok(T), and and error track, Err(U). The difference being that we can return any type as the Error rather than just None or Nil.

Bind

Bind is like our |> but it pipes a "two-track" type like our Result into a function that accepts a T and returns a two-track type. Since it has similar functionality to |> we'll use the same precedence group for it.

// Bind, often seen as >>=
infix operator ||> : ForwardPipe

public func ||> <T, E, U>(result: Result<T, E>, fn: (T) -> Result<U, E>) -> Result<U, E> {
    switch result {
        case .Ok(let data):
            return fn(data)
        case .Err(let e):
            return .Err(e)
    }
}

If you look closely, you'll see the it's the same implementation as |> with a few more generic types thrown in. The main difference being that we pattern match on the Result we're piping into the function, and if it is an Err, we pass along the error value instead of processing anything with the function. If the original Result is an Ok, we pipe the T into the function to be processed.

>>= is already used in Swift for the bitwise left-shift assign operator so I used a different operator to make it distinctive. I opted for the double bar pipe since we are dealing with two-track types with bind. It seemed fitting. |> for one track piping, ||> for two track piping.

To test ||>, we'll use a divide function that throws an error if we try to divide by 0 and a function that multiplies even numbers by 100 and throws an error if given an odd number. Contrived, I know, but bear with me:

func div(_ x: Int, _ y: Int) -> Result<Int, String> {
    return y != 0
        ? .Ok(x / y)
        : .Err("Can't divide by 0!!!")
}

func multiplyEvensBy100(_ x: Int) -> Result<Int, String> {
    return x % 2 == 0
        ? .Ok(x * 100)
        : .Err("\(x) is not an even integer!")
}

Aside: in order to print the result of a function that returns a Result, we need to unwrap it somehow. Here's a simple function that can do just that:

func printResult<T, U>(_ res: Result<T, U>) -> () {
    switch res {
        case .Ok(let value): print(value)
        case .Err(let errMessage): print(errMessage)
    }
}

We'll use the div function to create our first result, and then pipe its result into multiplyEvensBy100:

// First test:
let oddResult = div(10, 2)
print(oddResult)    // Ok(5)
printResult(oddResult)    // 5

let oddResultX100 = oddResult ||> multiplyEvensBy100
print(oddResultX100)    // Err("5 is not an even integer!")

// Second test:
let evenResult = div(20, 2)
print(evenResult)    // Ok(10)

let evenResultX100 = evenResult ||> multiplyEvensBy100
print(evenResultX100)    // Ok(1000)

// Third test:
let divideByZero = div(20, 0)
print(divideByZero)    // Err("divide by 0 error!!!")

let divideByZeroX100 = divideByZero ||> multiplyEvensBy100
print(divideByZeroX100)    // Err("divide by 0 error!!!")

let divideByZeroX100 = div(20, 0) ||> multiplyEvensBy100    // Err("divide by 0 error!!!")

Our first test shows that an Ok result pipes the wrapped value into the next function, but since it isn't even, the second function returns an error. In our second test, we get an Ok from our first function, and this time we get a get a second successful result. Notice how we don't have an Ok(Ok(1000)) because the success value is unwrapped for the second function. In our third test the initial function call fails and the error is preserved through the second function call.

Kleisli

We need a way to compose two-track functions, and the Kleisli operator will do just that. I like to think of it as the rocketship operator, but I'm not sure how much mileage you'll get out of that. Kleisli composition takes one function that returns a two-track result and another function that returns a two-track result and composes them. We'll need to preserve errors generated by the first function, but make sure successes pass the unwrapped value onto the next function. It sounds complicated, but we can simplify it by implementing it with our bind operator! We'll use the same precedence group as or one-track composition operator:

infix operator >=> : Composition

// Kleisli
public func >=> <T, E, U, V>(fn1: @escaping (T) -> Result<U, E>, fn2: @escaping (U) -> Result<V, E>) -> (T) -> Result<V, E> {
    return { data in
        fn1(data) ||> fn2
    }
}

We need to use @escaping again because the functions won't be called until after the function returns. We're able to conveniently use our ||> bind operator to pipe the result of the first function call into the second.

Let's use the same div and multiplyEvensBy100 functions to test it out:

let divideAndMultiplyEvensBy100 = div >=> multiplyEvensBy100

// tuples
let t1 = (200, 0)
let t2 = (200, 2)
let t3 = (200, 8)

let t1_result = divideAndMultiplyEvensBy100(t1)
let t2_result = divideAndMultiplyEvensBy100(t2)
let t3_result = divideAndMultiplyEvensBy100(t3)

print(t1_result) // Err("divide by 0 error!!!")
print(t2_result) // Ok(10000)
print(t3_result) // Err("25 is not an even integer!")

As with bind, we're able to preserve errors so that we can identify where the failure occurred, and successful results get unwrapped and passed through.

Limitations

You may have already noticed, but there are some limitations to using these operators. First, they only work when piping a single value into a function. We can use tuples to pass in multiple pieces of data as a single value as we did in the example above, but that doesn't compose very well. In F# we get around this because F# functions are automatically curried.

Something like

let add x y =
    x + y

Works more like this under the hood:

let add x =
    fun y -> 
        x + y

We can do something like this in Swift, but we have to do it manually for each function. Here's an example of turning our div function into a curried version.

func div(_ y: Int) -> (Int) -> Result<Int, String> {
    return { x in
        return y != 0
            ? .Ok(x / y)
            : .Err("divide by 0 error!!!")
    }
}

Previously we could only pipe into div if we piped a tuple into it. Now we can pipe into div by supplying just the divisor:

let result = div(2)(1000)

print(result) // Ok(500)

let result2 = 100 * 10 |> div(2) ||> multiplyEvensBy100

print(result2) // Ok(50000)

let result3 = 100 * 10 |> div(0) ||> multiplyEvensBy100

print(result3) // Err("divide by 0 error!!!")

As fun as this exercise was, I'm not sure it's the best way to be writing Swift (not that I'm an expert). If you love it, by all means do it. I'm sure I will. But Swift already seems to use a common API for these sorts of things by using the Object Oriented style and using methods like flatMap to handle two-track types. It may be more prudent to be consistent with the APIs of other types that already exist.

With that said, I think it would be really fun and interesting to see how we could use these for things like mathematical notation and operators. If I ever get around learning linear algebra I'll try it out and report back!

Reference: F# Symbol and Operator Reference, Swift Operator Declarations, F# For Fun and Profit: Railway oriented programming, F# For Fun and Profit: Functional Composition and Pipeline, Railway Oriented Programming, Swift Optional