Nice pictures, but I just don't get it. Sure, it's very easy for me to understand monads mathematically, what kind of structure are they. I don't need any pictures for that, the definition suffices for me. But that doesn't tell me what are they good for . We invent things for a reason, and I don't see the reason. Now I know the reason, it's been told to me many times (some way to wrap I/O while preserving functional…
I tried covering it (and showing there's a better way) in this talk: https://www.youtube.com/watch?v=449j7oKQVkc
Functors, Applicatives, and Monads in Pictures (2013)
31–40 of 56 posts
Re: Functors, Applicatives, and Monads in Pictures (2013)
#32Earlier quoted context omitted.
The reason that the Monad structure is interesting, in my view, is that it's simple and neatly captures the notion of a "computation" that we can compose in different ways. The core useful operator for monads in Haskell is >>=. It has the following type: m a -> (a -> m b) -> m b If you squint, this is like function application with a few extra m's thrown in. Here's normal function application for comparison: a -> (a…
> Well, the problem in Haskell is that it's a language of evaluating expressions at heart: executing effects makes no sense any more than it would in arithmetic. You know what else doesn't make sense in arithmetic? Computation. I think it is important to wonder how come mathematical definitions of computation didn't come about until the 20th century[1] even though math has had most of its big breakthroughs (to date)…
So the answer to "why are monads useful?" is that if for whatever reason (e.g. it makes programming easier for you) you decide to model computation as Haskell does, as LC and "equational reasoning", monads are invaluable as an elegant description of various computations that wouldn't otherwise be convenient to express. Once you use other approaches, monads (as a cognitive abstraction) quickly lose their usefulness and appeal.
[1]: Which is one common definition of computation and one of the "original three" definitions, but not a very good or powerful description of computation as it lacks good measures of complexity (it also lacks introspection which makes it less powerful than the Universal Turing Machine, but that is no a very common capability in programming languages other than Lisp).
[2]: In fact, "computational math" doesn't care in the least that 2+2 equals 4. All it cares about is the computation 2+2 -computes-> 4 (or 4 -computes-> 2+2, the latter is completely separate from and unrelated to the former). It is those various approaches of verifying computation that care about the relationship between 2+2=4 and 2+2 -computes-> 4, but those can (and should?) be completely separate from the programming language.
[3]: That split between some computations and others is exactly why a `sleep` operation in Haskell can be implemented to either be "effectful" (using monads) or "pure" (using only "pure" computation). That artificial distinction doesn't really have a rigorous definition in computation theory; rather, the definition of "pure" is a language-level design choice. For example, in Koka a divergent computation (i.e. infinite sleep) is an effect, but not a finite sleep; in Haskell, neither is an effect. In both Haskell and Koka, a stack overflow is not an effect and neither is memory allocation. OTOH, in Haskell spawning a new thread is considered an effect (which seems really weird to me). I hope that at this point it is clear that the distinction between "pure" and effect is arbitrary and not a fundamental concept of computation (aside, perhaps, from IO).
Re: Functors, Applicatives, and Monads in Pictures (2013)
#33Nice pictures, but I just don't get it. Sure, it's very easy for me to understand monads mathematically, what kind of structure are they. I don't need any pictures for that, the definition suffices for me. But that doesn't tell me what are they good for . We invent things for a reason, and I don't see the reason. Now I know the reason, it's been told to me many times (some way to wrap I/O while preserving functional…
You kind of just have to use them to “get it”, but here’s an analogy: if you write a data structure, you naturally want to make it “iterable” in your language’s usual way, so you can take advantage of a library of generic functions for working with iterable things. Same goes for monads. If we have N data types and M functions, instead of writing N×M implementations, we can write just N monad instances + M generic imp…
Bear with me for a bit, because I still don't get it (although I'm not the OP, I share the same doubts). In OO terms, if you have N types and M functions, with M different behaviours (function code), you aggregate the N types into an inheritance tree that makes you write 1xM functions (one function against the ancestor of the N types). If you have 2M behaviours, you aggregate the types in two different inheritance hierarchies and then write 2M functions. What expressiveness advantages do monads provide against this OO scenario?
Re: Functors, Applicatives, and Monads in Pictures (2013)
#34Earlier quoted context omitted.
Because you write functions like this in mathematics as well. What we usually see is something like this: f(x) = y But if you look at it carefully the function f is a mapping of the domain x to the range y (the arrow is f), written like so: x -> y Of course, the domain, being a set of all permissible values, is the type. So we write the type instead: type1 -> type2 Everything in Haskell is a function, and pure functi…
I don't understand one thing about this notation: fun :: A -> B -> C -> D can be bracketed in a bunch of different ways - fun1 :: (A -> B) -> (C -> D) fun2 :: A -> (B -> C -> D) etc. and don't these all mean different things? fun1 would take a function and return a function, whereas fun2 takes an A and returns a curried function of B,C -> D.
Re: Functors, Applicatives, and Monads in Pictures (2013)
#35Earlier quoted context omitted.
The reason that the Monad structure is interesting, in my view, is that it's simple and neatly captures the notion of a "computation" that we can compose in different ways. The core useful operator for monads in Haskell is >>=. It has the following type: m a -> (a -> m b) -> m b If you squint, this is like function application with a few extra m's thrown in. Here's normal function application for comparison: a -> (a…
I appreciate your explanation but again it fails to actually show it being useful outside of the context of working around Haskell's strict type system. What the OP and myself would like to see is concrete examples why this is a better approach than the way something would be done without explicitly caring about monads (I say explicitly because I know there is a tendency to say that some given structure is a monad an…
When I see a bunch of Maybe monads, for example, I can instantly know that the programmer's intent was maybe to not just have a type safe null and avoid null pointer exceptions, but also to possibly make use of Maybe's toxicity.
What I mean by toxicity is you know how in some languages you have "not a number" aka NaN? A NaN times / divided by / added to / subtracted from anything is a NaN, that means that if you have a complex formula, if you throw a NaN in there anyway you get back a NaN. Maybe works the exact same way but with all functions not just arithmetic operators. That's what I mean by its toxicity.
Without Maybe you'd have to explicitly apply that logic everywhere you'd otherwise apply a monadic "join." The reader would then have to study your code much more carefully to understand that intent, and probably look at it with some skepticism that the toxicity works perfectly every time or is in every place you might expect. And of course not everyone values brevity and expressiveness, but when done right it makes code more readable not less, and Monads done right facilitate that.
Maybe's toxicity is just an example of pretty much any "magic" you can build into a Monad. You could for example have a Future Monad that will handle asynchronous control flow for you.
As a side matter, it's not just working around Haskell's strict type system that make Monads a good fit for IO, but also Haskell's non-strict execution.. monads help things happen in the right order which is crucial for IO. That's kind of neat when you think about - the bridge between the mathematical rigor of pure functional code and real world devices that make computing useful. Functional purity makes code easier to reason about and less error-prone.
Re: Functors, Applicatives, and Monads in Pictures (2013)
#36Off-topic, but why are Haskell's function signatures written like this? function_name :: Param1Type -> Param2Type -> Param3Type -> ReturnType To me that just makes no sense. There's no way to logically "read" it. The natural way would be "function_name converts a Param1Type into a Param2Type and then ... what?! Is this a chain of functions?" Why not have something sensible like this? function_name :: Param1Type, Para…
Say we have a function with signature
f :: Int -> Int -> Int -> Int
Let's say this just add those Ints together.Now let's create another function by partially applying the initial function: f1 = f 42
f1 signature will be
f1 :: Int -> Int -> Int
Kind of "consuming" the signature. Repeat this until using up all the parameters. You end up with a function with no parameters, which map to a single Int.You could say that f3 -> Int (note, not a valid notation for the sake of example). No need for a different symbol.
Re: Functors, Applicatives, and Monads in Pictures (2013)
#37Re: Functors, Applicatives, and Monads in Pictures (2013)
#38Earlier quoted context omitted.
The reason that the Monad structure is interesting, in my view, is that it's simple and neatly captures the notion of a "computation" that we can compose in different ways. The core useful operator for monads in Haskell is >>=. It has the following type: m a -> (a -> m b) -> m b If you squint, this is like function application with a few extra m's thrown in. Here's normal function application for comparison: a -> (a…
> Well, the problem in Haskell is that it's a language of evaluating expressions at heart: executing effects makes no sense any more than it would in arithmetic. You know what else doesn't make sense in arithmetic? Computation. I think it is important to wonder how come mathematical definitions of computation didn't come about until the 20th century[1] even though math has had most of its big breakthroughs (to date)…
Including notions of computation makes equational theories hairier. That might be very much what you want, and there are plenty of ways in which mathematics talks about (2 + 2) being unequal to (4). Typically you instead replace equality with weaker notions going all the way down to the weakest (most intricate) examples of actual algorithmic computation.
So why avoid this kind of stuff? Because you want to pick a "cognitive abstraction", in your terms, which is most useful. It turns out that for a lot of purposes inclusion of computation is too hairy and obscures what people care about. It also turns out that in many cases what they care about is somewhat unaffected by the computational substrate involved.
I really can't say I agree that your beef with equational reasoning makes much sense to me, but I think it's just because you're shooting too broadly. You can easily have problems with the particular choice of granularity of equating taken by the Haskell community and language... but that's just a much smaller thing.
Re: Functors, Applicatives, and Monads in Pictures (2013)
#39Earlier quoted context omitted.
> Well, the problem in Haskell is that it's a language of evaluating expressions at heart: executing effects makes no sense any more than it would in arithmetic. You know what else doesn't make sense in arithmetic? Computation. I think it is important to wonder how come mathematical definitions of computation didn't come about until the 20th century[1] even though math has had most of its big breakthroughs (to date)…
Computation in mathematics is well represented in the constructive and intuitionistic camps. It was thoroughly examined in the 1930s via the invention of things like lambda calculus and turing machines. It was also subsequently rejected by most mathematicians not because it fails to be equational but just because they decided that they don't really care. Including notions of computation makes equational theories hair…
That's what I said. Only in the 20th century for something that might seem to an outsider to be fundamental to mathematics (after all, mathematicians sometimes compute, right?)
> So why avoid this kind of stuff? Because you want to pick a "cognitive abstraction", in your terms, which is most useful.
Oh, absolutely, but there are many ways and many forms of reconciling "equational" mathematics and computational mathematics, and PFP is just one of them. As I've shown in another comment, Prolog takes this much further than Haskell because it's truly equational (or, rather, relational), while Haskell gladly allows a direct (one-way) computation if it defines it as pure, i.e. in Haskell 2+2=>4 is still as different from 4=>2+2 as it is in Java. Other ways are the plain procedural style (which simply lets you name and parameterize a computation), and my favorite (although I don't know how practical), temporal logic used in, e.g. Esterel (and TLA+).
My beef is not with equational reasoning as a concept, and not even with Haskell's flavor of it. Certainly "equationality" in programming languages is a spectrum (as computation is inherently non-equational), and finding useful points on it is a matter for lots of empirical study. My "beef" (if that's what you want to call it) is with the claim that Haskell's particular design is somehow more fundamental or mathematical than other designs, and, to bring us back to the topic of the discussion, that (cognitive) monads are somehow essential. The reality is that there are many (good) justifications for Haskell's design, but they've been made by people to match certain human aesthetics and certain human goals[1] and they don't have any transcendent mathematical justification. They are just a point in the design space. Similarly, monads are only (pretty much) essential in Haskell given its design. Some people try to present Haskell (and monads) as the language (and abstraction) God, nay, better -- Math -- has given us, and that's just wrong. It's just a language like many others, with its own design choices. Besides, everybody knows God uses Lisp. My other "beef" is with the unjustified belief that a certain way of expressing an algorithm is automatically cognitively optimal just by virtue of it being equational (be it either in the Haskell or the Prolog sense).
[1]: One of them is a particular balance between correctness and specification effort, which, again, is also arbitrary and defined by the limitations of HM type inference.
P.S.
To clarify something I wrote in another comment, Haskell's definition of purity, while fundamentally arbitrary (from a computational theory point-of-view), makes a lot of sense if you've chosen that particular design.
Re: Functors, Applicatives, and Monads in Pictures (2013)
#40Earlier quoted context omitted.
Computation in mathematics is well represented in the constructive and intuitionistic camps. It was thoroughly examined in the 1930s via the invention of things like lambda calculus and turing machines. It was also subsequently rejected by most mathematicians not because it fails to be equational but just because they decided that they don't really care. Including notions of computation makes equational theories hair…
> It was thoroughly examined in the 1930s That's what I said. Only in the 20th century for something that might seem to an outsider to be fundamental to mathematics (after all, mathematicians sometimes compute, right?) > So why avoid this kind of stuff? Because you want to pick a "cognitive abstraction", in your terms, which is most useful. Oh, absolutely, but there are many ways and many forms of reconciling "equati…
I think as far as monads go I quite like your distinction drawn elsewhere between abstractions for mathematical sake and abstractions for human cognitive sake. I think ultimately it has a lot to do with someone's goals as a programmer and person as to which abstractions they should hold and realize.
I think, personally, monads have a very high power-to-weight ratio here, even outside of Haskell, but I've never been one to argue that one cannot live without them. Or even shouldn't.