Railway-oriented programming
fsharpforfunandprofit.com
fsharpforfunandprofit.com
If you can forgive the snark there, the point to take home is that this entire pattern of programming can be nicely summarized through some very high-order topics like Monads. That's one "why" for "why should I learn what 'monad' means".
A very similar essay could be written for non-deterministic programming ("it's 'just' the List or Logic monad"), for backtracking parser combinators ("they're 'just' the State + List monad"), or for state threads themselves ("they're 'just' the State monad"). Each of these programming patterns is neatly summarized by the "obvious" behaviors of a simple type.
One of the powers of Haskell is that it lets you abstract over patterns like this and talk about them inside your language. That's why Haskell has such a fetish with Monads—it's as though GoF had been formally encoded into the language. This notion comes up a lot under the name "Monad" but it might also be "Profunctor" or "Applicative" or "Category".
I really love this essay because it really goes through all the details. Oftentimes an advanced Haskell/ML/F# programmer might summarize a large topic as "it's just the X monad" anticipating that someone familiar with this kind of programming can reverse engineer almost all of the meaning from that plain statement. This is typically true, but certainly expects a lot of experience from the listener.
This essay unpacks all of that meaning and presents it in easily digestible bits. That's great technical writing.
1. The "success, but..." scenario. Sometimes you have different types of failures to handle and this style conflates all of them into "failure". For example, I want to WARN about something my caller; everything is OK, but there is something to be known (The call went fine, but the file was not there so I created it). Should information about that value be passed on the failure pipe? If I do so I lose the ability to do simple checks like "if failure.empty?". A third pipe for "warn"? And another one for "info"? Mhhh...
2. There are different way to fail. There are cases in which you really should not go on with any computation, there are cases in which can go on, just note that you are in "danger" mode (ENOSPC).
3. What about transient failures? Should "Network just went down" be treated like "Server returned HTTP 404".
4. Sometimes you want to just ignore all the errors because you are prototyping something and you do not want a lot of boilerplate code laying around. And definitely you do not want a catch all that hides the errors.
I haven't tried it though.
We're trying to figure it out at the moment in Snowplow. We process raw events and either accumulate errors on Failure or compose an enriched event on Success. The trouble is, we would like to switch some of these errors to warnings, so we would need to push both the warnings and the enriched event through on Success.
I think we need to switch:
Validation[NonEmptyList[ProcessingMessage], EnrichedEvent]
to something like:
Validation[NonEmptyList[ProcessingMessage], Tuple2[List[ProcessingMessage], EnrichedEvent]]
It feels a bit clunky though...
No, it doesn't. I'm not sure where you get that, because even in this example "Failure" carries an error message. That's a string for didactic purposes, it can be anything you need it to be.
"2. There are different way to fail. There are cases in which you really should not go on with any computation, there are cases in which can go on, just note that you are in "danger" mode (ENOSPC)."
Here is where avoiding the word "monad" probably does not play to the author's favor. Monadic computations are able to examine the results of computations, so nothing stops you from
case somethingThatMayFail arg1 arg2 of
Right x -> do
... -- carry on the computation
Left x -> -- fail out however you're going to fail
Which I believe also handles the rest of your questions... the pattern conveniently propagates errors when you don't want to treat them specially, but you can still dig in and examine them if you need to. Whatever you want to do, you can do, because it's just a program like anything else. For 3, the answer is "just write it the way you want it"... there isn't really an "answer" because that's not a problem in the first place, any more than it would be in C. And for 4, again, just do that then. Nothing's stopping you.Not that that's necessarily a bad thing; I think this article does a great job of teaching the reader about monads without them knowing it. The subject of the post -- error handling in a purely-functional context -- is usually my go-to example for explaining monads.
Explained here: http://fsharpforfunandprofit.com/about/#banned
"So this is obviously a catamorphism. This evidently a job for Kleisli arrows!".
And a completely different thing is to construct a solution step by step, using familiar ideas, and then show that it can be seen as a monad, then give a brief explanation of why monads are useful and what motivates them. Which is exactly what the extremely didactic Learn you a Haskell site does (seriously: look at their example for the Writer Monad. It doesn't get more didactic and step-by-step than that)
By avoiding the correct technical terms, they are:
- Losing precision.
- Failing to generalize the concept.
- Underestimating their audience's ability to understand new concepts when property explained.
The last one in particular kills me. I for one do not assume Visual Basic programmers are incapable or unwilling to learn new things.
(Incidentally, for people who've found themselves thrust into this unfortunate situation, a chapter titled A Fistful of Monads probably isn't likely to help much.)
To that end, I'd even go so far as to say your worries about that last bullet point underestimate the audience's ability to understand new concepts when properly explained much worse than the author did.
I am not a psychologist, but isn't part of the definition of priming that it happens before? How would saying something afterwards harm your ability to previously understand something? Well, I guess I could see how that'd be possible, but I'm not sure that's priming anymore.
I disagree I'm underestimating anyone. I'm advocating teaching people. If you never tell them about monads, they can never realize they've been using them on their own, simply because no-one is ever going to mention monads to them. It just doesn't work like that; at some point someone must tell you about them (a blog or a paper counts in this context).
How can someone have gotten themselves into a situation where the very mention of the word 'monad' generates feelings of frustration and simultaneously never have heard about monads?
> When we first talked about functors, we saw that they were a useful concept for values that can be mapped over. Then, we took that concept one step further by introducing applicative functors, which allow us to view values of certain data types as values with contexts and use normal functions on those values while preserving the meaning of those contexts.
> In this chapter, we'll learn about monads, which are just beefed up applicative functors, much like applicative functors are only beefed up functors
> Monads are a natural extension of applicative functors and with them we're concerned with this: if you have a value with a context, m a, how do you apply to it a function that takes a normal a and returns a value with a context? That is, how do you apply a function of type a -> m b to a value of type m a?
Some people might find this easy to understand, but I guess that many people won't. I don't think that it is because they are stupid, rather that most people are concrete thinkers rather than abstract thinkers.
You would expect programmers to be more comfortable with abstraction than the general population, but even so, in my experience, the level of abstraction that mathematicians use is a level too far for most programmers.
Why should a concept be generalized when you are just learning it? Would you complain that a elementary book on arithmetic fails to mention that the integers form a ring? And that addition is just a special case of a monoid? What advantage is there to this kind of premature generalization?
Of course, you cannot expect to jump to any point in the middle of a book or article explaining something you don't know and expect to understand every bit of terminology. Without knowing C++ or OOP, would you jump to the middle of an "OOP with C++" blog and expect to understand the terminology? Ok, answer quickly: what's a method? What's a template? What's the difference between class and instance? What does "static" do? (I hope you get the idea).
There is nothing particularly difficult about Haskell, Monads or FP at the level that is taught in LYAH. Seriously. Sure, there is hard to read code in real Haskell projects (much as it happens with C++, by the way), but not in this tutorial.
This argument about "many people won't understand new or abstract terminology" is pretty weak and I'm having trouble with people buying it. Programming IS abstract, but we all learned it. We all were new to crazy new terms like "overloading" and "operator precedence" and it didn't deter us. So why on Earth are we deciding that "Monad" and "Functor" are too difficult?
See also: curse of the monad
It's true for me as it took me several tutorials to get the intuition for it and be able to revisit category theory tutorials (which at first seemed unwilling to give examples).
http://www.i-programmer.info/news/167-javascript/5207-crockf...
"In addition to it begin useful, it is also cursed and the curse of the monad is that once you get the epiphany, once you understand - "oh that's what it is" - you lose the ability to explain it to anybody."
val bind : ('a -> Result<'b,'c>) -> Result<'a,'c> -> Result<'b,'c>
Because that's precisely the signature of bind (=<<) in Haskell. Although it looks nicer in Haskell. :PBut I do respect the author's desire to avoid getting too mathy and hifalutin with the theoretical stuff, because you can really go overboard down that path...
bind :: (a -> Either b c) -> Either a c -> Either b c
So really you just save yourself a couple of angle brackets.That being said, the author does introduce the (>>=) function, but just doesn't say monad because that can turn people off really quickly.
bind :: (a -> m b) -> m a -> m b
Which is exactly the signature of (=<<). So you don't just get a cleaner syntax; you now have the ability to use `bind` with any monad (Maybe, List, IO, State...).DPs in object-oriented programming are also an abstract concept that is difficult to grasp at first, but most senior programmers have already gone through it and succeeded; so liking monads to them can inspire an initial sense of confidence that will be useful when learning the concept, and provides a useful frame to understand the purpose of using monads in functional code (which unfortunately is missing in most monad tutorials).
I don't know what about monads feels scary to some people. They aren't actually that hard. There's stuff from C++ that scares the hell out of me, but I've seldom seen people arguing "let's not blog about this C++ feature, because it may scare readers away".
Haskell's utterly ridiculous amount of syntactic sugar really doesn't help, and I figure its laziness probably doesn't either.
First the author introduces a simple problem which you could have thought of without knowing anything about Haskell. Then he writes a naive, step-by-step solution without knowledge of Monads, Monoids or whatnot (ok, maybe later chapters assume you've read the previous ones). Then, he shows how the solution he arrived at maps to Monad/Monoid/whatever. He also shows how the solution can be made "more general" than the particular problem you started attempting to solve.
Give it a chance and you'll see what I mean. There's no black magic there.
Also, you can completely bypass do-notation and you'll see there's nothing exceptional about monads.
I want to think in terms of actual, concrete pieces of data. Types are just definitions for shapes of data. I can't think at the level of "X takes a Y", I think at the level of "the x function takes the result of the y function". Haskell is obviously not the language for me.
I'm working on a tutorial to take people from zero to functional programming thinking and understanding the intuition, and I need people that are interested in learning Haskell but are currently experiencing difficulties grasping the concepts in order to understand which things are the common obstacles.
If you're interested, please email me (my profile should have my email, if not just answer if interested and we'll figure out a way).
Thanks!
Do you want a step-by-step guide for implementing a monad, or using a monad?
The first thing to realize is that Monad is a design pattern. Like any design pattern, it's more applicable to some goals than others.
Could you clarify a bit?
"right, I have these two functions, one looks like `x = 1` and the next looks like `incr y = y+1`, what do I call on what to combine them with a monad, then how do I define a monad which only calls the latter if the former returns 1?"
The best interpretation I can come to is that you want some plumbing that will hide an implicit check that the value being passed along is 1? This would not fit "monad" well - for reasons I can get into if that's actually what you're asking.
There are a whole bunch of us who are more pragmatic than theoretical. We care more about "that approach can simplify my code" than we care about "that's another instance of a monad".
sigfpe's blog has the "original" tutorial: http://blog.sigfpe.com/2006/08/you-could-have-invented-monad...
Warning though, you do have to make an account to view the video.
Was a pretty good talk :)
getData().then(validate).then(update).then(send, handleError)
This reproduces the code flow of the first figure of the article.Once I started using the async.auto() it completely changed my way of thinking about how code worked, and I've started coding in a much more functional style. Node.js makes this very easy.
The author's primary example uses a "layout" I've never seen on a railway (http://fsharpforfunandprofit.com/assets/img/Recipe_RailwaySw...). Railway switches are expensive, so you never see multiple switches going the same direction between a pair of tracks. If there are two consecutive switches (and going say from left to right), one shunts from track A to track B and the other shunts from track B to track A. When space is limited, a cross-over might be used by these are even more expensive (an provide additional gaps where derailments can happen).
valid = true
if valid and not check1(): valid = false
if valid and not check2(): valid = false
...
if not check1(): return false
if not check2(): return false
...
When we are enriching raw events, we try a few separate validations/enrichments and then either accumulate errors or compose a valid enriched event.
[1] https://gist.github.com/justjoheinz/9184859 [2] https://github.com/snowplow/snowplow/blob/master/3-enrich/sc...
Maybe Go's somewhat verbose way of handling errors isn't so bad after all. It seems easier than teaching your coworkers a bunch of combinators, no matter how you try to sugar-coat it.
I'd be curious to know how the author came up with the idea of representing these ideas with tracks, as it is a very fortunate perspective.