3 ms·
> https://philipnilsson.github.io/Badness10k/posts/2017-05-07-escaping-hell-with-monads.html https://philipnilsson.github.io/Badness10k/posts/2017-05-07-... Ev
by nyanpasu64 6y ago
> https://philipnilsson.github.io/Badness10k/posts/2017-05-07-escaping-hell-with-monads.html https://philipnilsson.github.io/Badness10k/posts/2017-05-07-...
Every "monadic solution" prints the same code block without explaining how it would work, the types of the various variables, the semantics of the <- operator. I didn't leave the page with an understanding of how monads achieve these tasks.
- virtue3 6y agoThe article is literally: "Hell thing" Step 1 -> monads step 2 -> ???? step 3 -> profit? This is apparently the monads curse where people that get them can't effing explain them to save their life.
- strbean 6y agoMonads are just monoids in the category of endofunctors! /s The issue is pretty much a language barrier. All these articles talking about benefits of / interesting ways to use monads are written by people who speak the language, assuming the reader speaks the language as well. As with many functional programming topics, the fundamentals aren't incredibly easy to wrap your head around. But if you already understood monads, can you imagine how annoying it would be if every resource, discussion, article, etc. relating to them started with a pages-long introduction on What is a Monad? In Haskell, this is a fundamental topic. Monads are used everywhere. If people always explained how they worked when discussing them, it would be like looking up sorting algorithms and having every algorithm description start with a long-winded explanation of how for loops work.
- infogulch 6y ago> But if you already understood monads, can you imagine how annoying it would be if every resource, discussion, article, etc. relating to them started with a pages-long introduction on What is a Monad? I agree that it would be unreasonable for every article that uses monads to describe what they are. But I don't think it would be unreasonable for every one of them to link to another article that does explain them.
- dllthomas 6y agoWould you say the same about for loops? Depending on your audience, it's totally reasonable to expect they know certain things. Further, figuring out what to recommend isn't always easy, and I'd usually rather authors put their efforts into presenting the material that they have to share.
- dllthomas 6y agoI just skimmed but IIUC, the article is a little more of an in-joke than an explanation and does seem to expect its audience to already understand (or maybe be intrigued enough to learn more from other sources?). That said, I can try to explain here: Haskell has a bit of syntax available called "do notation". You can write Haskell without it, but it makes some things read better (as a matter of common but not universal opinion). There's a simple, purely syntactic translation from do notation into regular application of functions. "Syntactic sugar causes cancer of the semicolon." There are four rules, none of which is complicated, and only two of which are relevant here: First, a single expression is just that expression, nothing magic happens. do expr simply becomes expr Next, the arrows: do x <- m ... more stuff, which might use x ... becomes bind m (\ x -> do ... more stuff, which might use x ... ) or in a few other syntaxes: bind(m, x => do ... more stuff, which might use x ...) (bind m (lambda (x) do ... more stuff, which might use x ...) m.bind(|x| { do ... more stuff, which might use x ...) bind(m, [] (auto x) { do ... more stuff, which might use x ...) That internal do is then expanded recursively. So to translate the whole block that's repeated throughout the article: do a <- getData b <- getMoreData a c <- getMoreData b d <- getEvenMoreData a c print d becomes bind getData (\ a -> do b <- getMoreData a c <- getMoreData b d <- getEvenMoreData a c print d) which becomes bind getData (\ a -> bind (getMoreData a) (\ b -> do c <- getMoreData b d <- getEvenMoreData a c print d)) which then becomes: bind getData (\ a -> bind (getMoreData a) (\ b -> bind (getMoreData b) (\ c -> do d <- getEvenMoreData a c print d))) and then: bind getData (\ a -> bind (getMoreData a) (\ b -> bind (getMoreData b) (\ c -> bind (getEvenMoreData a c) -> (\ d -> do print d)))) and finally bind getData (\ a -> bind (getMoreData a) (\ b -> bind (getMoreData b) (\ c -> bind (getEvenMoreData a c) -> (\ d -> print d)))) Which is "just" a bunch of chained functions combining lambdas. So... why bother? and how does it do so many different things? and how does it know which to do? and what does it even do? The key is that we're overloading "bind", picking the behavior that we want. You could do this in most languages by passing in a choice of function. We could have "do" take a parameter, like do(bind) or in an OO language you might hang bind on the objects involved. We often see this for particular instances - for instance, .then for promises/futures. Haskell does it with a mechanism called "type classes", where you can specify an implementation of an interface for a given type, and the compiler will figure out which implementation to provide. This is very similar to Traits in rust, implicits in Scala, etc. You can usually avoid specifying the types manually because of inference. So in Haskell, Monad is an interface providing two functions: class Monad m where bind :: m a -> (a -> m b) -> m b pure :: a -> m a We've already come across `bind`, which lets us operate "inside" a context in a way that combines the contexts. The other function included, `pure`, takes a value and gives it the minimal possible context. What that means is implied by the "monad laws", which tell us how `bind` and `pure` must interact. For each type that "is a monad", we tell the Haskell compiler how that type implements the interface: // optionality: instance Monad Maybe where bind Nothing _ = Nothing bind (Just x) f = f x pure x = Just x // state: newtype State s a = State (s -> (a, s)) instance Monad State where // pure gives the State action that produces x and leaves the state unchanged pure x = State (\ s -> (x, s)) // bind threads the state through the chained actions bind (State f0) f1 = State (\ s -> let (x, s') = f0 s State f2 = f1 x in f2 s') // lists: instance Monad [] where pure x = [x] // bind for lists is concatMap bind [] _ = [] bind (x:xs) f = f x ++ bind xs f ... that got long. Please ask any questions you're left with :)