Skip to content

Instantly share code, notes, and snippets.

@GarrisonJ
Last active September 16, 2019 21:35
Show Gist options
  • Save GarrisonJ/c249d940128d8580acd3b2a858ddf897 to your computer and use it in GitHub Desktop.
Save GarrisonJ/c249d940128d8580acd3b2a858ddf897 to your computer and use it in GitHub Desktop.

ROP Guide (Draft V1)

The original Railway Oriented Programming (ROP) blogpost can be found here: https://fsharpforfunandprofit.com/rop/

Summary

  • ROP abstracts away error handling so we can pull it out of business logic
  • ROP helps us remember to handle errors by failing to compile when we forget
  • If you have a method than can fail, have it return a Result<T> instead
    • When your method succeeds return Success<T>
    • When your method fails, instead of throwing an exception or returning null, return Failure<T> instead
  • ROP Logic methods
    • Use Then whenever you have a method that returns a Result<T>
    • Use Map when you have a method that doesn't return a Result<T>
    • Use OnSuccess when you don't care what's returned
  • ROP Error methods
    • Use Verify when you want to to return an error when some predicate returns false
    • Use VerifyNotNull when you want to to return an error when some data is null
    • Use MapFailure when you want to change what kind of error a method is returning
  • Other ROP methods
    • Use OnFailure when you want to run some code only if a failure happend
    • Use Always when you want to run some code regardless if your code has failed or not
    • Use MapTo when you are done with ROP and you want the final result of your computation

Why use ROP

ROP abstracts away error handling so we can pull it out of business logic.

Say we want to get the first element out of a list. We have to handle the scenario of of an empty list. We usually choose between throwing an exception or returning null. That's the design Linq took for First() and FirstOrDefault(). This isn't ideal because we'll have to remember to check for null or put our code in a try-catch block and if we forget our program could crash!

If we use ROP, we can write a method that returns a Result<T>. Result<T> is an abstract class with two subclasses, Failure<T> and Success<T>. Let's call that method FirstOrFailure() and see how it compares to Linq's methods:

try {
    item = data.First();
} catch {
    /* Handle error */
}
/* Process item */
item = data.FirstOrDefault();
if( item == null ) {
    /* Handle error */
};
/* Process item */
data.FirstOrFailure()
    .Then( item => /* Process item */ ); 

Then is a method that takes the data out of the Result class and lets us operate on it. If an error has happened, it doesn't do the operation. We'll learn more about how to use it later.

In the FirstOrFailure() example, the error handling section of the code is gone!

Another benefit of ROP is, to a certain degree, we are forced to handle the error case with FirstOrFailure().

Imagine that you have a list of Ints and you want to get the first element, add 1 to it, and then send it to the function SendNumber() but you forget to check if the list is empty:

// Won't compile
item = bunchOfNumbers.FirstOrFailure()
var plusOne = item + 1; // Oops, we forgot to check if bunchOfNumbers was empty!
SendNumber( plusOne )

This won't compile because item is a Result<Int> and you can't add 1 to it. You must write the logic in a way that unwraps the Int. This (almost) forces you to consider the case where Result<Int> is a Failure<Int>.

bunchOfNumbers.FirstOrFailure()
.Map( item => item + 1 ) // The error case is handled for us
.Then( plusOne => sendNumber( plusOne ) );

We would have to go out of your way to write this method using FirstOrFailure() in a way that would crash on an empty list.

Map and Then are methods defined on the Result<T> class that help us tie actions together. We'll learn how they work next.

ROP Methods

Program logic

Then is the power tool of ROP. If we wanted to, we could write all program logic in ROP with Then statements. All other ROP logic methods can be considered helper functions. Then ties statements together.

In functional programming, Then is sometimes called Bind because it binds actions together, Result<T> is called a monadic value, and methods that return Result<T> are called monadic functions. ROP is a specific instance of the monad design pattern.

Use Then whenever you have a method that returns a Result<T>

Then handles methods that fail. Use it whenever possible.

.Then( x => DoSomethingSometimesFails( x ) )

Use Map when you have a method that doesn't return a Result<T>

What if you have a method that doesn't return a Result<T>? You could wrap the result of the method in a Result<T>, and then it would fit in a Then. A less verbose way to do the same thing is to use Map. Map is the same as Then except it wraps the result in a Result<T>.

.Map( x => DoSomething( x ) )

is the same thing as

.Then( x => DoSomething( x ).AsSuccessResult() )

Use OnSuccess when you don't care what's returned

If you have a method that returns a value that you don't care about use OnSuccess. OnSuccess will throw away whatever is returned, and pass what was passed in to the next action.

.OnSuccess( x => DoSomething( x ) )

is the same as

.Then( x => DoSomething( x ).Map( _ => x ) )

or, if we want to write OnSuccess only using Then (just to prove to ourselves that we can)

.Then( x => DoSomething( x ).Then( _ => x.AsSuccessResult() ) )

ROP Error Methods

Use Verify when you want to to return an error when some predicate returns false

Verify is a way to explicitly set your method to error when some expression is true.

 .Verify( number => number < 10, ErrorKey.NumberMustBeGreaterThanTen )

Use VerifyNotNull when you want to to return an error when some data is null

VerifyNotNull will create an ArgumentNull error when some variable is null. The second parameter saves the name of the thing that was null in the ErrorKey for easier debugging.

 .VerifyNotNull( payload, nameof( payload ) )

is an alias for

.Verify( _ => payload != null, ErrorKey.ArgumentNull( payload ) );

Use MapFailure when you want to change what kind of error a method is returning

TODO

Other ROP Methods

Use OnFailure when you want to run some code only if a failure happend

TODO

Use Always when you want to run some code regardless if your code has failed or not

TODO

Use MapTo when you are done with ROP and you want something even if your code failed

TODO

@boonew2
Copy link

boonew2 commented Jun 28, 2019

Unless it is outside the general scope i'd put in a short definition of what ROP actually is. I think i asked awhile ago and it is railway oriented programming, but that doesn't even show up on https://acronyms.thefreedictionary.com/ROP !

@GarrisonJ
Copy link
Author

Unless it is outside the general scope i'd put in a short definition of what ROP actually is. I think i asked awhile ago and it is railway oriented programming, but that doesn't even show up on https://acronyms.thefreedictionary.com/ROP !

Yeah, that makes sense. Maybe I should just post a link to the original (or what I think was the original) blog post about ROP.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment