Hello functional and object developers, Leo here. The topic today will discuss how to make a Single Forward Pipe Operator in Swift.
We’ll explore an operator typically embedded in functional programming. The pipe operator is useful when we have a long chain of functions that the return value is used as the input value of another function, in turn, the result value is the input for another function, and so on.
The painting is from an unknown artist from 19 century and it’s St. Cecilia. The story tells that she is a Christian saint and she 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 instrument manufacturers. I choose her because she is playing and flute organ that has many pipes, got it?
Let’s see how we can implement this operator in Swift!
The problem – Single Forward Pipe 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.
Swift doesn’t have yet the pipe operator, this way 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 occidental people, you understand the first problem: cToD is the first function that appears but is the last function that runs, it’s not natural for us to read this. The rationale 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 an operator that could solve this mind-tangling code? And that’s where the pipe operator comes in handy.
The target of this post is to transform all the spaghetti above into this clean 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, then B to C, and finally c To D. And that’s it.
Creating the Custom Operator
Forward Pipe operator is an infix operator that means it is used *between* two values, for example: value1 + value2.
Other operators types are:
- prefix operator that is used before a value (ex: !value),
- postfix operator used after a value (ex: value?).
- 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 it more or less the way you want just be careful to not overload some existing Swift operator. Our pipe operator will work 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 the right side as shown below.
The first step to implementing our custom operator is to declare its precedence group. In this group we will tell the compiler: Hey man creates 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 declare the precedence group just do this:
precedencegroup SingleFowardPipe { associativity: left higherThan: BitwiseShiftPrecedence }
The precedence group has other keywords like *higherThan/lowerThan* to express its priority among other operators and *assignment* which means you can use assignment when chaining within your operator but those we will deep dive on another day.
Other Pipe Operators
It is called a 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 uses the left parameter on the third parameter of the right side function.
Declaring Custom Single Forward Pipe Operator in Swift
Now let’s 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 on the right side would be proxied to a function on 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:
- Declare a func with the name: |>, this is only possible because we have declared the infix operator before.
- Use two generic parameters: V and R. I use V for value and R for return to make it simple to understand.
- The func parameters are: the value of the left represented by V type and closure that the input parameter is the V type and the return is R type.
- 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!
Full Example Code
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 it? You should see in your console those messages in your console:
Summary – Single Forward Pipe Operator in Swift
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 reading this article as much as I enjoyed writing it. If you want to support this blog you can Buy Me a Coffee or leave a comment saying hello. You can also sponsor posts and I’m open to freelance writing!
You can reach me on LinkedIn or Twitter and send me an e-mail through the contact page.
Thanks for reading and… That’s all folks.
Credits: image