6 ms·
You seem to be disagreeing with the OP about how to explain Monads, yet the link provided by the OP is referred to as a "non-explanation". It is true that Mona
by SeanDav 6y ago
You seem to be disagreeing with the OP about how to explain Monads, yet the link provided by the OP is referred to as a "non-explanation".
It is true that Monads are a pain point in Haskell. They are extremely powerful but not easy to grok, even by some very smart programmers.
Quote from main article about Monads:
> "Monads.....Despite many attempts though, I don’t think we have yet found the best way of explaining what they actually are. "
and
> " The Wikipedia article has quite a good example of non-explanation."
- rednum 6y agoTo rephrase what I mean: I disagree with the idea that we can explain all technical concepts in non-technical way, which OP seems to support. I don't think we can get much better with teaching monads than providing actual formal definition plus motivating examples. I simply believe monad is too abstract and we can't have a nice analogy similar to the one OP provides ("Functors can be broadly explained in terms of containers..."). In my opinion the "platonic ideal of a monad tutorial" is more or less the (semi) formal definition of monad + some motivating examples. This is also something that cannot be understood if you don't have proper background (which is understanding simpler typeclasses like monoid and functor). I also would not call monads a pain point to be fair.
- mumblemumble 6y agoI would turn that on its head: Provide several motivating examples, together with concrete solutions. Once you've provided a couple of those, then you can call attention to the commonalities that unite all those solutions. At which point, the abstract explanation is almost trivial. For example: My understanding of monads ultimately came together rather quickly and painlessly. After I had initially just given up on the whole thing. And then I watched Scott Wlaschin's Railway Oriented Programming talk (https://fsharpforfunandprofit.com/rop/ https://fsharpforfunandprofit.com/rop/). And then I learned what's really going on with LINQ's query DSL and SelectMany. And had someone show me how to easily compose Optional types without having to forever be explicitly interrogating whether they have a value or not. With all that under my belt, the pattern suddenly became rather obvious. What did not help me in any way was any article that explicitly sets out to try and explain monads. They are all, as far as I'm aware, guilty of tring to sow the seeds before tilling the earth.
- lkuty 6y agoIt was quite similar for me. Scott Wlaschin's Railway Oriented Programming and its other articles on its site (and in particular https://fsharpforfunandprofit.com/series/map-and-bind-and-apply-oh-my.html https://fsharpforfunandprofit.com/series/map-and-bind-and-ap...) helped me greatly too. I liked its idea of an "elevated world". Another helpful source was/is https://wiki.haskell.org/All_About_Monads https://wiki.haskell.org/All_About_Monads. But I thought a lot about them for the past few years. I tried also to make a detour through category theory but it did not help except for getting a lot of books in my library. Another helpful article was Philip Wadler's "Monads for Functional Programming" (http://homepages.inf.ed.ac.uk/wadler/papers/marktoberdorf/baastad.pdf http://homepages.inf.ed.ac.uk/wadler/papers/marktoberdorf/ba...).
- jackcviers3 6y agoA monad is an abstract interface for sequencing side effecting operations in a way that guarantees referential transparency. They do this by hiding the extraction of the value contained in the monad and passing that value to a user supplied lambda that takes a value of the type of the value contained in the monad and returning a new monad containing a different type. Thus, you cannot do step one after step two, because step one must extract the value from its containing monad. In a modern OOP language, like Java, this is analogous to using for each in to build a new list from a given input list. Due to the abstraction, a monad can do this without exposing the internal loop state to the outside world, a single bind expression safe to refactor, whereas moving the for loop requires moving two statements in order to be safe to move.
- gavinray 6y agoWhat is "referential transparency"? > "by hiding the extraction of the value contained in the monad and passing that value to a user supplied lambda that takes a value of the type of the value contained in the monad and returning a new monad containing a different type" I'm not sure I follow. Is this like saying I have a function: let myFunc = (arg1: SomeType): NewType => {} And it takes SomeType, and returns NewType? What does it mean to "hide the extraction of the value"?
- gmfawcett 6y agoI wrote a Reddit post a while back that might help. (The parent comment has since been deleted -- the poster was asking whether using monads in Haskell was kind of like "dependency injection", i.e., passing in all the information that a function might need, as arguments to the function.) https://www.reddit.com/r/programming/comments/593ud7/a_taste_of_haskell/d962888/ https://www.reddit.com/r/programming/comments/593ud7/a_taste...
- louthy 6y ago> I'm not sure I follow Imagine you have a context (the monad), which contains a value of type `a`... > "by hiding the extraction of the value contained in the monad... Means, you don't need to care about how we get the value out of the context, it will just be done for you. That's part of the monad's job. Each different type of monad may do it differently (a List will do it many times, an Option will do it zero or one times, Async promises to give you a value, etc. ). > "... and passing that value to a user supplied lambda that takes a value of the type of the value contained in the monad ..." So, we have a function called `bind`, which is part of the interface for monads ... it takes the original context (monad) as well as a lambda as arguments. Below is the signature: `m a` which is the original monad value, (a → m b) is the lambda, and `m b` is the return value, which is a monad with its contextual value type changed from `a` to `b` bind :: m a → (a → m b) → m b Where you see `m a` and `m b`, they're generic types which are parameterisable on both the inner and outer type, the inner type `a` is fairly usual, but the outer-type `m` is a higher-kind and can also be parameterised, `m a` could become `List a` (which is like List<A> in C# or Java). So, if you squint, you might be able to see that the `a` comes from within the `m a` context, gets passed to the lambda, which needs an `a` argument and returns an `m b`, then that `m b` is returned as the final result. Each type of monad has its own implementation of `bind`. So, for a List the bind operation looks like this: bind :: List a → (a → List b) → List b And so you should see that for Lists each `a` will be extracted from the first argument (so we'll have zero or many `a` values), each `a` is passed to the lambda which will return a `List b` (and so we have zero or many `List b` values), and those lists of `b` will be concatenated to give a flattened result of `List b`. And so, that's the behaviour if the List monad. Each monad will have its own rules, but must follow the signature of `bind`. > "...and returning a new monad containing a different type..." This just means the `a` turns to a `b` (but the `m` stays the same). Essentially it means you can do some kind of mapping of the contextual value(s) to return a new type. So, if you were to map Ints to Strings the bind signature would look like so: bind :: List Int → (Int → List String) → List String In practice you can see how the lambdas returning `m b` allow for chaining of computations. The code below visits the items in two lists (listA and listB) and sums them. The lambdas close over each other to create a context for the `return` at the end of the process. Which is very close to how scoping works in imperative languages. bind listA (\a → bind listB (\b → return (a + b))) The key to any monad is its bind function, that's where its behaviour is. But fundamentally, it's a contract, or interface which forces ordering of computation... the values returned by the lambda (which are monads themselves), clearly can't be evaluated until you evaluate the first argument to get the `a`. And so, `m a` must run before `m b` ... combine that with `do` notation and you'll get something that looks and feels a lot like imperative code, but all of the side-effects are encapsulated and declared, and all of the logic is pure (referentially transparent). The example above with `do` notation is: a ← listA b ← listB return (a + b) Which is essentially equivalent to a nested for loop without the ceremony.