17 ms·
Railway Oriented Programming
- metayrnc 3y agoHonesty this whole site is a gold mine. Even if you are not interested in using a functional language, if gives you a different perspective which was very helpful for me. I would recommend other posts as well.
- giraffe_lady 3y agoThe series on building a parser combinator from scratch has been one of the most valuable things I've read and worked through. A lot of concepts and mechanisms from that have been just incredibly useful working with typed languages in a functional style. I still will never know what a monad is tho. https://fsharpforfunandprofit.com/series/understanding-parser-combinators/ https://fsharpforfunandprofit.com/series/understanding-parse...
- evntdrvn 3y agoY’all might enjoy this book by the same author! https://pragprog.com/titles/swdddf/domain-modeling-made-functional/ https://pragprog.com/titles/swdddf/domain-modeling-made-func...
- janislaw 3y agoThis is exactly how LabVIEW implements error handling.
- elteto 3y agoHah! I was going to post this myself. When he overlaid the gray boxes over the tracks I thought “that’s a VI!”. I’d like to point that LabVIEW is not doing any of the fancy type stuff explained here (Either, Maybe, etc). It’s just that VIs (or functions in common parlance) can have multiple inputs and outputs, and LabVIEW’s graphical approach lends itself well to this use case. But LabVIEW’s type system is fairly primitive.
- bandyaboot 3y ago> For example, if I want a recipe for making a loaf of bread, saying “just use flour and an oven” is not very helpful. > Relationship to the Either monad and Kleisli composition Well, sure the former isn’t very helpful, but I at least know what an oven and flour are.
- waynesonfire 3y agoat least you know what you don't know.
- Cthulhu_ 3y agoAnd you know how to find out what you don't know ...that said, functional programming and the university level words associated with it intimidate me and quickly go over my head. Mainly because I haven't got a practical application for it, and they're more abstract concepts. A railway switch? Sure, I can understand that. A monad? What? Why are you making up words? (tongue in cheek, I've had a stint of Scala so I can sort of apply some of these things in practice. I just don't have the vocabulary, educational background, or practical applications)
- thewix 3y agoThis is probably the biggest problem with FP, and I love FP, and use fp-ts which is marred in category theory. It took me a while to understand concepts that are really quite simple but explained very academically. Remember, [Monada are just monoids in the category of endofunctors](https://stackoverflow.com/questions/3870088/a-monad-is-just-a-monoid-in-the-category-of-endofunctors-whats-the-problem https://stackoverflow.com/questions/3870088/a-monad-is-just-...) That being said, I love FP and am a better programmer today because of it.
- greydius 3y agoYes, I also was born knowing about ovens and flour. If these other things were important we'd be born knowing them as well.
- bruce343434 3y agoSo basically, monadic error handling, that is, using `Optional<T>` and `Error<T>` and just `map`ping everything (which is a nop on the nil and error variants)
- jgilias 3y agoWhich is how things actually work in non-FP languages too. In those you’re _always_ within Exception<T> and MaybeNull<T> monads. It’s just implicit and we tend to just cross our fingers and hope it’s all well.
- mrkeen 3y agoTo drive the point home a little a little harder: I don't use Haskell because it has a representation for values which might exist (all languages have that), I use it because it has a representation for values which do exist.
- tome 3y agoA little harder still: It does not have a representation for values which do not exist! (see also: "make invalid states unrepresentable")
- sillysaurusx 3y agoHi HN! I know this is an inappropriate comment but I hope you have a good day today. Just feeling grateful to the universe. Hoping that anyone feeling down will catch a break, and everyone feeling excited will enjoy your weekend. Much love to all of you. (And if you ever need someone to vent to about random stuff, DMs are always open — please remember to prioritize yourselves above other life considerations once in awhile. It’s not selfish.) Anyway, back to your regularly scheduled programming. Radio announcer voice: you’re listening to smooth jazz^W^Whacker news…
- zogrodea 3y agoThank you! Hope you have a good day too!
- xyzal 3y agoSomehow, this comment lifted my spirits. Thank you!
- revskill 3y agoSorry it doesn't help the poor souls who's debugging useEffect issues.
- adhvaryu 3y agoVery gloomy weather in the United Kingdom today. Thanks for this!
- samsquire 3y agoThis is the kind of uplifted positivity of light and goodness that I like to see on the web. I think society, television, films and books focus on the opposite of utopia and negative things and dark things. But they ignore all the blessings and positive things. LOVE, gratitude and kindness and light, and faith are what matter and what we should be focused on. Why would you embrace something that is darkness when you could embrace goodness and light? Your attention should be on good things 100% of the time. Reacting to a bad situation or something negative, in a good, positive way. Not a negative way.
- deleted 3y ago
- tuukkah 3y agoFor TLDR, see the slide 151 of 154. PDF of the slides (here the summary is slide 137 of 141): https://github.com/swlaschin/RailwayOrientedProgramming/blob/master/Railway_Oriented_Programming_Slideshare.pdf https://github.com/swlaschin/RailwayOrientedProgramming/blob...
- ctenb 3y agoA few years ago, I took some time to do a write-up about this programming style, specifically for C#: https://chtenb.dev/?page=rop-cs-1 https://chtenb.dev/?page=rop-cs-1
- dzonga 3y agoreally helpful posts. however, I wanted to save them as pdf's but your website's layout is broken. when you select print - it only wants to show wants on the screen not everythign on the page.
- ctenb 3y agoThanks, I haven't considered printing as a usecase indeed, I could make that work at some point with some css tweaks I reckon. But until then, the raw html can be found here: https://github.com/chtenb/chtenb.github.io/tree/master/docs/blog https://github.com/chtenb/chtenb.github.io/tree/master/docs/... They can probably be converted to pdf just fine, or you could just use the html
- xupybd 3y agoThere was an interesting tweet recently on doing this with a computation expression. https://twitter.com/ijrussell/status/1691752042539237413?s=20 https://twitter.com/ijrussell/status/1691752042539237413?s=2...
- FrustratedMonky 3y agoYes. Think a lot of people think this is just using <Option>'s. But really in Fsharp, you can build your own 'Monad's called Computation Expressions, and for error handling can pass along different information about the errors. It doesn't have to be "Only" an <Option>, it can be <MySpecialOption>.
- iraqmtpizza 3y ago[flagged]
- louthy 3y ago> Railway Oriented Programming This term needs to die. I have lost count of how many times people have asked me, on my language-ext [1] issues pages, if the library supports "Railway Oriented Programming". The documentation is clear, it is an FP library with many monads, functors, etc. Inventing new terminology for existing concepts just creates even more confusion. Creating completely new terminology for existing concepts just because you don't like the words is utterly baffling. Learn them. If you're going to teach monads then by all means use railways as an analogy, but don't call it "Railway Oriented Programming", you are just misleading the reader and not helping them communicate with others in the FP community. Did anyone know what 'polymorphism' was when they first encountered OOP? No, they learned what it meant. Learn what 'monad' means, learn what 'functor' means, learn what 'applicative' means. You'll be enlightened. OOP people: not everything needs to be 'oriented' ;-) [1] https://github.com/louthy/language-ext/ https://github.com/louthy/language-ext/
- FrustratedMonky 3y agoIt is a handy metaphor. Like all handy metaphor's, it helps people conceptualize an idea. That doesn't mean it is a strict technical term, you can break down all metaphors if you want. "He is the light of my life." "NO, he is not a 'Light', he is a human, read a biology book". LOL. Just realized you are Drax? Drax is posting on HN?
- louthy 3y agoSure it's a metaphor and as I state, there's nothing wrong with using the railway analogy, but renaming 'monads' as 'Railway Oriented Programming' is having the effect that newbies to FP-land think that's what _programming-with-monads_ is _called_. Then they try to have discussions with others in FP-land and realise they don't have the lexicon to talk about FP because they've been taught some spurious terminology. This is a monad tutorial, it is over 150 slides worth of explanation (probably the largest 'Yet Another Monad Tutorial' I've seen yet), there's no reason to leave the reader thinking they've learned some new concept called 'Railway Oriented Programming'.
- 3y ago
- bongobingo1 3y agoDoes this argument hold: This pushes the error handling away from the call site, and that is a bad thing. Being that the caller knows best how to handle the errors, so it should instead of passing them down. (Full disclosure, I have seen this talk and read Wlaschins book, but not in a long time, so maybe he did cover this and I forgot.) Eg given: validate and-then update-db and-then send-email What about when validate fails? The error is returned to the caller, which is maybe suitable. What about when update-db fails? Should there be a retry? Try another service? Requeue? Tell the user? What about if the send-email fails? Dont you end up with (except with more unwrapping and sub branches) validate and-then update-db and-then send-email and-if (Ok, ok) or-if (ValidationError, notify-user) or-if (DBError, retry-in-5 or-then retry-remote or-then notify-user) or-if (EmailFailed, requeue-email and ok) Maybe thats ok?
- Akronymus 3y agoYou can wrap the error types into a discriminated union and then check for retry-able errors, and retry if its one of those, otherwise, propagate the error. Or if you don't need to do special handling for any errors, you can just propagate the whole error and let the caller handle it. ROP definitely isn't useful in all cases, but it has served me quite well in many by simplifying error handling when I don't care at the callee what error occured.
- cjfd 3y agoI think it should be pretty obvious that for instance retry logic should be near the place in the code where the first attempt is made. The best case for gathering all errors in a 'railway' is when they can be handled uniformly. E.g., if we want to not retry things but instead report a descriptive error message to the user. Note that also that most error handling logic that should be near the calling place might also fail in more global ways that are then best handled using the railway/exception pattern.
- piaste 3y agoLow-level and high-level error handling can coexist. It's no different than having a specialized try/catch inside a larger try/catch. You may, for example, choose to handle very fast transient errors inside your `send-email` function. If you manage to connect in under $acceptable-time, return to the happy path*, otherwise return to the caller. The only hard and fast rule is that any handling requiring human intervention should definitely be wrapped up and passed up. * but do log a warning, regardless of whether you're just doing classic impure logging, collecting the logs as part of the success value, or using a writer monad.
- samsquire 3y agoI see a lot of correlations between parsing train tracks and BNF and error handling and state machines. I wrote a bit about it here https://github.com/samsquire/ideas5#252-happy-path-state-machines-parsing-and-how-it-relates-to-control-flow-of-state-machines-train-track-rendering https://github.com/samsquire/ideas5#252-happy-path-state-mac... The problem: how do you re-join the control flow paths to the happy path.
- rawoke083600 3y agoI love that this comes up every few years, it's really a briljant article and talk. What is also interesting, I'm usually at a "different place" in my tech-journey, in terms of skill or philosophy every time I re-read.
- el_oni 3y agoI really like things like this. There is a python talk called "so you want to be a python expert" and i think i watched it every 6 months for the first few years of my journey and each time i realised i got more and more of it. I'm sure if i were to watch it again there is probably still something now that i could get from it.
- FrustratedMonky 3y agoWOW. Great to simply see this being brought up and discussed at all. I spent a year reading through this site and working through examples. One of best F# resources. Just the 'concepts' discussed on this site has helped me with 'functional' thinking in all languages. If this is getting attention now (because this is old site). Does this mean F# is gaining traction? And to the subject, do programmers in other languages use 'railway' style error handling. Like Rust?
- fsloth 3y ago"do programmers in other languages use 'railway' style error handling." I use that in C++. Basically I define a templated return type that can be parametrized using the return type T and the specific enumeration definining the result condition. Although you can do with std::pair<MyValue, std::string> In a pinch, where you return any error statements in the string and exit early if the string is non-empty (when all functions return types like that you can then bubble up the error statement up to top level).
- Archelaos 3y ago> Does this mean F# is gaining traction? I have a strong background in C# for desktop applications (with a database server backend). Over the years my coding style in C# becomes more and more functional (such as using a lot of Linq, immutable classes, return types that potentially include detailed error infos, etc.). What me holds back from F# is that I typically spent most of the time in tailoring the desktop front end. Here F# does not seem to bring any benefit. As far as I know, all available production ready desktop frontends are not native F#, but plain old object-oriented code, mostly in C#, that are bound to F# by just an intermediate layer of glue-code. As soon as I need an even remotely sophisticated user interface customisation, I am back in object-oriented land and should do it best directly in C# again. So in my particular case, F# needs a native UI framework before it could gain traction for me.
- FrustratedMonky 3y agoAgree. I think the UI is the biggest block. I like ELM Architecture, and ELMISH for F#. For F#, all the UI options seem like hobby projects. They are Great. Great concepts. But, can take some time to wrap head around these concepts if you are trained from birth that a 'button' is an 'object'. F# really need some of these UI's like ELMISH to be built in, like tooling in VStudio, or part of a release from MS to give them more weight.
- jammycakes 3y agoThe author followed up this post with another one a few years later titled "Against Railway Oriented Programming": https://fsharpforfunandprofit.com/posts/against-railway-oriented-programming/ https://fsharpforfunandprofit.com/posts/against-railway-orie... Railway-oriented programming is an interesting concept and it does have its use cases, but it does need to come with a massive health warning. I've often seen it used in practice to reinvent exception handling badly, and this is something I consider particularly ill advised because exceptions, when understood and used correctly, provide a much cleaner and more effective way of handling error conditions in most cases. The thing about exceptions is that in most cases, they make the safe option the default. An error condition is an indication that your code can not do what its specification says that it does, and in that case you need to stop what you are doing, because to continue regardless means that your code will be operating under assumptions that are incorrect, potentially corrupting data. Error conditions can happen for a wide variety of reasons, many of which you do not anticipate and can not plan for, and in those cases the only safe option is to clean up if necessary and then propagate the error up to the caller. Exceptions do this automatically for you by default (you need to explicitly override it with a try/catch block) but alternative approaches, such as railway oriented programming, require you to add in a whole lot of extra boilerplate code that is easy to forget and easy to get wrong. If you can't handle the error condition on the way up the call stack, you would then log it at the top level and report a generic error to the user. Having said that I see two particular use cases for this kind of technique. The first is situations where you need to handle specific, well defined and anticipated errors right at the point at which they occur. Validation is one example that comes to mind; another example is where you are trying to fetch a file or database record that does not exist. The second is situations where exception handling is not available for whatever reason. Asynchronous code using promises (for example with jQuery) are pretty much an exact implementation of railway oriented programming, but since modern JavaScript now has async/await, we can now use exception handling in these scenarios.
- fsloth 3y ago"The first is situations where you need to handle specific, well defined and anticipated errors right at the point at which they occur" Barring system level errors can you give an example of an error state that's not like that, that would then rather merit an exception? I would like to understand your point of view, is it due to the nature of the problem, or the constraints of runtime that make exceptions preferable. In the C++ code I need to write, we can 1. check data for error conditions in the beginning 2. if we fail the error check, let application crash 3. use the found error state to debug and fix the error in the initial checking code. The data my code needs to process is fairly straightforward - data abiding by some known CAD data format or given geometric topology, so the error conditions are "quite easy" to tackle in the sense that there is an understanding what correct data looks like in the first place.
- barrkel 3y agoYes, this is a monad tutorial. It's concrete and specific and doesn't attempt to generalize, but it's explaining an application of monads. And yes, the approach described, if followed naively and robotically, reinvents a clumsy kind of checked exceptions. As another comment https://news.ycombinator.com/item?id=37173435 https://news.ycombinator.com/item?id=37173435 points out, after learning and before serious application, it should be leavened with caveats: https://fsharpforfunandprofit.com/posts/against-railway-oriented-programming/ https://fsharpforfunandprofit.com/posts/against-railway-orie...
- deleted 3y ago[deleted]
- mpweiher 3y agoWhat works even better is using dataflow instead of call/return, as the problem largely goes away by itself. With call/return, you have to return something, so if you have nothing to return because of an error, you have to return that, or both the error and the normal return value (Go). This pollutes the happy path. With this polymorphic container through which you thread the remainder of the processing, you make the problem a little nicer, but it is still there. With dataflow, you simply don't send anything to the next filter stage, so your happy path is completely unaffected. You then send the error to some kind of standard error output, which can often be centralised for your application. Sounds to good to be true, but used it in Wunderlist, for example, and it worked like a charm. Oh, and the same technique that works for when you don't have a value (errors) works the same when you don't have a value yet (async).
- rco8786 3y agoIs this fundamentally different than returning the data or throwing an exception to be caught elsewhere?
- golergka 3y agoType system doesn't force you to handle exceptions, they're spooky action at a distance.
- naasking 3y agoSome do, like checked exceptions.
- Karellen 3y agoAlternatively, they're just reducing boilerplate. Yes, anything might return an error. Do we need to really write `or error` or whatever everywhere that all we're going to do is pass the error immediately back up the stack? One way to improve this would be to have some kind of way of declaring the atypical case where you have a function that can never throw an exception... which is a feature of some exception-based programming languages.
- 1234543y24624 3y ago[dead]
- el_oni 3y agoElixir has a nice take on this with the `with` keyword/macro with {:ok, file_handle} <- File.open(filename), {:ok, contents} <- IO.read(file_handle), {:ok, parsed} <- MyModule.parse(contents) do {:ok, parsed} end what this does is run the functions in order top to bottom, and if the return value from each function doesn't match with what is on the left, it returns early with the thing that didn't match, otherwise it continues. This means you don't need to write each function to take a tuple of {:ok, value} and another clause to take {:error, reason}, you can just write your functions to take the value they care about and let pattern matching in the with block to take care of error propagation. so if File.open returns {:error, reason} then IO.read never executes and the result of the with is {:error, reason} It essentially means you can program the happy path and let the caller match on the sad paths (if they want to)
- bjourne 3y agoWhat is the advantage of that over exceptions? And what if IO.read fails? Who closes the file handle?
- ahoka 3y agoUnless you are writing oldschool Java, then exceptions are not typechecked.
- el_oni 3y agoThe file handle is the PID of the process that opened the file, it monitors the process that asked for the file to be opened. and if that process goes down the file will be closed. You can also add an else in the with so it looks like with {:ok, fh} <- File.open(filename), {:ok, contents} <- IO.read(fh) do contents else {:error, reason} -> File.close(fh) {:error, reason} # this will return after the file has been closed end or maybe you would prefer to open the file, and pass that into the with and either way close the file. I just used IO as an example because they return nice {:ok, x} or {:error, reason} tuples, but this works with any pattern.
- RedShift1 3y agoSo in Java this would translate into always returning an Optional<T> from your functions?
- the_af 3y agoThis is Either, not Optional. Optional would lose the error.
- teddyh 3y agoNot to be confused with Railroad Syntax Diagrams: <https://en.wikipedia.org/w/index.php?title=Syntax_diagram&oldid=1156833954#/media/File:Syntax-diagram-example.png https://en.wikipedia.org/w/index.php?title=Syntax_diagram&ol...>
- dgivney 3y agoOr Rail, the programming language https://esolangs.org/wiki/Rail https://esolangs.org/wiki/Rail
- gsuuon 3y agoThis site has been the best programming education site I've encountered in terms of real, pragmatic concepts taught. My favorite is the concept of making invalid states unrepresentable[1] which I try to apply now regardless of language, though some make it easier than others. [1] https://fsharpforfunandprofit.com/posts/designing-with-types-making-illegal-states-unrepresentable/ https://fsharpforfunandprofit.com/posts/designing-with-types...
- beders 3y agoUsing this style is infectious like any other monad, so all your business will look like this. Stop reading if you are ok with this. That said, let's talk about validation errors vs. exceptions. Exceptions are - as the name implies - unexpected errors that unwind the stack to whoever catches them. Along the way any necessary cleanup is handled automatically. Exceptions are for cases where the error occurring is outside the scope of the business function. I/O errors come to mind, OOM etc. Now if you want to VALIDATE your data, you can do this without resorting to exception handlers or monads: You turn the outcome to your functional pipeline into data. Have validation errors? collect them in a set. Look at them after the pipeline completes. Chances are you don't want to fail on the first one. Need to do side-effects in a pipeline? Don't. Instead describe the effect with data, run them later. Need to back out early out of the pipeline? Split the pipeline at that location, handle the result, stuff it into another pipeline. Or use (shudder) a chain of interceptors that can decide if the pipeline should be continued along its happy path. If your language supports it, use pattern matching to make that decision. Note that the decision making to skip parts of the pipeline or the rest of it, is done outside your business function (which improves the chances of them being reusable because they will only be concerned with a pure data transformation and don't need to know about machinations like Maybe/Either or some such to signal things to the caller).
- paddy_m 3y agoI implemented a data processing API for customers to write convert and validate their unstructured tabular data in a defined way. For each column a user could specify/override one of 5 functions. cast: (str|null) -> T compute: T -> T validate: (T) -> Message[] // doesn't modify the value serialize: (T) -> str // primarily used so dates can be formatted Each function was guarded by a try/catch block. This allowed users to write simple functions and not have an edge case blow up all of processing. It ended up working pretty well. I don't think it was quite railway oriented programming, more a carefully thought out framework.
- dang 3y agoRelated: Railway Oriented Programming - https://news.ycombinator.com/item?id=31404643 https://news.ycombinator.com/item?id=31404643 - May 2022 (1 comment) Railway-Oriented Programming (2015) - https://news.ycombinator.com/item?id=17337155 https://news.ycombinator.com/item?id=17337155 - June 2018 (160 comments) Railway oriented programming - https://news.ycombinator.com/item?id=11955917 https://news.ycombinator.com/item?id=11955917 - June 2016 (57 comments) Railway Oriented Programming - https://news.ycombinator.com/item?id=9166943 https://news.ycombinator.com/item?id=9166943 - March 2015 (6 comments) Railway-oriented programming - https://news.ycombinator.com/item?id=7887134 https://news.ycombinator.com/item?id=7887134 - June 2014 (52 comments) Also: What is railway oriented programming? (2020) - https://news.ycombinator.com/item?id=34245639 https://news.ycombinator.com/item?id=34245639 - Jan 2023 (17 comments) Railway Oriented Programming in Elixir (2015) - https://news.ycombinator.com/item?id=11958578 https://news.ycombinator.com/item?id=11958578 - June 2016 (1 comment)
- fuzzythinker 3y agoIn the JS/typescript world, neverthrow [1] may be the closest library for this. If anyone has good experience with something else, please reply. Not sure if neverthrow is the right choice after replacing all Errors in a 16k LOC engine library with it. It did added more "noise" to the code. Would a custom bespoke code/library add less noise? I'm not sure. [1] https://github.com/supermacro/neverthrow https://github.com/supermacro/neverthrow
- lincpa 3y ago[dead]
- archibaldJ 3y agoisn't this pretty much the Either monad except that it's more "linear" and less "wrapped"? (since we are not following the monadic bind but a bind that does continuation by case) edit: I just saw author's post note. nice. I think this actually makes a good monad intro.
- scottlawson 3y agoThis reminds me of the LabVIEW error model where you form a train out of error signal