Single Forward Pipe Operator in Swift

Swift |> FunctionalProgramming

Subscribe to my newsletter and never miss my upcoming articles

Hello functional and object developers, Leo here.

Today we'll explore a operator typically embedded in functional programming . The pipe operator is useful when we have long chain of functions that the return value is used as input value of another function, in turn, the result value is the input for another function and so on.

The painting is from unknown artist from 19 century and it's St. Cecilia . The story tells that she is a Christian saint and she has lived in Rome in the early days of Christianity. She was eventually sentenced to martyrdom, having converted many people. A passage from the legend says that going to martyrdom she heard heavenly music. This story will make the patron saint of musicians, instrument makers and other musical instruments manufacturers. I choose her because she is playing and flute organ that has many pipes, got it?

Let's see how can we implement this operator in Swift!

You have a long chain of functions like: cToD(c: bToC(b: aToB(a: stringToA(text: "my text")))) and want it to be more readable.

The problem explored

Swift doesn't has yet the pipe operator. So we have to create by ourselves. Let's observe the example below and check why is the pipe operator important:

struct A {}
struct B {}
struct C {}
struct D {}

func stringToA(text: String) -> A { return A()}
func aToB(a :A) -> B { return B()}
func bToC(b :B) -> C { return C()}
func cToD(c: C) -> D { print("success") ; return D()}

let d = cToD(c: bToC(b: aToB(a: stringToA(text: "my text"))))

If you read from left to right, like most of the ocidental people, you understand the first problem: cToD is the first function that appear but is the last function that runs, it's not natural for us read this. The rational would be something among these lines:

  • Oh I have a cToD function here.
  • The input is a function called bToC, so it won't run until this bToC run completely....
  • Now the input of bToC function is another function called aToB, ok so we have to first run aToB then bToC then cToD...
  • My god, the input of aToB is ANOTHER function that will run before all the previous ones.
  • So to understand: it's written cToD -> bToC -> aToB -> stringToA but the execution order is the inverse: stringToA -> aToB -> bToC -> cToD

Wouldn't be great if we had a operator that could solve this mind tangling code? And that's where the pipe operator comes handy. The target of this post is transform all the spaghetti above into this clean as hotel sheets code:

let d = "my text"
        |> stringToA
        |> aToB
        |> bToC
        |> cToD

This is something that is very understandable. "My text" is going from String to A type, then A type to B, them B to C and finally c To D. And that's it.

Defining the Custom Operator

Forward Pipe operator is an infix operator that means it is used between two values, example: value1 + value2. Other operators types are:

  1. prefix operator that is used before a value (ex: !value),
  2. postfix operator used after a value (ex: value?).
  3. The ternary operator that are two symbols inserted between three values (ex: value1 ? value2 : value3), swift don't support as the time I'm writing this to create your own ternary operators.

Now back to the pipe operator we will implement it with the symbol |> . As this is a custom operator you can declare more or less the way you want just be careful to not overload some existing Swift operator. Our pipe operator will works from left to right i.e. this means that it takes value from the left side of the expression and passes it as argument input to the function on right side as shown below.

The first step to implement our custom operator is to declare it's precedence group. In this group we will tell the compiler: Hey man create a precedence group that has a left associativity, in other words saying that this operator will implicitly work with the values on the left side of the operation. This is important because the DefaultPrecedence precedence group category has no associativity so the compiler can't tell what to do with the values of both sides. To the declare the precedence group just do this:

precedencegroup SingleFowardPipe {
    associativity: left
    higherThan: BitwiseShiftPrecedence
}

I called it single forward pipe because you can have de double forward pipe as : |>> or how many forwards you want, but generally it would go only to the triple forward pipe like: |>>> . And as you can imagine the double forward pipe just use the left side of the operation into the second parameter of the function on the right side and the triple forward pipe use the left parameter on the third parameter of the right side function.

The precedence group have other keywords like: higherThan/lowerThan to express it's priority among other operator and assignment that means you can use assignment when chaining within your operator but those we will deep dive on another day.

Now lets declare the operator itself:

infix operator |> : SingleFowardPipe

Now we have to declare how the operator will work. Let's think about it: we want that a value in the right side would be proxied to a func of the left side. So the input of the func would be a value and a closure, and the return value should be the same as the closure return. We can generalize doing this:

func |> <V,R>(value:V,function:((V)->R)) -> R {
    function(value)
}

Explaining because a lot is going on there:

  1. declare a func with the name: |> , this is only possible because we have declared the infix operator before.
  2. Use two generic parameters: V and R. I use V for value and R for return to make simple to understand.
  3. The func parameters are: the value of the left represented by V type and a closure that the input parameter is the V type and the return is R type.
  4. The return type of the whole function will be the same as the return type of the closure in the parameters, in this case R type.

And that's all you need!

Now you can put all this together and test it on your Xcode's playground:

precedencegroup SingleFowardPipe {
    associativity: left
    higherThan: BitwiseShiftPrecedence
}

infix operator |> : SingleFowardPipe

func |> <V,R>(value:V,function:((V)->R)) -> R {
    function(value)
}

func maxList(_ list: [Int]) -> Int {
    list.max() ?? 1
}

[1,3,4] |> maxList

struct A {}
struct B {}
struct C {}
struct D {}

func stringToA(text: String) -> A { return A()}
func aToB(a :A) -> B { return B()}
func bToC(b :B) -> C { return C()}
func cToD(c: C) -> D { print("success") ; return D()}

let d = cToD(c: bToC(b: aToB(a: stringToA(text: "my text"))))

let d2 = "my  text"
        |> stringToA
        |> aToB
        |> bToC
        |> cToD

Very clean isn't? You should see in your console those messages in your console:

Screen Shot 2021-04-15 at 08.50.05.png

Conclusion

The pipe operator can be very useful when you dealing with long chains of functions that the return type of one is the input type of another.

That's all my people, I hope you liked this as I liked writing. If you want to support this blog you can Buy Me a Coffee or just leave a comment saying hello.

Thanks for the reading and... That's all folks.

credit: image

Comments (1)

Liana's photo

You are unlikely to come across a fake profile when using EliteSingles. In fact, you can view the entire list of people you have ever contacted on the site and elitesingles review you are unlikely to find a fake account! Why is this? Well, while the site doesn't have any of the specific security features they advertise, they do have one thing that is sure to scare off most scams: massive paywalls.