4 ms·
I've seen this referred to as "railway programming", and there's a wealth of explanation over at https://fsharpforfunandprofit.com/rop/ https://fsharpforfunandp
by Smaug123 8y ago
I've seen this referred to as "railway programming", and there's a wealth of explanation over at https://fsharpforfunandprofit.com/rop/ https://fsharpforfunandprofit.com/rop/. In particular, the slide deck from the FP eXchange contains a diagram (slide 75 out of 154) with a happy path along the top, and a sad path along the bottom, and at every stage when you `bind` a Result into an existing Result, you have the opportunity to divert into the sad path. Neat little metaphor.
- pbiggar 8y agoWe're planning on using a similar strategy to ROP in Dark (https://darklang.com https://darklang.com). My suspicion is that editor support for "happy path programming" can make a huge difference in how easy it is to write safe code.
- mpweiher 8y agoWhat I found interesting is that if you have a pipe/filter architectural style, your happy path stays completely free of error handling, because when a filter doesn't have a good result, it just doesn't pass any data to the next filter in line. Done! You can then centralize the error handling by having a "stderr"-like output on your filters. When you have a call/return architectural style, you need to return something, and thread the result along, making things more complicated.
- singularity2001 8y ago>> if you have pipes/filters, you stay free of error handling: when a filter doesn't have a result, it just doesn't pass any data to the next filter in line. That is an important root insight. It also applies to for loops, and could apply to switch statements switch result { case something: break case none: error: empty: default: //? }
- lmm 8y agoI find this concept of a "stderr output" far more complicated. What's next, a "controlling terminal"? Values are much easier to reason about and inspect than functions, so it's better to have simple functions that return complicated values than to complicate the functions themselves.
- mpweiher 8y ago> I find this concept of a "stderr output" far more complicated. And you know this how? > Values are much easier to reason about and inspect than functions Right, which is why we want to avoid functions as much as possible. > simple functions that return complicated values than to complicate the functions themselves Fortunately, these filters aren't functions, so this doesn't complicate them, it makes them simpler.
- lmm 8y ago> And you know this how? Professional experience. > Fortunately, these filters aren't functions, so this doesn't complicate them, it makes them simpler. Functions are at least restricted enough that you can sort of reason about them. If these things are not even functions then there's no hope of ever understanding them.
- dep_b 8y agoIt's not exactly the same as railway programming as in Swift it's typically not used to connect functions together but rather to replace separate callbacks with one "result" function and to hard type certain results. One typical case in Objective-C style API's would be: - (void)fetchData whenDone finished: [a block] ifFails failed: [another block] Or: - (void)fetchData whenDone finished: [a block that has nullable Data and nullable error as parameters] Both approaches had problems. The first one uses two result blocks, but you want to do a lot of the same stuff usually, like: * Stop the spinner * Enable the send button again This often lead to repetitive code or a maze of function calls. The second one made you basically duck type for the kind of response it really was. Does it have an error? It probably failed. Does it have data? It probably worked. But what if it has both data and an error (error data?) or simply both values were nil? Brittle code. While I really like the railroad approach the typical problem area Swift is applied to (often web API driven iOS and macOS applications) simply don't lead to these huge chains of functions. I'm definitely going to try it for backend code though.
- nine_k 8y agoYou don't need huge chains. Also, compare it to the interface of Futures.
- bonesss 8y ago> web API driven iOS and macOS applications) simply don't lead to these huge chains of functions I agree you don't see them collected together, as a rule, but if you follow Results<> like this in those domains you see them being used multiple times on the backend before being yeilded to the frontend for further handling, resulting in chains that are just spread out a little bit more. Personally I've started exposing them through JSON at the API level to further extend the systems explicitness and idempotency.
- _pmf_ 8y agoI refuse to see where the difference to exception handling is. My exceptions bubble up to my "sad path" handler.
- Smaug123 8y agoYour phrasing ("I refuse to see…") makes me very apprehensive of trying to explain, but I'll bite. Using exceptions is dealing with errors out-of-band. Using Result types is dealing with errors in-band. In-band is somewhat less flexible because you don't have the power to arbitrarily alter the execution flow of your program. Out-of-band is much easier to reason about by that very token.