6 ms·
I'd say you're now almost guilt-edged required to provide us with your simple explanation here. But i'll warn you, i've been nicely flummoxed by competing 'simp
by theophrastus 11y ago
I'd say you're now almost guilt-edged required to provide us with your simple explanation here. But i'll warn you, i've been nicely flummoxed by competing 'simple' explanations in this forum previously: "A Monad is an object whose methods return monads.", "They're simply a highly specific way of chaining operations together.", "You know how jQuery methods can string out multiple methods with periods? That's what monads are.", "Monads are merely an analogy to control flow what abstract data types are to data."
- sportanova 11y agoA monad has two operations - bind and return - and they're used for chaining
- theophrastus 11y agoNow i've got N+1 'simple' explanations which I can use to confuse me [wink]. "bind" and "return" you say? so they're quite like a socket library?
- sportanova 11y agoha well it's more like a burrito that has two operations
- im3w1l 11y agoThose names are not very good. return takes a value and "monadifies" it. If you have function that takes a normal argument and emits a monadified value (e.g. int -> monad string). Then bind can transform your function so that it takes a monadified argument (e.g. monad int -> monad string) instead. return and bind satisfy three laws: 1. If you have function that takes a normal argument and emits a monadified value. You could apply it to some argument right. Or you could monadify the argument with return, and then use bind on the function so it will accept the monadified argument. These two ways of using the function give the same result. 2. Remember how return takes an argument, and emits a monadified value? That means we can bind it! The second law states that the resulting function is just the identity for monadified stuff. 3. Lets say you have two functions f (e.g. float -> monad string) and g (e.g. string -> monad int) from normal argument to monadified value. We could bind them both and then compose them. g_ = bind(g) f_ = bind(f) g_f_(x) = g_(f_(x)) Or we could imagine instead binding only the second function, then composing the functions, and then monadifying the composition. g_ = bind(g) g_f(x) = g_(f(x)) g_f_ = bind(g_f) Law 3 states that these two procedures yield the same g_f_
- im3w1l 11y agoOk let me give you an example of a monad! The Maybe monad. In this case a monadified value either has a normal value, or it has a null. Ok, so for the operations. "return" should monadify a value. In this case it does this by creating a Maybe that has that value (Maybes that have null can not be created with return. They must be created in some other way.). The bind operation creates a new function that takes a Maybe value from one that doesn't. If the Maybe has a normal value then the function is run like normal. But if the Maybe has a null, the function is not run. Instead null is emitted directly. Why is this a good thing? It means you can chain together many failure prone operations, and you only have to check for null at the end, not in the middle. So it works a little bit like exceptions. Let's verify the laws: 1. We have some value, and some function that could take that value, and either emit a result, or a null. If we monadify our value, we get a Maybe that has that value. If we bind our function, and then run it with our Maybe, what happens? Well we know our Maybe is not null, because we just put in a totally legit value. So because of how we defined our bind, the function will run just like normal. 2. Ok so lets bind "return" and see what happens. If the input is a maybe that has null, then we never run "return" but just emit null. Consistent with identity! If the input is a maybe with a normal value, then we run "return" with that value. Return creates a maybe that has that value. So we get out what we put in, it works! 3. There are a number of cases we have to go through but let's do them one by one. First construction: a) x is null. f_ skips f and just emits null. g_ skips g and just emits null. b) x is non-null. first we put x in f_. Since x is not null we just apply f to the normal value. Now f can either emit a normal value or null. b1) f emits null. g_ is skipped result is null. b2) If result is not null, g is run with the normal result, and result of overall computation is g(f(normal argument)) Second construction: a) x is null. g_f_ skips g_f and just emits null. Same result as before b) x is not null. g_f is run with value in x. To run g_f we first run f. Two cases. f emits normal value or null. b1) f emits null. g_ of null is null. So if value in x causes f to emit null we have null just as before. b2) f does not emit null. g_ just runs g with normal result. So if value in x does not cause f to emit null, result of computation is g(f(normal argument)) So both constructions work the same, they continue running until problem arises.
- theophrastus 11y agoHaving offered this kindly explanation, would you say that this continues in the holy pursuit of a 'simple explanation' of what a monad is? Here's an intermediate question triggered on your explanation: what is the byte level nature of your "monadification"? you say one has a function which converts a floating point to a "monad string", a string value to a "monad int" (a raven into a 'monad writing desk')? exactly what bytes characterize a "monad string" from a string? a "monad int" from a plain old int? it's perhaps a structure? maybe it has stack pointers among those structural elements?
- belovedeagle 11y agoSo this is going to sound snarky and/or stupid at first, but: Monads are functors with a 'join' ("flatten") operation. A functor is a type parameterized by another type[1], say 'a', with a function 'fmap' taking a function 'a -> b', yielding the original object except that its type is now parameterized by 'b'[2]. A monad's 'join' operation takes 'f (f a)' onto 'f a'. On top of this, there are certain laws which must be obeyed. The functor laws amount to saying that 'fmap' can't change anything about the object (including structure) which doesn't depend on the parameter 'a', but must change everything which does depend on 'a'. The monad laws amount to saying that 'join' has a certain associative structure[3], and also that there is a left- and right-identity 'return: a -> f a'. [1] a la Java generics, for those not into FP [2] i.e., fmap has type 'forall a b. (a -> b) -> (f a -> f b)' [3] Explaining exactly what this associative structure is in these terms is tricky, since 'join' as described is unary. Here's where you have to introduce the function 'bind', '(\f -> join . fmap f)': 'bind g . bind f' === 'bind (bind g . f)'. But it's really not essential; the most intuitive consequence is that Haskell's 'do' notation works like you want it to.
- tel 11y agoYou need "return" too. Can't get that from merely being a functor.
- belovedeagle 11y ago> and also that there is a left- and right-identity 'return: a -> f a' RTF, uh, Comment. The point being that I think it's best to understand return in the context of the more important operation join. You might wonder, "well why don't functors have a generic constructor", and (among other answers to that) the reason is that it doesn't serve any purpose in the functor laws. But return does serve a purpose for the monad laws, namely, being an identity of sorts.
- anon4 11y agoHaskell is a pure language. Purity is a concept that is absolutely bizarre to the real world, where adding two numbers together results in tons of side effects (load into registers, oops, have to load cache lines, alter memory, raise the ambient temperature, etc. etc.). However, the idea of pure functions is useful. So we make our basic program blocks from pure functions that don't "do" stuff, but just transform values. However, we must chain these into useful programs somehow. Monad is the type name chosen in Haskell for the construct that does this. It's kind of simple if you work it backwards from the syntax. You want to be able to say do a <- readInt b <- readInt add a b print The first two can fail, rely on side effects and the last one is purely a side effect. The only pure function is add, which can't work if a or b aren't there. Monads serve as a way to represent the real-world-ness without it seeping into the add function. You can think of each line as a closure getting the next value from the previous line. So readInt returns a Maybe Int. If it returns a Nothing, the next closure is simply skipped. You can insert a check after the add, if you want to, to see if you got Nothing and alert the user, etc. However, the basic idea is that the program does not crash with a NullPointerException. We know what to do if we get Nothing. The add function doesn't need to concern itself with that. Hence, the Monad. It wraps a real world concern around a type (it has a type constructor). You must be able to make one from a concrete value that is unburdened with exceptional state (it has the unit function). You must be able to call pure functions on these monadic values without the side state seeping into the function (it has the bind function). Edit: the reason you don't see Monads in other languages is because they're not essential to them. You can do stuff in C without Monads. To Haskell, Monads are like the Higgs boson. It's how you bind stuff together.
- dllthomas 11y ago"You must be able to call pure functions on these monadic values without the side state seeping into the function (it has the bind function)." That sounds more like fmap.
- nemothekid 11y agoYour explanation makes sense, but I don't see how are Monads different from Rust's Option Type (or Java's Optional Type?), or any other nullable type?
- flippinburgers 11y agohttps://www.youtube.com/watch?v=yvwaxYro4FU https://www.youtube.com/watch?v=yvwaxYro4FU I've struggled with blog posts about monads countless times. Maybe I just find it easier to listen to some explanations rather than read them.
- gimboland 11y agos/guilt/gilt/
- tel 11y agoLet's try this one. "A monad" isn't a thing, but we can ask what it takes for something to "have monad-nature" or "be a monad". For this to occur our something must be three things, a triple (T, mu, eta) The names are all meaningless so for the moment they just get short letters. T must be what's known as a Functor. For PLs really, though, it's even more specific. It's an "endofunctor on the type category of your language". What this means is simple * For any (static) type `a` in your language, `T a` is also a static type. * For any function `f` from type `a` to type `b`, `T f` is a function from type `T a` to `T b`. If your language doesn't have static types then you can approximate this by pretending that it does. Also note that `T f` is not usually the syntax used, but it'll do for now. Now to make a monad we take any choice of Functor `T` and give it two operations. `eta` takes values in type `a` to values in type `T a` while `mu` takes values in type `T (T a)` to values in type `T a`. In other words they give you a "layer manipulation toolkit". They must follow laws as well, but these laws are all "common sense laws" which allow you to think of values in the following types: `a`, `T a`, `T (T a)`, `T (T (T a))`, `T (T (T (T a)))`, ... as all being the same as values with just one layer: `T a`. This is the "flatten" idea. And there you have it! Anything at all which can be regarded as equivalent to one of these triples following this design and laws is a monad! --- So the real question is why should someone care? The answer is simple. Lots of things have monad-nature in a typed language. By recognizing their common nature we can (a) see them in a new light full of perhaps previously unknown similarities, (b) share terminology lightening the burden of how to use them, (c) write generic operations which work over any monad and expect them to work with each specific one in a similar way, (d) introduce new syntactic sugar which is built entirely from (mu, eta) and expect it to work similarly for every thing with monadic-nature, (e) begin to form theories and impressions of "what it means to be a monad". The reasons (a-d) show up all the time once you recognize this pattern since monads are really common. Reason (e) is what everyone wants to hear about but it's hard to talk about without being super arm-wavey. But I'll try. In order to provide the operation `mu :: T (T a) -> T a` the type `T a` must be able to "internalize itself" without losing too much information. In order to follow the laws, one bit of important information is an idea of sequence or nesting—but one unique to each particular monadic triple. In this way monads are a very, very, very general way of talking about sequencing or nesting. What makes thins interesting is that you can see imperative languages as being about sequencing or nesting as well. If you have a listing of statements { X ; Y ; Z } then you can see { Y ; Z } as being nested in the execution context that X created and { Z } being nested in the execution context that { X ; Y } created. This would maybe make you think that imperative languages have monad nature and indeed they do. This leads to an interesting study of questions like "For some given imperative language, what is a pure-functional representation of the monad representing it?" which sometimes has interesting answers. What's even more interesting is that one example of reason (d) above is to introduce a syntax sugar which makes any monad look a bit like an imperative language. This leads to an interesting study of questions like "What does the imperative language for some monad X feel like?" So if you really like imperative languages and recognize that there's a larger design space here that most have any familiarity with then monads are a good thing to keep an eye out for.