Railway Oriented Programming in Swift
In this article we will talk about dealing with two tracks of function execution: happy path and error path.
Almost every programming task consists of two user’s tracks or paths. When everything goes fine we call it a “happy path”. But every time something happens, especially in mobile apps. Validation error, the Internet goes away, system kills our app, hardware reports an error, server is unavailable and so on. And usually error path requires much bigger effort. We should handle somehow all the expected and unexpected errors. Railway Oriented Programming (ROP) is to help us with this routine.
Original inventor of this term is Scott Wlaschin. He is creator of https://fsharpforfunandprofit.com/ and author of fantastic book “Domain Modeling Made Functional” (highly recommended for everyone). After he did few talks about ROP in context of F# language many communities started to adopting this approach for all the other languages (and not just functional). And now I want to show how we can do it in Swift.
Let’s start with the problem. Assume we have a task to add basic user’s registration feature. User must provide email and password, they must be valid, and there shouldn’t be any email duplicates in the system.
Ok, let’s modeling our registration feature.
We create simple structures for the input data and for the user. CustomDebugStringConvertible protocol will help with printing out, and UserError enum describes all expected and unexpected errors during registration process. It’s pretty simple and clear, let’s move on.
After validation we want to save our user to the database or post it to the web-service. Again, for simplicity I decided to use a function which emulates saving to the database.
In saveToDb(_:) function we try to create and put new user to the store, and throwing an error in case of duplicate found.
Ok, preparation is done. Now let’s do our first attempt to write register function.
First of all let’s look at the signature (UserInput) throws -> User. We want to receive an input and return a new User. But we also want to return all the possible errors somehow. One way to do it is throwing errors. But usually throwing an error means that something unexpected just happened. Do we expect that email might be invalid? Yes. Do we expect that password might be invalid? Yes. Do we expected that there might be already exists user with the same email. Yes. So why don’t just return a user and one of expected errors together? And people call such kind of types a tuple. Likely Swift supports tuples.
New signature is (UserInput) -> (User?, UserError?). That’s better, but now we must use Optionals for User and UserError. That’s because we can return a User OR an Error, but not both of them. Who familiar with functional programming can say: “We need a sum-type here!”. And they will be right. And we don’t have to invent something new, there is already exists good abstraction for that purpose. It’s called Result.
Simple and yet powerful Result type. It has just two possible states: .ok with object of type T (success path), .error with object of type E (error path). Cool, now refactor our function.
Oh, that’s much better. We return exactly what we need, without throwing any errors. And we can use this function like this.
And here we see nice side-effect of Result’s usage. It enforce to handle both cases on the caller side. It means that all the errors must be processed somehow.
Our register(input:) looks good, but not perfect. What’s the problem? It does so many things. It validates email, validates password and saves user to the database. Let’s extract all different logical parts to separate functions, easy.
Oh, looks ugly, right? What’s the problem? Some people call it if-else-nesting-hell. That’s because we replace or guard statements with switch. Yes, that’s price of type-safe error handling. Well, we can actually change signature of validation functions to (String)->Bool and return guard statements. Using this approach we are losing consistency. Now let’s talk about how to get rid of this ugly nesting.
First, we need to think what exactly we want to achieve. We have sequential functions calls. Every call returns two-track Result, and we want to go further only by “happy path”, and in case of error just stop execution and return an error. Ideally, we would like to write something like this.
It can’t be compiled, but we want something powerful and beautiful. And that kind of functions composition is called Railway Oriented Programming.
Picture above illustrates desired behavior. Green track — happy path, red — error path. But how exactly can we implement such functions composition?
Chained functions composition (or chained calculations) is coupled with Monads. Right, now we are moving to the Monads world. We can think about a Monad as about special container for some type with ability to create chained calculations. And that ability is done via so-called bind function. If we just add that function to the Result type we will get a Monad.
What exactly it does? If current Resul’s case is .ok then we call function, which is mapping unwrapped value of type T to the new Result. And if current Result’s case is .error we just pass it through. That’s exactly what we need. Let’s rewrite our functions using monadic composition.
That’s better, but still we have some ugly code inside last bind closure. To make it more readable we can just move it to separate function.
Perfect! Now it’s crystal clear what we are doing in that function: validate email first, then validate password, and the last save user to database. And if any of those functions return error, then execution is stopped and Result with error is returned.
Even though the latest refactored function is good enough, I would like to propose few more techniques.
In functional world many weird operators are used. But some of them might be very handy. For example we can use special operator for composing monadic functions. It is called Kleisli composition, or Kleisli arrow. Let’s define custom operators and see how it looks like.
And now we read our functions composition even more distinctively (if one knows what that ‘fish’-operator means :D ). That’s equivalent to previous version with bind, but without parentheses.
Also in F# and many other functional languages we have neat pipe-forward operator |>. It help us to write code that could be read like a book, from left to right. Let’s define that operator and refactor our function a little bit more.
First, let’s look at operator’s definition. Pipe-forward operator is as simple as calling function with an argument, but instead of f(x) we can do x |> f, which is the same but is read from left to right. Also we need to define correct precedence for the custom operators. Passing argument via pipe-forward operator should be done after Kleisli composition, that’s why we define Kleisli operator precedence higherThan: PipeOperatorPrecedence. And register(input:) function is now simple readable composition of an argument and three different functions. Sweet!
Such approach would be very helpful for domain modeling. It help us to strictly define all possible results of a function call (including errors). And neat composition help us to write easy readable code with less boilerplate. But beware of custom operators! They are as useful as dangerous! Every custom operator should be discussed with all the team members.
Complete gist can be found here.
Happy coding!