Forward Application

3 minute read

Previously we built up compositions of functions. These created piplines to pass data through. The data though is always an after thought though. Semantically we describe an action or series of actions then we say what those actions are going to apply to.

Using the previous example we had a list of moves

 let moves = ["jumpkick", "roundhouse", "uppercut"]

We want to loudly declare a move, in this case the last one:

 let loudLastUpper = reverse 
                        >>> head 
                        >>> toUpperCase 
                        >>> exclaim
loudLastUpper(moves) //"UPPERCUT!"

This is a familiar paradigm: action => data. Action or function is applied to some data. Programming 101 right? But what if we thought about our data first? Could we apply our moves to the loudLastUpper action? We could define an apply function:

func apply<A, B>(_ x: A, to f: (A) -> B) -> B {
    return f(x)
}

Now let’s apply it to the previous example:

apply(moves, to: loudLastUpper)  //"UPPERCUT!"

This is different, but still understandable right? Apply these moves to the loudLastUpper action.

We could inline the moves list and it still reads well:

apply(["jumpkick", "roundhouse", "uppercut"],
      to: loudLastUpper)

This is almost what we are after. What if we infixed this apply function so we had somthing like moves applyTo loudLastUpper. This seems similar to what unix does with the pipe (|) operator:

// find all the `swift` files on a computer:
locate "*.swift" | grep swift

Unix pipes or applies the results of locate to grep two totally seperate commands are chained or composed together!

How about a similar oppertor for swift, we can borrow from other and use the |> operator used in F# and Haskel:

precedencegroup ForwardApplication {
    associativity: left
}

infix operator |>: ForwardApplication

public func |> <A, B>(a: A, f: (A) -> B) -> B {
    return f(a)
}

Same goodness of apply(to:) but now are an use it infix!

moves |> loudLastUpper //"UPPERCUT!"

While this is a new operator to get used to, it is easy to undersand even thought knowing F# or Haskel, we are just piping data through!

We might not even need our loudLastUpper function any more, if it’s only used here it seems a waste. Could we just inline that:

moves |> reverse
        >>> head
        >>> toUpperCase
        >>> exclaim

Out of the box this isn’t going to work. We have conflicting precedence groups. We need to define an order of operations (just like math!). Here we have both ForwardComposition and ForwardApplication with left associativity. We could use our friend parenthesis to group the composition first:

moves |> (reverse
            >>> head
            >>> toUpperCase
            >>> exclaim)

This works, but adds noise and cognative overhead to our smoothly read composition. In some situations we may want that, but in others we might want a rule. If we are doing forward application and composition in the same place it might make sense to compose first as a rule. We can change ourForwardComposition precedence group to be placed higher than our ForwardApplication:

precedencegroup ForwardComposition {
    associativity: left
    higherThan: ForwardApplication
}

Now this works with no complaints:

moves |> reverse
        >>> head
        >>> toUpperCase
        >>> exclaim

Now you might be wondinging how different this apply (|>) function is from compose (>>>)? On the surface they are very similar, apply puts a function’s argument in front where as compose calles one function with the result of another:

public func |> <A, B>(a: A, f: (A) -> B) -> B {
    return f(a)
}

Vs:

func >>> <A, B, C>(_ f: @escaping (A) -> B, _ g: @escaping (B) -> C) -> (A) -> C {
    return { x in g(f(x)) }
}

In some cases (like our example) you could easily replace one with the other:

["jumpkick", "roundhouse", "uppercut"]
    |> reverse
    |> head
    |> toUpperCase
    |> exclaim
// "UPPERCUT!"

// same result as:

["jumpkick", "roundhouse", "uppercut"]
    |> reverse
    >>> head
    >>> toUpperCase
    >>> exclaim    
// "UPPERCUT!"

In this use case I personally find the piped version easier to read than the composed. For inline logic or one offs this is great, easy to visualize. This doesn’t mean compose doesn’t have its uses too. Being able to form new functions, save them, pass them around is an incredibly powerful thing.

Working code can be found in the Chapter 5 page of the Mostly Adequate Swift Playground