We're sorry but this page doesn't work properly without JavaScript enabled. Please enable it to continue.
Feedback

Railway Oriented Programming

00:00

Formal Metadata

Title
Railway Oriented Programming
Subtitle
A functional approach to error handling
Alternative Title
Railway Oriented Programming — error handling in functional languages
Title of Series
Number of Parts
170
Author
License
CC Attribution - NonCommercial - ShareAlike 3.0 Unported:
You are free to use, adapt and copy, distribute and transmit the work or content in adapted or unchanged form for any legal and non-commercial purpose as long as the work is attributed to the author in the manner specified by the author or licensor and the work or content is shared also in adapted form only under the conditions of this
Identifiers
Publisher
Release Date
Language

Content Metadata

Subject Area
Genre
Abstract
When you build real world applications, you are not always on the "happy path". You must deal with validation, logging, network and service errors, and other annoyances. How do you manage all this within a functional paradigm, when you can't use exceptions, or do early returns, and when you have no stateful data? This talk will demonstrate a common approach to this challenge, using a fun and easy-to-understand "railway oriented programming" analogy. You'll come away with insight into a powerful technique that handles errors in an elegant way using a simple, self-documenting design.
3
Thumbnail
59:06
71
112
127
130
Computer programmingFunctional programmingEmailAddress spaceString (computer science)Formal verificationCodeImperative programmingEmailCASE <Informatik>CodeException handlingBitMultiplication signEndliche ModelltheorieLine (geometry)Validity (statistics)ArmIdentity managementCodeRight angleServer (computing)DatabaseDirected graphAddress spaceComputer programmingData storage devicePoint (geometry)Functional programmingRevision controlTwitterDifferent (Kate Ryan album)Equivalence relationProcess (computing)NeuroinformatikComa BerenicesWebsiteFunction (mathematics)Dependent and independent variablesService (economics)Goodness of fitValue-added networkInformation technology consultingFormal languageSoftware developerImplementationImperative programmingoutputSpacetimeInstance (computer science)Row (database)Message passingConnected spaceWindowMathematicsQuicksortAuthorizationStreaming mediaSurfaceError messageWeb serviceRun time (program lifecycle phase)Visualization (computer graphics)Computer programmingProper mapProgrammer (hardware)Differential (mechanical device)Block (periodic table)Formal verificationComputer fileKeyboard shortcutResultantComputer animation
Dependent and independent variablesFunctional programmingFunction (mathematics)String (computer science)Personal digital assistantSeries (mathematics)ChainMotion captureMonad (category theory)Abelian categoryHomomorphismusCategory of beingFunktorMessage passingTrailAnalogyTransformation (genetics)BitoutputCategory of beingAxiom of choiceDependent and independent variablesFunctional programmingInheritance (object-oriented programming)Function (mathematics)Monad (category theory)MereologyFunktorCASE <Informatik>DatabaseCodeProduct (business)Validity (statistics)QuicksortData conversionDifferent (Kate Ryan album)ExplosionMathematicsSummierbarkeitAnalogySingle-precision floating-point formatEndliche ModelltheorieSystem callFocus (optics)ResultantBlack boxTransformation (genetics)Exception handlingInformationType theoryPower (physics)Greatest elementString (computer science)Point (geometry)Formal languageHomomorphismusSeries (mathematics)Set (mathematics)MappingMonoidMultiplication signSocial classTerm (mathematics)DataflowLogic programming2 (number)Branch (computer science)Right angleOffice suiteAutomatic differentiationDisk read-and-write headParsingDecision tree learningGoodness of fitVideo gameComputer programmingTrailAuditory maskingComputer animation
Function (mathematics)outputEmailTrailData modelBasis <Mathematik>Video trackingBlock (periodic table)LengthSymbol tableTransformation (genetics)Shape (magazine)CodeView (database)Functional programmingKeyboard shortcutValidity (statistics)Right angleBitDatabaseoutputWind tunnelProcess (computing)EmailQuicksortType theoryMereologyShape (magazine)Function (mathematics)2 (number)CASE <Informatik>Generic programmingSingle-precision floating-point formatTrailFitness functionAdaptive behaviorEndliche ModelltheorieParameter (computer programming)Transformation (genetics)ResultantBoolean functionWordElectronic signaturePoint (geometry)Structured programmingSystem programmingDifferent (Kate Ryan album)Green's functionArrow of timeEquals signBranch (computer science)Heegaard splittingSymbol tableLine (geometry)Real numberRevision controlFormal languageService (economics)Matching (graph theory)MathematicsTypprüfungCarry (arithmetic)FamilyData storage deviceOffice suiteStress (mechanics)Multiplication signSource codeComputer clusterInterior (topology)Computer configurationComputer animation
Transformation (genetics)Shape (magazine)String (computer science)Function (mathematics)Continuous trackSoftware frameworkCurve fittingTrailSingle-precision floating-point formatException handlingEmailoutputData conversionControl flowWeb browseroutputFunctional programmingEndliche ModelltheorieEvent horizonValidity (statistics)Block (periodic table)Variety (linguistics)Fitness functionWeightStreaming mediaException handlingType theorySemiconductor memoryTrailElectronic mailing listEmailCASE <Informatik>Web browserFunction (mathematics)String (computer science)Multiplication signCodeBitServer (computing)DatabaseBranch (computer science)Parameter (computer programming)Web 2.0Computer fileElectronic signatureMappingForm (programming)Keyboard shortcutTerm (mathematics)Level (video gaming)Wave packetShape (magazine)Boolean functionSlide ruleWritingLine (geometry)Right angleVideo gameWordInheritance (object-oriented programming)QuicksortExecution unitSingle-precision floating-point formatAdditionCellular automatonGeneric programmingMultilaterationSheaf (mathematics)Mechanism designProgramming paradigmArmInclusion mapSubsetComputer animation
outputTrailSoftware frameworkPersonal digital assistantCodeEmailMessage passingContinuous trackString (computer science)Address spaceRegulärer Ausdruck <Textverarbeitung>DatabaseAutomationFunctional programmingObject-oriented programmingData conversionMereologyException handlingPoint (geometry)Shape (magazine)TrailAddress spaceErlang distributionRight angleError messageType theorySet (mathematics)Multiplication signAbsolute valueEmailLogic programmingCrash (computing)Logical constantMonad (category theory)Validity (statistics)Telephone number mappingCodeTypprüfungConnected spaceSystem programmingAxiom of choiceString (computer science)DivisorDatabaseNumberCodeServer (computing)DataflowDivision (mathematics)outputCASE <Informatik>Software frameworkWeightEndliche ModelltheorieFunction (mathematics)Formal languageDomain nameMatrix (mathematics)MultiplicationLine (geometry)Single-precision floating-point formatConcurrency (computer science)Generic programmingMatching (graph theory)Regulärer Ausdruck <Textverarbeitung>Electronic mailing listInheritance (object-oriented programming)Event horizonLatent heatMessage passingComputer clusterArmInformationSoftware testingReal numberShift operatorMetropolitan area networkState of matter2 (number)1 (number)System callCausalityAutomatic differentiationHazard (2005 film)Online helpDiscrete groupGoodness of fitJSONComputer animation
Message passingDatabaseAutomationString (computer science)Address spaceEmailServer (computing)Software testingTape driveCodierung <Programmierung>Function (mathematics)InformationEvent horizonTime domainCustomer relationship managementFinite element methodDevice driverBoundary value problemSynchronizationDatabase transactionPhase transitionPersonal digital assistantDemo (music)Logic programmingEndliche ModelltheorieCompilerSynchronizationString (computer science)Connected spaceElectronic mailing listAuthenticationFunction (mathematics)EmailResultantSocial classDatabase transactionoutputAxiom of choiceMultiplication signGame controllerEvent horizonTranslation (relic)CodeContext awarenessSystem programmingDifferent (Kate Ryan album)CASE <Informatik>Object-oriented programmingLevel (video gaming)Intrusion detection systemSoftware testingDomain nameParallel portCompilerService (economics)Operator (mathematics)User interfaceGeneric programmingAddress spaceDatabaseType theoryBoundary value problemServer (computing)Validity (statistics)Error messagePattern languageMessage passingTrailAsynchronous Transfer ModeClient (computing)Functional programmingPhase transitionSoftware bugFehlererkennungData conversionAuthorizationProduct (business)Keyboard shortcutMobile appUnit testingComa BerenicesTouchscreenDevice driverMonoidWave packetLoginSingle-precision floating-point formatInheritance (object-oriented programming)Focus (optics)Right angleDirected graphGame theoryAreaMathematicsProcess (computing)CurvatureCodePower (physics)Machine visionPosition operatorConservation lawEnvelope (mathematics)Covering spaceStreaming mediaExecution unitCompilation albumGroup actionArmSurfaceSelf-organizationComputer chessForcing (mathematics)Noise (electronics)Personal area networkArithmetic mean
String (computer science)Mobile appModulo (jargon)Chi-squared distributionVisual systemMIDIWindowBuildingFile formatSystem programmingLoginDatabaseException handlingDocument Type DefinitionView (database)CodeComputer programmingGame controller1 (number)CASE <Informatik>Identity managementCodeException handlingBlock (periodic table)Computer programmingAliasingRight angleSlide ruleSampling (statistics)Online helpDataflowService (economics)Digital electronicsShared memoryComputer animation
Transcript: English(auto-generated)
All right, can everyone hear me okay? Can everyone hear me all right? Yep, okay, very good. All right, so welcome to Railway-Oriented Programming. So in this talk I'm going to explain the functional approach to error handling and
if you already understand the either monad with bind, then you don't need to be here. So does anyone understand what I mean when I say either and bind? No, okay, then hopefully this will be useful to you. Hopefully actually by the end of this talk you actually understand what I'm talking about. Maybe not under that terminology, but under Railway-Oriented Terminology.
So, okay, so what do railways have to do with programming? Not a lot, except it's quite a nice metaphor for what I'm going to be talking about today, which is trying to introduce these concepts using pictures and concepts that hopefully will be so obvious that you think, well, why did anyone ever think these things were complicated in the first place?
So my name is Scott Velosian. I managed to get the Twitter handle Scott Velosian. That was very good. There was quite a lot of demand. I had to spend a lot of money to get that. I have a website called fsharpforfunandprofit.com, which is an F-Sharp website, and
I have a consulting business called fpbridge.co.uk. The examples in this talk will be in F-Sharp, because that's the language I use, but in fact they will work equally well for Haskell, OCaml, Rust, and Swift, if you're into Swift.
So this is a visa, just very general concepts. Right, so what I'm going to talk about is I'm going to talk about happy path programming first, which is what we normally spend our time thinking about. What happens when everything goes right, and we basically don't spend enough time thinking about what happens when things go wrong.
And that's really what this talks about. So when we deal with things that go wrong in an imperative language like C-Sharp or Java, we have certain ways we deal with that. I'm going to show you the functional equivalent, which I am using. I'm calling railway-oriented programming, show you how to do this in practice, and then various
techniques you can do to actually extend it to be quite powerful. So, let's start off with a simple use case. So here's my little scenario. As a user, I want to update my name and address, and email address. OK, so I receive, I'm going to assume this is a very, very crude website, web service.
I'm not going to get hung up on the implementation too much. It's really just a concept. So let's say there's a request, it's got a user ID or a customer ID, and there's a name, and there's an email address, and so on. I need to validate that. Maybe I need to make sure the user ID is not negative, make sure the name is not blank, maybe canonicalize it, maybe strip out spaces,
you know, lowercase the email address, something, I don't know. Then we update the existing user record in the database, and then maybe if the emails change, we might send out a verification email, saying, your email has changed from this to this. You know, is it OK, you know, just to make sure that someone else hasn't taken over your account?
And then finally we return the results user. That's really extremely simple use case. Can't really get much simpler than that. So how would we try to intimate C Sharp? Well, we have a request, and then we validate the request, and we canonicalize the email, and then we update the database, and we send an email, and then we return success.
So that's, the code pretty much matches the use case. By the way, I'm going to show quite a bit of code. I don't want you to actually spend too much time reading the code. I just want to really just go over the, roughly what the code looks like, you know, so it doesn't really matter the details of the code. But just to show you, sometimes there will be more code, sometimes there will be less code.
Just to give you an idea, just quickly scan it. You don't have to, like, understand every line. So that's how it looks in C Sharp. Hopefully that would be very familiar. I'm not saying this is the greatest code in the world. You know, you might want to put an async version of the send email or something, but it's good enough for this example. So let's look at the functional equivalent. So this is how you might write the same thing in F Sharp.
You have an update customer function, this is a method. You receive a request, you validate a request, you do the, almost identical, line for line, pretty much the same thing. Those little double arrows, that's F Sharp's way of composing functions. So basically the output of one function goes into the input of the next function.
But other than that, it looks, you could understand, this is pretty much like the C Sharp code. Right, so that's if everything goes right, but what happens if things go wrong? Okay, so, happy path, what could possibly go wrong? And we never really think about it enough. This is a great quote I like, a program is a spell cast over a computer turning input into error messages.
So how often, if we're lucky, things will come out right, but a lot of the time, if we're not careful, it'll come out wrong. So here's, you probably, I don't know if you can see these, but I'll read some of these out to you. This is invalid, unhandled exception, the device is not ready. Okay, you've seen this kind of thing zillions of times. This one says, unhandled exception has occurred, invalid username.
This one is a VB, visual basic error, overflow, very helpful. Says runtime error six. This one, an exception was unhandled, the developer needs to do his job. This one says, you've been warned three times, this file does not exist.
Now you've made us catch this worthless exception and we're upset, do not do this again. This one, a classic. Keyboard not plugged in, press F1 to retry. Good job, you broke photosynth. It wasn't your fault, but photosynth will crash and burn, perhaps even taking this instance of RE with it.
An error has occurred while creating an error report. That's a good one. An error has occurred, but this error message cannot be retrieved due to another error. And this is my favorite one, error, the operation completed successfully. So this is the kind of thing, you'll end up on one of those websites
like the Daily W Chef with something stupid. Don't do that, handle your errors properly. So that's I think one of the difference between, you might say, a professional programmer and a weekend amateur, is how well do you handle your errors, really? That sort of differentiates the grown-ups and the kiddies.
So let me go back to this use case. As a user, I want to update my name and email address and see sensible error messages when something goes wrong. We never put this in our use case. We sort of assume this, we take it for granted. But I'm going to explicitly put it in here because we need to think about it. So let's think of all things can go wrong in this, even in this like three steps, OK, what can go wrong?
Well, the first thing is when we try and validate it, we can get a blank name, we can get an email which is not valid. All sorts of things can go wrong. When we update the user record, maybe the user isn't found in the database. Maybe we get a database connection error. When we try and send an email, we get an authorization error,
we get a timeout. Lots and lots of things can go wrong. I haven't even started. I mean, this is just scratching the surface. As you can think, there's hundreds of things that go wrong. So let's see how our C-Sharp code changes as a result of all this error handling. So here's the original code. But then we need to validate the request. So if the request is not valid, we return request is not valid.
And then if the database fails, then we have to say customer record not found. But then we need to, the database might throw an exception. So we have to wrap the whole thing in a try catch block. And then if we send the email, maybe we can't log in properly. So all of a sudden, our nice, clean C-Sharp code that modelled the use case very nicely in the happy path,
now it looks really ugly, all right? And basically, we went from six clean lines to 18 ugly lines. And that's 200% extra code. That's two thirds the code is error handling. And only one third of the code is actually doing something useful. And I'm sure you've all seen code like this.
I'm sure your code is full of this kind of stuff. And it's really annoying because it sort of gets in the way of trying to understand what the code's trying to do. It's like the night, the original code without hearing was lovely, I could totally understand what that was doing. Now I'm like, I'm totally lost about what this code is going to do. So let's look at the functional equivalent of this code. And the question is, can we preserve the elegance of the original code in the functional thing?
Let's see how complicated the functional code gets. So here's the original functional code, all right? Very similar to the C code. And here is the code after error handling. So does that code look familiar? Yes, it's exactly the same code.
Now, you might think that's impossible. How can you have error handling code that hasn't changed? And you might not believe me. Hopefully by the end of the talk, you will believe me. And I can even demo, I actually have a little Visual Studio project. I can actually demo it to you. So that's the point of this talk. So let's look at the difference between error handling
in an imperative design and a functional design. So in an imperative design, I have a request handling service like a website. The request comes in and response comes out. That's in the happy path, I pass it through a validation function and an update method and a send method and I get the response back. Now, if something goes wrong in an imperative model, I can return early.
So if the validation fails, I just return early. I just never pass it through to the next thing. And if the update database fails, I just return early and I never pass it to the next thing. So that's pretty much what the imperative code looked like. There was try something if it fails, return. Try something else if it fails, return.
If, if, if, fail, fail, fail. So how does the functional design differ? So in the functional model, you don't have an input. You don't kind of call method and get response. So once you have a function, a function has an input and an output. So it's like a little black box. And every function has exactly one input and exactly one output.
That's sort of the definition of a function. All right. So in this case, we have a function. The whole use case is going to be represented by one single function with an input and output. So in the happy case, the single function is going to be consist of smaller functions, which are connected together in a pipeline.
So the output of the validate goes to the input of the update and the output of the update goes to the input of the send and so on and so forth. The final output is your response. That's fine when everything goes well. What happens when things go badly? So the problem when things go badly is you can't do an early return. That concept does not exist in functional program.
What you have to do is you have to keep going all the way to the end. You cannot pass go. You cannot collect your $200. You always have to go to the end. So that's what a functional model looks like. So there are a couple of questions. First of all, how can you bypass these downstream functions? I want to return early, but I can't.
How do I do that? And the second question is, how can a function have more than one output? I just said a function can only have one output. And here, it looks like I've got like four different outputs, one success output and three different error outputs. And that's not possible. Functions can only have one output.
So how do I do that? Well, let me ask, let me answer the second question first. So in a functional design, here's my three failure cases. What you can do is you can create something called a sum type. Discriminated union is called an F sharp or choice type, I like to call it, because it's basically a choice of these four different things,
but it's encapsulated into one single value. So it's not four different results. It's one single result. There are four different choices in it. So you can think of it like as a old style C union type with a tag that tells you which one, except it's type safe, unlike C. Well, that's very, very specific to this particular use case.
So, yeah, some types are great, by the way. One of the very best things about F sharp or Haskell or OCaml or languages that have them. Worth switching languages just for that one feature, if you're interested, if you're into it. This one, this one is a bit specific to this particular use case. It's a bit more of a generic one I can use.
Let's just have a more generic one. You have a success or a failure. You've got two choices, right? So I've merged all the failure cases into one single failure output. All right. The problem with that is now I have no information about the failure, no information about the success either. So what I do is I modify that bit and I parameterize it by a type.
So it's this T entity. So it's some sort of value that is on the success path, like the customer or the product or whatever it is you're trying to do. And in the success path, that's what you get. And on the failure path, you get a string. So you've got a choice of two different things, a successful thing, a successful entity or a failed string.
And that's returned as a single value from your function. Does that make sense so far? You can think of this as a little bit like an inheritance with two subclasses. That's another way of thinking about it, if you want to do it that way. Right, that's actually not the final time,
but that's a good starting point for the rest of the talk. So each case, each use case is a single function. The function returns a sum type with two choices, success and failure. The function is going to be built from a set of smaller functions and each smaller function is going to represent one step in the data flow.
And then you can connect all the smaller functions together. And all the errors are going to be combined into a single failure. So that's our functional design for doing error handling. All right, well, that's hopefully that sort of makes sense. But the devil is in the details. So let's look at how we're going to do it. How do you actually bypass the downstream functions when you have an error?
Right, that's the key thing. How can we actually get that to work? So how do I work with errors in a functional way? Well, as it happens, I have this very clever friend and he knows everything about functional programming. And next to him, I kind of feel like I'm kind of stupid.
So I asked him, how do I handle errors in a functional way? So I have a series of functions that I want to chain together and I need to capture errors at the same time. And he said, that's easy, you just need a monad, right? So you probably all heard of monads, but you probably don't know what they mean. And that's how I felt when I first encountered it. So I said, well, what's a monad?
And he said, a monad is just a monoid in the category of endofunctors. And you might have heard that too, and that's kind of not very helpful. So I said, well, you know, and he said, what's the problem? And I said, I don't know what an endofunctor is. And he said, well, it's easy. A functor is just a homomorphism between categories. And so an endofunctor is just a functor that maps a category to itself.
So this is the kind of thing when you talk to smart people, this is the kind of stuff they tell you, OK? Simple, he said, and I said, yeah, right, of course, I understand. But seriously, seriously, what do I have to do? So he said, OK, well, you don't really need to know everything about monads. You just maybe need to use maybe.
And I said, maybe what? And he said, maybe the monad. And I said, maybe the monad what? And he said, no, no, no, maybe it's the name of the monad. And I said, don't you mean maybe the name of the monad is? And he said, no, you're talking like Yoda.
Maybe the name of the monad is. And I said, no, no, talking like Yoda, you are. So he said, yeah, I say, OK, maybe it's definitely what you want. So I said, definitely maybe. And he said, actually, I prefer what's the story, Morning Glory. So, yeah, whatever that is. And then he says, and he changed his mind.
He said, OK, actually, either might be better. And I said, either what? And he said, either the monad. And I said, either the monad or what? And he said, either that's all. And I said, just either. And he said, no, just as part of maybe. So if you're a Haskell person, you'll understand what I'm talking about. And I said, just maybe.
And he said, no, you have to say just just or just nothing. And I said just nothing. But a minute ago, you said definitely maybe. And he said, well, now I'm talking about either. And I said either just nothing or definitely maybe. Which one is it? Make up your mind. And he said, neither, just use either. And that's sort of when my head exploded.
So I don't know if you've ever had a conversation with, you know, people expert, maybe academic people in functional programming. I think if you understand all this stuff, it's very easy to talk like that. And I want to try and present a way which is a little bit easier to understand than that. So really, monads are actually not that confusing.
They have this thing about being confusing. If you actually read the original paper by Phil Wadler, it's actually not that bad. A little bit scary if you don't like math, but it's actually very, very readable. Just skip over the math bit and focus on the text and it should make perfect sense. So instead of monads, I'm going to talk about railway oriented programming.
It's got nothing to do with monads. So let's go back to the definition of a function again. So I think I like the analogy of a function. A function is like a bit of a railway track, OK? And on this railway track, there is the tunnel of transformation. And that turns things from one thing into another.
When they go through this tunnel, they get transformed into a different kind of thing. So in this case, maybe I have an apple going into this tunnel of transformation and it comes out as a banana, OK? So this function takes apples to bananas. It turns apples into bananas. So you write it in F sharp and in Haskell as well, you write it as apple, little arrow, banana.
So it takes an apple as input and it outputs a banana as output, OK? Does that make sense? Pretty straightforward. So what happens when you have two of these functions? So here is a function that turns an apple into banana and here's another function that happens to turn a banana into a cherry. How do I connect them together, right?
So in functional programming, what's very nice is you can compose them together and you basically glue them together. And it's pretty obvious how you glue them together. You just stick them together like that. You take the output of one and you stick it at the input of the other. That's called composition. And when you do that, you get a new function and this function turns apples into cherries, right?
The banana is kind of hidden inside. In fact, what's really great about this is you cannot tell that this new function was built from some more functions. And that's one of the great powers of functional programming is you get this composition model where the tiny things are functions but you build them, you glue them together and you get this giant big function and it's the same thing.
You literally cannot tell how it was built. And it's the same model all the way from the bottom to the top. All right, so that's with a single input and a single output. So in this situation, we have an error. So here we have, let's say we have a validation function and it gets an input, it gets a request as an input,
but it has a success branch and a failure branch. So how could we model that using this railway analogy? Any ideas? Branch line, yes. So I'm going to show you, just show you a little bit of code here. So if the name is blank, then throw a failure case.
If the email is blank, throw a failure case, otherwise a successor. So that's an example of real F-sharp code that generates a failure and a success. All right. Yes, the way you model it is one of these things, OK? Now I'm going to call them switches, which is the US terminology.
UK, British English calls them points. I don't know what they say in Norway, but I'm going to use American terminology just because I think switch is actually a better word from a functional program point of view for what these things do. So we have a switch and it has inputs and it has two outputs.
Now, like I said, it's not really two outputs. It's one structure with two cases, but I'm going to call them two outputs, basically. So you've got a success case in green and a failure case in red. So how do you glue these together? So let's say we have the validate function and it's got this kind of split and we have an update database function and it's got a split.
And I want to glue them together in a line. So what we have to do is if the validate function is successful, we want that input, the output of that one to go to the input of the update. But if the validate is a failure, we want to bypass it and go all the way to the end.
So I think it's pretty obvious that the way you connect them is like that. OK, I think it's hopefully it's really obvious this is the right way to connect these two things together. Let's see what happens when we have three different functions. So here we have our validate and our update and our send email. And I want to glue them all together.
And when I do that, I end up with this. So this is what I call a two-track model. All right, so instead of having one track, you have two tracks, you have a success track and a failure track. And the data comes in and when something goes wrong, gets shunted onto the failure track and the failure tracker
keeps going to the end of the function. So hopefully that's pretty straightforward. I think it's kind of quite easy to understand. So how do you actually do this in practice? So let's just step it back a bit. So we have our two-track system and we have these two-track tunnels. So these steps on our process are now these tunnels,
they cross both tracks now, right? It's not just a single tunnel of transformation. The tunnel is now quite wide and it covers both tracks. But if you look inside the tunnels, you can see that inside each one is actually a little switch.
So, gluing them together. So I talked about it's quite easy to glue together if you've got one coming in and one coming out, you can glue them together, it's really easy. It's also easy to glue them together if you have two things going in and two things coming out, because you just connect the two things together, just like some sort of plug and socket, yeah?
But we don't have two things coming in, we have one thing going in and two things coming out. So how do we connect them? That just won't connect properly. So what we want to do is take our one-track input, two-track output thing, our switch,
and we want to turn it into something which has two-track input and two-track output. Right? If we can turn it into those things, then we can glue them together really nicely. So how do we convert from the first case to the second case? Well, what we do is we have a little adapter block, okay?
So this is a little thing, and it has a two-track input and a two-track output, but it's got a little slot in the top that we feed our switch into and it magically sorts it for us, yeah? So we pass in one of these switches and we get out one of these two-track things.
So it's literally the adapter pattern, if you want to think of it from OO, I'm turning, I'm converting something that doesn't fit into something that does fit. So, let's see how that works. We have this two-track input, so we're actually going to define the function, I'm going to define the function for you,
it's literally a four-line function like this. So, we pass in a switch function and we output a new function and it has a two-track input and the two-track input on the success case, we call the switch function, right? And if it's the failure case,
we just go straight out, it's a failure. So failure in, failure out, success in, we call the switch function. The switch function could be a success or failure depending on how it actually works. So that's exactly how you write this adaption function. Now, this adapter function is actually called bind
in functional languages. Well, there's various reasons why it might be called bind, but if you see a function called bind in a functional language, this is exactly what it's doing. And I'll just show you the type signature. In functional languages, type signatures are really important and they actually tell you everything you need to know about the function normally,
so you don't really need to know what the name is. If it has that type signature, you know what kind of function it is. So this particular function has three parts. The first part is something that takes an apple to a banana, right? But it takes it from a one-track apple to a two-track banana, right?
So that's our switch function. The little a and b, that's the functional way of doing generics. So, in a C-sharp, you might call it a T entity one and a u and a v or something, right? So in functional programming, you use abc for generics. So you think apple, banana, cherry. So it takes an apple to a banana
and as a result of that, the output is a new function and the new function takes a two-track apple and turns it into a two-track banana. Alright? So that's the bind function. Like I say, this is one of the very most important functions in functional programming.
You can actually write the same function with two parameters. This one has one parameter, a switch function. This one has two parameters, a two-track function. It's exactly the same function. One of the weird things you can do in functional programming is have a one-parameter function and a two-parameter function that's the same function.
And that's called currying. And I have an interesting post about that if you want to, but you don't really need to understand that for this talk. Alright, so let's look at some real examples. We have our name not blank validation function. If the name is blank, it returns a failure. If the name is not blank, it returns success.
Let's say it has to be less than 50 characters long. So if it's longer than 50, you return a failure. Otherwise it's a success. If the email is blank, it's a failure. Otherwise it's a success. So there's our three little switches and we want to glue them together. So what we do is first of all for each one
we put a bind in front of it and that converts each one of those little functions into these two-track functions. And then once we have the two-track functions we can glue them together with composition. Does that all make sense to everyone? Alright. And what's cool about this
is we now have a new function called validate request say, that has those three different validation steps in it. But it looks like one big two-track function. It's like now one big tunnel rather than three small tunnels, it's one big tunnel. So I now have a new function that's a two-track function input
and it takes a two-track input and a two-track output and again, I cannot tell that it was composed of smaller functions. Which is kind of cool. And as you can see, it's pretty obvious that now I just build up my bigger functions, my big two-track functions and my small two-track functions, just the same way that I built my one-track functions
by gluing them together. I can build my two-track functions by gluing them together. But I have to use the bind to do that. Sometimes you'll see this symbol for bind, which is double arrow followed by an equal sign. And so the code might look a bit like that. I'm not going to use that, actually.
I'm going to try and stick with the word bind just so you know what I'm talking about. But this is one of those things where you see some strange symbols in functional programming and you think, oh, it's full of all sorts of weird symbols. There aren't actually that many weird symbols. There's maybe five or six things that kind of crop up over and over and this is one of them. So if you see this, it's just another word for bind.
It's an infix version of bind. Right. Just to point out that this bind thing has got nothing to do with how the things get transformed. It's about the shape. So if we take an apple input and it outputs a banana and a banana input and it outputs a
cherry, the whole thing takes an apple and outputs a cherry. Now if I try and pass in the wrong kind of thing, if they don't connect up you know, if I'm trying to, if something takes a pineapple instead of a banana or something, they won't I still can't connect because the types won't match. Alright. So it's still type safe. But it's all about the shape
not the types themselves. So here's my generic two-track thing. Same as the example I showed originally. So in this case the T entity is an apple. In the second case, the T entity is a banana and the third case, the T entity is a cherry. So it's a generic type. Just like
a list of T entity or you know, an I repository or not disposable you know, the various functions that take generics in .NET. Alright. So let's review what we've got. We started off with these two switches and we
turned them into two-track functions using bind and then we glued them together with composition and we now have a new function. Alright, time for a joke. Because that's kind of boring. This is a kind of boring, I mean I'm not saying this is an exciting talk. I think it's interesting but I wouldn't say it's exciting. So here's a bit of a joke for you. What do you call a train
that eats toffee? I don't know. What do you call a train that eats toffee? You call it a choo-choo train. Right. Okay, that's for all the seven-year-olds in the audience. Some of you might be eight-year-olds so you might not find that funny anyway. Alright. So let's see
what we can take, let's see what we can do with this. We can take this out of a spin and work with other kinds of functions because that's not the only kind of function we have to deal with. That was, you know, a very very simple model. So let's, real life is always more complicated than that. Let's see if we can fit real life into this. So the first kind of thing we have to deal with is single-track
functions. So we talked about these switch functions like validation where there could be a success and a failure. What happens if it's always going to be a success? If you can guarantee it's going to be a success. How does that fit into this model? What happens if you have a dead-end function? Like you put something in a database and nothing comes out. It just, you know, it just vanishes, gets sucked into nothing.
How do you handle that? What about functions that throw exceptions, right? Because if you're dealing with .NET code it may well do that. And what about, you know, what I'm calling supervising functions like logging, monitoring, event handling, that kind of stuff. So we'll start with single-track functions. So let's see, here's an example of a
single-track function canonicalize email. Okay, so we're going to trim the email and we're going to lowercase it. Alright, now that function can't go wrong assuming the input's not blank because we know it's not blank because we validated it before. So I'm going to assume the input's not null, not blank and hopefully it won't throw an exception. If it throws
an exception like out of memory or something then there's nothing we can do about that. But it's not going to throw an IO exception or anything. So it's a one-track function but that doesn't fit into our model, right? We can't glue a one-track function in between these two-track functions. So, what do we do? Well we have to turn it into a two-track function, right?
It's kind of obvious. How do we turn it into a two-track function? We have one of these adapter blocks. So in the previous case the adapter block had a switch in it. This adapter block just has a single slot for a one-track function. So the failure track is never used. So how do we turn a one-track function into a two-track function?
There it is, it goes in like that and it comes out like that. So the function that does that in functional programming is called map. Or sometimes it's called lift. So it turns a one-track function into a two-track function. And it's very simple. Again if you have a
successful input, you run that function on the success and then you put that on the success branch. And if you have a failure as input, you just return the failure. And it has this type signature. Slightly different. The first one with bind it took an apple and it returned a two-track banana. In this
one it's an apple and it takes something of some type like an apple and returns a different type like a banana. And it turns it into a two-track apple going to a two-track banana. But there's no error handling. The first parameter doesn't have any errors. So it's just straightforward mapping from one thing to another thing.
Alright? So I say don't worry about this code. I'm not expecting you to understand this code. It's just showing you that it's actually just literally a few lines of code. Hopefully I'll put the slides up. You can go over them at your leisure later on. And you can actually write map in terms of bind. So
it's kind of one of the cool things about functional programs. You can build, even functions like map, you can build it from small functions. So that's how you build it from bind. Right, so now we've turned our one-track into a two-track and we can glue it together. That's great. What about dead-end functions? So a dead-end function is something like updating a database. You have some sort of customer, you update the database
nothing comes back. It's like a void. Alright? Now in functional programming you actually can't have void. Every function has to return something. In functional programming that's called a unit. So it returns a unit but it's still pretty useless. There's not a lot you can do with it. So again, it doesn't fit into our
model, two-track model. We need another adaptor function. So what we're going to do is we're going to turn our dead-end function into a single-track function. Alright? So have a one-track input and a one-track output. And I'm going to call it T for lack of a better word. There's not
really a consistent name for these kinds of things. But you can see that you can slot in my dead-end function. But what it does is it takes the same input and just passes it on as the output. Now once I have my one-track function I can then use map to turn my one-track function into a two-track function.
Just like I showed you before. So my dead-end function can be slotted into this model as well. Alright? Let's look at functions that throw exceptions. So I'm specifically thinking of anything to do with IO. That's where you get it. File exceptions, web exceptions,
database exceptions. You know, you call something and it says oh yeah, I return a file. It's like no you don't. Sometimes you return a file, but sometimes you throw an exception. So what we do here is we have a, what looks like a one-track input. Right? Like let's say you're sending an email to SMT server.
It looks like it's a one-track function but it's not really because it could throw exceptions. So what you want to do is catch all the possible exceptions it could throw. Right? And you basically wrap it in a try-catch. Right? And now what you've done is you've turned something that throws exceptions into something which is a switch function. So any possible
error that could happen in that function is now put on the failure path. So once you've turned it that way you now don't have to worry about exceptions in your code anymore. You just wrap your IO stuff with these exception handling things. So now send email looks like that. Once it looks like that you can glue it together with all the other ones. And
like I say I wouldn't worry about things like out of memory exceptions. I would worry about, you know, file not found exception. You know argument null exception or all the weird exceptions. Web timeouts, you know. So it's a very important guideline in functional programming. You don't
really use exceptions in functional programming as an error handling mechanism. A camel does a little bit but it's generally considered bad form. You basically want to turn things into errors. Especially this two-track error model. And then you can handle it nice. You can see exactly what's going on. It's not just me who says this. Yoda says this too.
You probably didn't think that Yoda had an opinion about programming models. But in fact he does because remember he said do or do not. There is no try. yeah. Alright.
So yeah. Don't use try. If you're using try catching code you're doing something wrong. Now you can use try catch at the very low level when you're dealing with IO. But once you've done that it should not bubble up. You should never have to handle exceptions higher up. And finally we have super variety functions like you say tracing, logging, like that.
And here we go. The same model we have let's say we want to log everything that happens on the success. Or maybe we want to log everything that happens on the failure. Right? So we just want to insert that into our stream. Well we just have an adaptor block and it takes two functions. One you know that you do on a success and one that you do on a failure.
So that's really straightforward. So putting it all together we have our validate function which takes an input, we have a canonicalize, we have our update database, we have our send email. Oh. Something we've forgotten is how do we actually get the output of this thing. So we've got this two track model. But your browser
doesn't understand two track types. Right? Your browser deals with strings. Right? So what we need to do is we need to handle, take both tracks and merge them together into something that your browser can handle. So here's an example of return. So let's say it's a
success. You return an OK and you turn the object to JSON or something or XML or whatever. On that if it's a failure you might return a bad request say. Or you know invalid server, invalid server operations and exactly which one you can do.
I'm actually going to show you you can choose in more detail exactly which one to return. But if you look at this workflow you can see that there's no early returns and all the error handling is done at the very end. Right? The final conversions is done at the very last step at the return side. Which makes these things really
really easy to test. So if I just want to test the validation logic, if I just want to test the send email logic I can do that. Each one of these things is completely isolated. So it's a really useful framework and I think it really pretty much covers most cases. I mean there's some cases it doesn't handle but I think for most things I would recommend
using this model. let's look at the code we had before and after error handling. Remember I said I want to see what we can do what it would look like. So that was the code before. Right? Receive request you take the output of that, you pipe it into validate
request, you take the output of that, you pipe it into update and so on. And here's the code afterwards. Now I promised that they would look the same. And I think that you can see they really are because if you look to that two flow that two track model is exactly the same receive, validate, update send, return.
Right? So the code looks the same. But what's been passed around are two track things rather than one track things. Make sense? Yeah? Still clean and elegant. That's what I like about it. Time for another joke. You're laughing already. I haven't even said the joke.
OK. Why can't the steam locomotive sit down? I don't know. Because it has a tender behind. Right. I'm going to talk about some more stuff. Any questions so far? Making sense? So this entire thing is predicated on the fact that you're not really concerned about what
functions are doing. You have this set of functions that take those things and convert them into something else that you can then compose and handle it while you go along. Yes. So the question is, the whole thing's predicated on the fact that you can have a generic way of composing functions that don't really care
what they're actually going on. It's about the shape of the functions being glued together. Absolutely. This is a completely generic error handling technique. So it's a uniquely functional way of working? Yes. Well actually all you need is choice types or discriminated unions. So if you actually have what they call the ether monad in Haskell
you can do this. So you can do this in C sharp. It's painful. You can do it. It's a lot easier if the language... If you can do it in four lines of code it's a lot easier than doing it in a hundred lines of code.
If I don't have a surrounding exception here... So the question is what happens if I have divide by zero? Well no, every single... The divide by zero would be a step called divide by zero. That's a one track function that throws an exception. Yeah.
Well no, I think... Well yes, it depends on what you think is a pain in the ass. I would have a function called divide two numbers and it returns a two track function. It returns a yes or no. And I can then reuse that function over and over. I wouldn't have to write that function more than once.
Yes, because all your code that has a divide that's... Yes, but all your... Well, okay. So the question is do you have to change all your codes that has... Let's say you've got these divisions everywhere in your code and you have to change it. The answer is yes. If you want to have safe error handling, everywhere that you do a divide that is not wrapped in an
exception handler you have to wrap it in an exception handler. Yes. Which you have to do either globally... Well you don't have to. You could wrap it globally. I'm not saying this is good. If I'm doing things like matrix multiplication or something I wouldn't necessarily recommend this as the best method. But for business cases if you're wrapping... These are
reusable functions. So once I've got a generic exception handler for divide by zero I'd pass that through all the way. Absolutely. So yeah, it doesn't help if you've got a legacy code that doesn't do that. It's painful. Now in Erlang of course you just crash and carry on. But that's... This is not Erlang.
Yes. That's why... So this is good for systems where you don't want... Where it's bad if your system crashes. So the point is that in Erlang you just crash and carry on. In .NET
you don't really want to throw unhappy exceptions. It's not a good model. So yes one answer is don't use .NET but if you are using .NET this is probably what you want to be using. Right. Okay, so let's extend the frame and see what we've got. So I'm going to talk about designing for errors. So if you're going to use this model
there's a way you should design for errors. Designing for errors actually becomes part of your requirements. I'll talk about how to do stuff in parallel and I'll talk about domain events. So let's talk about error handling. So the first... The point I want to make here is that errors are actually requirements.
Okay, the unhappy paths are requirements too. Don't ignore them just because you just cross your fingers and hope they never happen. That's not very good to design. Alright. So let's look at how you can turn this model into something where you actually design the errors in. Alright, rather than just being an accidental afterthought. So
let's say we're validating our input and we have our failures here. So and here's our two-track type. First problem is that using strings is a terrible idea. Alright. Strings are just awful. They don't really tell you information. They're very
locale-specific. I can't translate this into Norwegian very easily. It's just not a very good model. Strings are not a good way for designing things. So what I really want to do is use an enum. Right, so I have a set of constants as you might say. And each possible error is represented by enum. And in
fsharp, that's actually a choice type. So we're creating a special type for the failure case. Not using string anymore. So in the two-track model the failure is no longer a string. It's now an error message type. Alright. And that error message type is now a list of choices. So
it could be a name not blank, it could be an email not blank. Alright. One of the nice things about using the fsharp choices is that the choices can actually have data along with them. So they're not just, it's not just an enum in the C-sharp sense. It's a set of, again, more like inheritance where each possible subclass can have
data with it. So in this case if the email doesn't match the regex for validating emails, you can actually say it's not valid and here is the email that didn't work. Alright. You can actually pass data along with it. And if I go down to my error message type the email not valid choice has an email address that goes along
with it. So later on when it comes time to generate the error messages I know exactly what the email address that triggered the email was. So I can log it later on I can give it back to the user and so on. So what you do is you start off, here's our error message type and basically you keep adding everything. Every time something goes wrong you add a choice to this.
So maybe the user ID is not valid or maybe the database user is not found or maybe you've got a connection string timeout, concurrency error, authorization, SMT everything can go wrong, you start putting this big long list. So you think oh, blimey, that's a lot of stuff I have to put everything that could possibly go wrong in this list
and I say yes, you do. Because it's documentation for everything that could possibly go wrong. If you don't put it in this list it means you've forgotten something. You don't have to have a special thing for email blank and you could have a generic database error or you could have a generic invalid server
error if you want to keep it generic. The more specific you are the better your logging can be and the more fine grained your error handling can be but that's up to you. But I personally think having 50 things that can go wrong is actually really good because I now know that I've thought about everything that can go wrong in this particular use case I have documentation for it.
And what's great about this documentation is it's type safe because this is a type, this is code right, this is not just a comment this is code. So if I try and create a new kind of error that is not handled you know, let's say I want oh, I don't know yeah, the user
the custom ID is not negative right, there's a little value that's not on here. So that validation if it tries to create a custom ID is not negative error there's a compiler error because it's not on this list I have to put custom ID is not negative as a choice and then I can then my code will compile.
Right, so it's you might say yes it's annoying but it's actually less annoying to think up front of everything that can go wrong. Yeah, question. Yes. Yes, because like I said you can put data inside so the email you could just have an email error of
and then specific individual emails yeah, exactly, you can organize, this is a very I'm deliberately making it very flat but you could totally make it hierarchical if you want. And you could have different levels at different service boundaries within one level you can be very fine-grained like with a database at the database service
you might have you know authentication error, timeout error, whatever that gets rolled up into a generic database error for the user interface or something. So yes, you can map between errors at service boundaries. Okay. What I also like about this is it triggers
conversations about what errors there are because when you're coding typically you don't even know what these are when you're starting to code but as you code you say what happens if the database times out what happens if I get an authorization error. You put that in this list to make your code compile and then you go back to your product owner
or your UI designer or whoever it is who's helping to manage the product, even if it's you and you think well what do I show on the screen when I get a database authorization error? So it really forces good conversations with the rest of the team about how you handle errors, which I think is really valuable because one of the problems
is again you think of the happy path that you don't focus on designing for errors and you might just say okay well I'll just show a generic error message but you know maybe the ops team want to know that you've got an authorization error, maybe that's useful for them so maybe you'll handle that in a special way. Once you do this thing, we've lost the strings
right? Originally we had an error as a string in the message, the failure case had a string, but now what we need to do is we need to turn the error code into a string. So now we have this ugly, long-winded method, you know if the name is not blank then return the string name is not blank, if it's email is not blank
return the string, email is not blank and so on. So again that seems awfully long and tedious but I actually say this is a good thing counterintuitively because one thing is that all your error strings are in one place right? You don't have them scattered throughout your code like a lot of apps do you don't have any strings in your code anywhere
except this very very end so only the very last step do you need to do the turn things into strings and if you're doing unit tests you don't need to so it's really just for the user interface that you need to turn them into strings and what's nice about this is you can use different strings for different purposes so if I'm logging it I might log
super fine detail about user authentication error with this connection string whatever and if I'm displaying on the user interface the string that I return might be you know sorry customer not found or something so you have a lot of control over different error messages for different context and finally it makes translation trivial I don't know if any of you
have tried to localize an existing legacy system but the first thing you have to do is find all your strings scattered around your code base and try and turn them into resource IDs and you know it's a pain in this model all your strings in one place in the very last step before the UI so it's a lot easier to translate
yes is it an anti pattern to override two string on the choice type? it's an anti pattern to override two string on the choice type yes because I think the translation should be context dependent so what I'd probably do is use different functions I'd say convert two string in the UI
context, convert two string in the error logging context convert two string in the ops notification context or something different context might need different you know for example in the ops logging context I might not bother to log the validation errors I don't care but maybe I do because maybe that's telling me that my UI has got
a bug in it or maybe it's too hard to use or something I don't know or it's not doing client side validation if you get the server side validation errors that means my client's not validating properly but the thing is there's a choice so let's review this it's a documentation of everything can go wrong it's type safe you can't go out of date
it surfaces hidden requirements it means your testing is now testing against error codes so rather than testing let's say I'm testing my validation logic I can say I would expect that if I pass in an empty name that I get a name must not be blank error rather than a looking for a string called name must not be blank
so it makes my tests less brittle and finally it makes translations easier so that's designing for errors let's look at parallel tracks now so if we take our validation logic here we have the name not blank followed by the name has to be 50 characters followed by
the email not blank now in this model they're in serial mode so you know I submit my name it's not blank I get an error so I try again and this time it's too long and try again this time it would be nice if I could run them all at parallel and get all the validation errors at the same time so how do you do that well what you want to do is you want to
parallelize these switches so what you do is you split the input you run them all and then you combine the output and if any of them have an error the overall output is an error and only if they're all successful is the overall output successful make sense? yeah? so how do you actually do that
well it turns out you don't have to write complicated functions you can actually write a function that just adds two things together so if you can write a function that adds two things together and outputs a new thing that will actually allow you to do everything so what you generally
do is write a simple add that says again if they're both successful returns success if any one is a failure return failure and the way you combine them then is you combine three of them by combining two of them that gives you a new one you combine those two and that gives you a new one so you can collapse a list of these switches into a single switch
alright and that pattern is what you might call the monoid pattern okay, anyone heard of monoids? yes? few people so I have a post called monoids without tears which might explain that but anyway that's one of the benefits of using monoids is you can collapse a list of things
down into one thing and the thing that you get the output is the same thing so you've got a switch you've got a bunch of switches as the input and you end up with one switch as the output so again you cannot tell what you did this final switch is the result of combining the other switches right, domain events
so we've talked about error handling so far but sometimes you don't have an error you just want to notify somebody that something happened so let's say your email address changed you just want to notify somebody or you just want to log something right? so in this model there's nothing saying that you can't put messages on the success track
as well, so along with your objects that you process on you have a list of notifications messages that you put on so for example in this case I'm going to have a user saved successfully event or an email sent event and I'm going to pass that into the COM to say oh I just sent the user an email
you just put it on the success track rather than the failure track so our success track now not only does it have the entity but it has the list of messages alright, pretty straightforward okay final joke why can't a train driver be electrocuted? because he's not a conductor
alright so I haven't got time to cover all the topics things I haven't covered is errors across service boundaries like we're talking about how fine grained are the errors at different levels how to do async, so everything I did here was synchronous right? in reality a real system would be asynchronous you wouldn't be waiting around for the database to return
you wouldn't be waiting around for the SMTP server to return compensating transactions, so if you're going to write to the database and then later on downstream you need to undo that rather than trying to do a two phase commit you can do what's called a compensating transaction just write straight out of accounting and things like logging I haven't had a chance to talk about that but I think it's pretty straightforward
how it works so just to summarize you start with a two track results, you use bind to turn it into these two track railway things you glue them together with composition and you make error cases of first class citizens now
what's the timer? have we got any time left? two minutes two minutes, ok there is a piece of example code at github which I can demonstrate and I think I can maybe just spend one minute just showing you that it's not totally fake so here is the
controller, I'll show you the C sharp controller first so this is a typical C sharp controller there is the get the original get method and there is the get method with error handling
so there's this error and then there's this error and the whole thing is wrapped in the try catch block and so on and so forth and the F sharp controllers look like this and
there is the successful case and there is the error handling case so I've got some extra stuff in there like logging and so on which I didn't have the original case and you can actually make both cases look identical just by doing some renames, so that's the
I just put some aliases in which logging a failure doesn't do anything. So that's an example of a successful case. And that's an example of the failure case. So it is true that you can make the code look identical. In this example, I tried to make the code look different, just so it's not too confusing.
So I can give them slightly different names. Right. Where are we? I don't always have errors, but when I do, I use monads. And I would suggest that you can actually use well-wanted programming instead. So there you go, well-wanted programming.
The slides are available at my website, slash ROP. If you want help with F-sharp, come and ask me. And there's the example code on GitHub. Thanks very much. Any questions before you all dash off?
No? Come and grab me. I'm around. Cheers.