Function Composition & Pipelining Operators in Swift

In functional programming, the composition of two functions function1 and function2 is another function that represents the application of function1 followed the application of function2. Pipelining enables function calls to be chained together as successive operations. The difference is that composition returns a function for future application whereas pipelining applies the function chain immediately. Here, we’re borrowing F# operator definitions for both composition and pipelining.

Composition

First, an example. Lets define three functions, plusOne, timesTwo and minusThree to do exactly what their names suggest. We then compose them in that order to create a composed function c1 and finally apply c1 on Int(10).
func plusOne (x:Int) -> Int { return x + 1 }
func timesTwo (x:Int) -> Int { return x * 2 }
func minusThree (x:Int) -> Int { return x - 3 }

var c1 : (Int) -> (Int) = plusOne >> timesTwo >> minusThree
println( c1(10) )  //Output: 19
In this example, we’ve using the >> operator (F#) to imply left to right composition. Composition in reverse (right to left) looks like this:
var c2 : (Int) -> (Int) = plusOne << timesTwo << minusThree
println( c2(10) ) //Output: 15

Composition Operators

The (overloaded) operator are defined like this:
operator infix >> { associativity left }
operator infix << { associativity right }

@infix func >> <T1, T2, T3> (left: (T1)->T2, right: (T2)->T3) -> (T1)->T3 {
  return { (t1: T1) -> T3 in return right(left(t1)) }
}

@infix func << <T1, T2, T3> (left: (T3)->T1, right: (T2)->T3) -> (T2)->T1 {
  return { (t2: T2) -> T1 in return left(right(t2)) }
}
We need to specify associativity to ensure sequence of application for our operators.

Pipelining

Here is an example of function pipelining.
println( 10 |> plusOne |> timesTwo |> minusThree )   //Output: 19
println( plusOne <| timesTwo <| minusThree <| 10 )   //Output: 15

Pipelining Operators

Again borrowing from F#, we define pipelining like this:
operator infix |> { associativity left }
operator infix <| { associativity right }

@infix func |><T,U> (left: T, right: (T)->U ) -> U {
  return right(left)
}

@infix func <| <T, U> (left: (T)->U, right: T ) -> U {
  return left(right)
}

Tuples

Lets throw in a couple of additional operators to be used with tuples:
operator infix ||> { associativity left }
operator infix |||> { associativity left }
operator infix <|| { associativity right }
operator infix <||| { associativity right }

@infix func ||><T1, T2, U> (left: (T1,T2), right: (T1, T2)->U ) -> U {
  return right(left)
}

@infix func |||><T1, T2, T3, U> (left: (T1,T2,T3), right: (T1, T2, T3)->U ) -> U {
  return right(left)
}

@infix func <|| <T1, T2, U> (left: (T1,T2) -> U, right: (T1,T2) ) -> U {
  return left(right)
}

@infix func <||| <T1, T2, T3, U> (left: (T1,T2,T3) -> U, right: (T1, T2, T3) ) -> U {
  return left(right)
}

And finally an example of tuples in pipelining:
func append(a: String, b: String) -> String { return "(a).(b)"}

println( ("x","y") ||> append )  //Output: x.y
In the next example, we inject a point (10, 10) in the pipeline which computes the length (assuming a (0,0) origin), multiplies the scalar by two and adds 1. Note that efficiency isn’t the overarching consideration of these example.
func length(x: Double, y: Double) -> Double { return sqrt(x * x + y * y) }

(10, 10) ||> length |> timesTwo |> plusOne |> println   //Output: 29.2842712474619
println <| plusOne <| timesTwo <| length <|| (10, 10)    //Output: 29.2842712474619
In the example above, println is included as part of the pipeline (see comment below).
  • Does pipelining work with the println function? E.g.,

    ("x", "y") |> append |> println
    
    • Venkat Peri

      Good point. I’ve updated the last example.

  • tnaber

    Can’t it be done without limiting the number of parameters?

    • Venkat Peri

      Yes, the double and triple operators merely provide readability. The basic opreators will support any tuple size, and the compiler will report an error if there’s a mismatch.

  • Sam Isaacson

    Thanks – nice article. F# chose their operators well!

    Why did you need the tuple specific operators ||> ?
    Would |> not have worked/generalized?

    • Venkat Peri

      Yes, the double and triple operators merely provide readability.