18 ms·
If monads are the solution, what is the problem?
- foldr 8y agoThe more I've learned about Haskell, the more I've come to realize that Haskell's approach to IO really has very little to do with Monads. Fundamentally, Haskell implementations sequence IO actions using hidden magic. (GHC uses dummy world variables.) As it happens, the operations for constructing IO actions from pure values and for sequencing IO actions form a Monad. But that's not really any more interesting than the fact that e.g. (\x -> [x]) and concatMap form a Monad.
- deleted 8y ago[deleted]
- newen 8y agoYep, you can alternatively use unique types (https://en.wikipedia.org/wiki/Uniqueness_type https://en.wikipedia.org/wiki/Uniqueness_type) to do IO while still maintaining referential transparency. Clean, Mercury, and Idris does this.
- theoh 8y agoReading this: https://wiki.haskell.org/IO_inside https://wiki.haskell.org/IO_inside did not make me come to that conclusion. The IO monad is noncommutative, and thus controls sequencing of operations, because of data dependencies, more or less hidden. A "Haskell wizard" tried to tell me the ordering of operations was implemented through low-level magic a few weeks ago. The fact is that whatever ultimate form GHC might compile your code to, the sequencing is expressible in the semantics of Haskell code. Getting sequencing into the semantics is one of the problems which monads solve.
- Tarean 8y agoIt's worth noting that the IO monad implementation is very much compiler magic and can't be done in library level haskell. Evaluation order without IO/ST is undefined in Haskell. This is similar to write barriers, you need a magic primop to specify external data dependencies. Without that magic the compiler may inline everything, drop the 'uselss' state token and happily move your buffer freeing before the allocation - or drop it entirely. Alternatively you could mark every function as noinline and end up with fantastically slow code.
- millstone 8y agoData dependencies don't viably smuggle operation sequencing into Haskell. Consider e.g.: fma a b c = (a * b) + c The sum depends on the product but this doesn't sequence anything: it may be compiled into a single fma machine instruction. More generally inlining, CSE, etc. can break apparent data dependencies. It's simpler and cleaner to merely state that the Haskell runtime is written to execute IO actions in sequence.
- solidsnack9000 8y agoThe data dependencies in how monads desugar does force the evaluation order, because the result of the first one is passed to the following one and so on and so forth. The desugaring turns each monadic action into a lambda with one argument.
- JadeNB 8y agoI think that millstone (https://news.ycombinator.com/item?id=17646166 https://news.ycombinator.com/item?id=17646166 )'s point was that the sense in which desugaring of monads forces evaluation order is the same as the sense in which fma a b c = (a * b) + c forces evaluation order: you need the result of `a * b` in `fma` in order to add `c` to it. One can argue whether both of these, or neither of these, have the evaluation order forced, but I don't think that it is reasonable to argue that one does and the other doesn't. (There is a slight artefact from the fact that addition is commutative, so that it makes no difference whether you compute `c` first and then add `a * b` to it or vice versa, but I think the idea should remain.)
- theoh 8y agoTo discuss this properly, you need to consider the evaluation strategy, which is part of the semantics of the language. Strict, lazy, call-by-need: things do get evaluated in a prescribed order, and sequencing using monads (predictably) involves strictness, meaning that a function argument is evaluated before the function. These details of evaluation order are important to know, and they are not something to be argued about: they are well-specified and the compiler doesn't just casually make lazy code into strict code or vice versa. That would cause chaos. And what instruction set Haskell is being compiled to is totally irrelevant to this level of the language's semantics. https://en.wikipedia.org/wiki/Evaluation_strategy https://en.wikipedia.org/wiki/Evaluation_strategy
- richard_todd 8y agoI’m not sure I follow, since the implementation details of any given monad have very little to do with monads. It’s the fact that IO/Continuations/Etc can be just another monad that was interesting when it was discovered. It’s cleaner to have 1 pattern/framework for N domains than to have N for N.
- foldr 8y ago>since the implementation details of any given monad have very little to do with monads Well, yes, that's the point. You don't need to understand anything about monads to understand how the implementation of >>= for IO forces the correct order of side effects.
- ekr 8y agoActually, that is not entirely true. The monad bind operator introduces a data dependency which is responsible for the sequencing itself, not the actual implementation of the bind (i.e. it is a result of the type of the bind). But I do agree with the general idea that Haskell as a language is still a leaky abstraction, you still need to understand a lot of behind-the-scenes stuff, like how the RTS interacts with the OS ABI, how concurrency is implemented, the consequences of laziness and so on. Just knowing the denotational semantics of the language is not enough.
- nine_k 8y agoI suppose that knowing only denotational semantics would be sufficient only for programs consisting of pure functions. E.g. a batch job that receives input parameters and returns the result via the minimal runtime-provided means would be an example.
- foldr 8y ago>The monad bind operator introduces a data dependency which is responsible for the sequencing itself, not the actual implementation of the bind (i.e. it is a result of the type of the bind) No, there is nothing in the type of >>= which guarantees sequencing of side effects. So for example: a >>= (\b -> c) There is nothing in the type of >>= that guarantees that a will be completely evaluated before c is evaluated. I suspect this misconception is what makes many people think that Haskell's way of handling IO has something crucially to do with monads.
- mbid 8y agoYou're describing my pet peeve with research in programming languages, or more generally use of abstractions in CS. What you're writing makes sense: Understand the structure of your problem, then think of similar problems you've seen before, finally try to abstract away details that are irrelevant for the solutions in each case. If you're lucky, all of these problems are an instance of one general one, and need to be solved only once. In your example, it's that IO and [] are both monads and thus share some structure. What's often happening in PL theory, though, is that people start off with axioms/syntax and then mutilate the actual problem until it fits the syntax (still badly). That's how you get ridiculous stuff like "everything is an object" or "everything is a file". In PL theory, people will usually first make up some syntax/theory and then search for models. If physicists worked the same way, they'd write down random formulae and would then go out to find phenomena described by them. You'd probably read this comment on a cave wall.
- JoshCole 8y agoAxiomatic formulations of a problem do not have the same goals as abstractions. They can appear similar on the surface, but one has to do with the laying of a foundation and the other is about problem simplification. One is about allowing great leaps in understanding after a great deal of derivation, the other about the reduction of work via problem equivalence. Criticizing someone for using an axiomatic formulation rather than a proper abstraction in programming language theory is like criticizing an architect for using pencil in laying his foundation on paper rather the much more appropriate cement at the construction site. You don't get everything is an X because of axiomatic formulation. You get it for the same sort of reason that instead of spending ages describing the configurations of atoms in front of you, you throw all that out and call it a screen. Is everything still atoms? Yes! But we can call it a screen and that makes things massively easier to reason about and so we do it, because we like to reason well. But there are still benefits to thinking the other way, thinking from the very foundations. They are just different benefits, which is why we choose to think of problems from more than one perspective.
- mbid 8y agoI didn't mean to critize all axiomatization but the concrete axiomatizations arrived at by PL research. In my opinion, one should start with a thorough understanding of the problem domain, and then try to come up with a syntax that allows expression of your intuition as faithfully as possible. Form follows function. Instead, I feel like PL research is often too focused on the concrete syntax considered, thus function follows form. Case in point: Building models for the syntax at hand is often an afterthought, done only to prove consistency. I'd argue that one should start with as precise an understanding of the intended models as possible, and then come with up with an axiomatization (thus, syntax) for the intended semantics. For example, the notion of elementary topos has been invented because its creators wanted to capture the way Grothendieck toposes kind of behave like constructive sets. This I find very useful, and also the axiomatizations of elementary toposes. On the other hand, Martin-Löf type theory didn't have a formal semantics at first, then an erroneous one, and finally ~20 years later a kind of acceptable one. And its category of models is... not really interesting. Except for categories of assemblies, I don't know of a single model of ML type theory that's not also an elementary topos. And the interesting thing about assemblies is that they can be turned into objects of the associated topos... so yeah.
- tel 8y agoThis, I feel, is the case with all monads. Even "free monads" are just an interesting name for an AST with embedded host-language functions. Monad is interesting in retrospect. After you "get" X, Y, and Z you come to realize that they're all monads and then maybe you get some mileage out of the mental compression.
- JadeNB 8y agoThis is a crucial point. There’s almost never value in using any abstraction just once; the value comes when you learn to recognise it and apply previously learned lessons in new situations.
- thanatos_dem 8y ago> The incomprehension of the newbies is matched in intensity only by the smugness of the experts. Oh good. A write up that’s aware of how unapproachable monads seem. Maybe this will actually be a good, plain-English breakdown. Four paragraphs later: > Monads, as a mathematical concept, can be always used as a part of the semantic model, but whether they transpire into the type system of the object language is a matter of design choice. Oh... well, never mind.
- Analemma_ 8y ago"Monads are just monoids in the category of endofunctors, what's the problem?"
- nicoburns 8y ago> Oh good. A write up that’s aware of how unapproachable nomads seem. Normally I don't comment on typos (there are more important things in life!), but this one made me chuckle :)
- thanatos_dem 8y agoSorry, fixed. That’s what I get for using my phone to comment on technical things :)
- rhencke 8y agoThe best practical description of monads I've heard, with apologies as I do not remember the quote's author: What if, in C++, the semicolon was an operator you could overload?
- willtim 8y agoI've heard Phil Wadler use this description. Why is it useful? To implement Async/Await as a library instead of a language change. Mainstream languages like C# are looking more and more like a collection of DSLs and less like a general purpose language (!).
- edflsafoiewq 8y ago
- lmm 8y ago> Monads in Haskell are used as a mechanism for scheduling evaluation, thus turning a language with a hard-to-predict evaluation strategy into a language with predictable, sequential, interactions. This makes it possible to add interactive computations such as state and input-output to the language, noting that benign (non-interactive) computations are already a part of the language, transparent to the type system. That's a very operational/imperative perspective, and to my mind is putting the cart before the horse. To me the whole value of monads is that they let you talk about things like state and input-output in a declarative way, using plain old functions and values, rather than having to use these awkward/confusing concepts of "evaluation" and "scheduling".
- alipang 8y agoThere is a point to it though - continuations, for instance, have monadic structure - but at the cost of introducing a strict evaluation order. There is other work related to the linear logic that does what Monads do - provide an abstract syntax - but working better with laziness, so it's not exactly wrong to give Monads an operational perspective. There are many perspectives one can use to understand Monads. The operational one is valid - and useful for many beginners. Of course, it's not the only way to look at them, as you point out.
- a-saleh 8y agoWhat is this linear-logic thing that does what monads do? I haven't yet grokked linear-logic (apart from the advert, that you might want to use it to reason about resource constraints), so I would like to read about this :)
- drb91 8y agoI think the commenter is referring to the process of declaring an order for executing a compute graph. With CSP languages this is established by the order in which the statements appear and reference each other, and computation is explicit: all previous statements in a given process have finished executing at any given point. This is in contrast to Haskell that uses monads to declare the computation graph (well, functions, but monads are under discussion here). In this world, data is computed lazily as needed. The questions you ask about the code center around functions and their dependencies, not necessarily computation order. A task that is trivial to express with on paradigm might be non-trivial to express on the other: haskell is excellent about expressing lazy computation and side effects, whereas a CSP language will offer easy reasoning about the specific and correct order of execution. Note I am not a PL expert, just an enthusiast. My diction may be off for the domain.
- platz 8y agoUnfortunately, the Haskell wiki is not a great place to point beginners. The wiki's growth was rather... organic.
- mattdeboard 8y ago> Some of the words above, especially the meandering second sentence, seem to be written to baffle rather than inform. Ironic. The message he's referring to is the only explanation for what a monad is that has made any sense to me.
- danidiaz 8y ago> The essence of monad is thus separation of composition timeline from the composed computation's execution timeline I also feel that it makes sense. Haskell prefers expressions to statements. When needed, statement-like things are constructed from expressions.
- solidsnack9000 8y agoMonads are a way to model effects -- to mark a computation as using IO (or not), as being parallel (or not), as having exceptions (or not). Monads in Haskell are used as a mechanism for scheduling evaluation... This is only partially true. Any language that tries to be fully declarative -- which is to say, that tries to make all effects "part of the value" (and in consequence, part of the type) -- faces the same problems as Haskell. Idris, a strict language, uses monads to model effects. The C language doesn't make effects part of the type signature, but C program verifiers do add effects modeling. This can be with regards to aliasing (which writes/reads could affect another pointer?), termination or timing. It's the underlying interest in program verification that drives the use of monads in Haskell, in part because it's program verification that drives the interest in Haskell to begin with.
- egillie 8y agoI can’t upvote this enough. I always wish monads has been described to me this way from the get-go, it would have saved me a lot of time.
- tathougies 8y agoAfter many years of Haskell, I was playing around in C the other day for fun. I'm writing low-level network code, and my code looks like this. int err = socket(...) if ( err < 0 ) { do_something; } err = listen(...) if ( err < 0 ) { do_something; } Haskell's bind operator >>= makes abstracting this really simple. I wish I had it in C.
- vikiomega9 8y ago:D I think go did a good job of internalizing this
- xamuel 8y agoCould you elaborate? As a C guy, I'm interested what you think would be simpler than that simple code you posted. Structurally, what you just posted (minus the details about what "socket" and "listen" mean) could be understood by a child. I'm skeptical the corresponding Haskell >>= construction would be so understandable.
- tathougies 8y agoIn Haskell, the 'if (err) { ... }' parts could be factored out into a new bind operator for a new monad stack. Then, you don't have to write these checks every single time. The do notation is automatically converted into the corresponding monadic code, and the algebraic monad laws guarantee that the meaning of the code is preserved. For example, for the `if (err) {...}` cases, this is just the `ExceptT` monad transformer. With regard to understandability, using error throwing code in Haskell is really easy. Implementing it is slightly more difficult, but so is implementing a C compiler. I mean, we shouldn't judge the utility of a thing based on whether the implementation of the thing could be understood by a child. By that measure, children would be incapable of using facebook, because they could not build it.
- xamuel 8y agoSo what would the above code actually look like, in your world? >you don't have to write these checks every single time But in real-world applications, the way you react to errors would differ from function to function. Some functions would print a notification to a logfile and return early. Some would terminate the program and send an email to an admin. Some would activate a red "offline" indicator somewhere in a program.
- tikhonj 8y agoThis is a somewhat misleading way to think about Haskell and monads—it puts the cart in front of the horse and obscures both how Haskell deals with effects and how monads are more general and useful than just IO. This blog post ends up mystifying more rather than demystifying. Then again, this shouldn't be a surprise from an article that describes the Haskell ideas as "the Haskell monadic dogma"! You wouldn't expect a particularly charitable or educational account to start like that. A much better way to think about it is to think about IO without considering monads at all. One of Haskell's core characteristics is that it explicitly separates code with side effects from code without side effects using the IO type. This is a powerful design in and of itself—it's a tool for helping programmers manage effects and not a hack around laziness. Personally, more tools to manage effects is exactly what I want. After all, even in languages, code I write is carefully organized to separate most side-effects (API calls, database accesses... etc) from the domain logic, but this happens solely through convention and code organization. Having tools in the language to express the same separation is powerful. Note how I never needed to talk about monads here—IO is the important concept. With this in mind the real question is not "why does Haskell need monads?" but "why are monads a useful abstraction?". This gets a bit long for an HN comment, but I wrote a blog post with my thoughts a few years back. It started like this: > I believe the notion that Haskell uses “monads” to enforce purity is rather misleading. It has certainly caused quite a bit of confusion! It’s very much like saying we use “rings to do arithmetic”. We don’t! We use numbers, which just happen to form a ring with arithmetic operations. The important idea is the number, not the ring. You can—and most people do—do arithmetic without understanding or even knowing about rings. Moreover, plenty of rings don’t have anything to do with arithmetic. > Similarly, in Haskell, we use types to deal with effects. In particular, we use IO and State and ST, all of which happen to form monads. But it is still better to say we use “the IO type” to do IO or that we use State for state than to say we use “monads” to do IO or state. You can read the rest here: http://jelv.is/blog/Haskell-Monads-and-Purity/ http://jelv.is/blog/Haskell-Monads-and-Purity/ This is by no means a definitive discussion of monads or anything like that; it's just a perspective I found useful for understand what Haskell gets by having a monad abstraction and why it's interesting.
- agentultra 8y agoIf you're looking to understand what Monads are or why they're interesting this isn't the place to figure that out. Monad: if you have a data structure that wraps some value of any type, then you also have to implement the interface that provides `return` and `bind` (there's talk about requiring another method, `join` but I digress). The data structure is the "context" and the interface is how you compose values from other Monads together using functions. If you can prove your implementation of the interface methods follow certain rules then you have a Monad. Why is this pattern/abstraction/interface useful in Haskell? I think there are several reasons. The article touches one of the most talked about: sequencing. Because of the way the rules of the Monad interface are structured you're guaranteed that the functions you use to compose your Monads together will be executed in sequence with respect to their definition in your function body. (How it all ties together is straight-forward but requires a certain pedagogy and exposition to follow that I won't get into.) This is, consequently, what makes Haskell the best imperative language in the world. Not only do you get to use it's wonderful type system but you can compose together small imperative programs into bigger, more useful ones.
- fooker 8y ago>Not only do you get to use it's wonderful type system but you can compose together small imperative programs into bigger, more useful ones. These bigger, more useful programs you can write with Haskell are pretty much unreadable though. There is a reason everyone is not jumping into the functional programming bandwagon, despite mature languages being available for 30+ years.
- jaggederest 8y agoI wrote a fairly reasonable toy raytracer in it and I found it very readable. The hard part was that the type system doesn't help you get your algorithms to spit out the correct answer, only the correct kind of answer. After a while I find that errors of that sort dominate the kind of things the type system helps with. And I'm sure there are type-system-ways-to-solve-that-problem but they're nonobvious and would probably render the rest of the program more confusing. Certainly adding in error handling and state did, compared to working strictly in the IO monad.
- skybrian 8y agoA side point since the author pointed to Agda as being more pure than Haskell: In many practical situations, computations have deadlines, which may either be a timeout or the user giving up and cancelling. Languages that guarantee that functions terminate aren't as useful as you might guess, because they don't say anything about how long it will take. (It could be millions of years.) A guarantee that a function will terminate is useful when you can avoid evaluating the function at all, while relying on a function call's existence (without compile errors) as a proof that the result exists. It's not too often that you want to know that an answer exists without knowing the answer. This mostly comes up in mathematics when proving things.
- lmm 8y ago> Languages that guarantee that functions terminate aren't as useful as you might guess, because they don't say anything about how long it will take. (It could be millions of years.) That's a theoretical problem, but is it one that comes up in practice? In my experience those languages eliminate the overwhelming majority of long-running functions, so are about as useful as you might think.
- skybrian 8y agoI don't think it comes up much in practice because bugs due to non-terminating code don't actually happen much to begin with (compared to all bugs that cause hangs or timeouts), and when they do they are often found via testing. We know that a function terminates for any inputs it was tested with. But I have little experience with these languages (I've mostly read about them) so I'd be interesting to hear about your experiences.
- lmm 8y agoThe "testing will catch it" argument is used as a rationale for avoiding types at all. Of course testing does work, but I find it's more costly and less reliable (e.g. I remember one infinite-loop bug that was missed because it was an overloaded function and the test was invoking another variant). I wouldn't start porting production systems to Idris just yet, but I do think it's worth adopting the same kinds of techniques: avoiding loops entirely, and making sure that when you do recursion it's very clear from the code what parameter is getting smaller in what sense.
- tufbhuf 8y agohttp://adit.io/posts/2013-04-17-functors,_applicatives,_and_monads_in_pictures.html http://adit.io/posts/2013-04-17-functors,_applicatives,_and_...
- emmelaich 8y agoVery nice!
- dustingetz 8y agoIf you think Function Composition is a good thing, then it is very natural to want composition for functions of more interesting types than T→U, for example T→Promise[U] which chains intuitively but not by type. How did I do?
- jimbokun 8y agoI don't know, but I think "Chain" might be a better name than "Monoid" to introduce the concept and capture the main idea.
- dan-robertson 8y agoWell “Monoid” is a different sort of composible than something like like T -> Promise U. I do agree that Monoid and Monad aren’t really great names for anyone who doesn’t come from an algebra background (ie basically anyone).
- bcheung 8y agoI like to think of monads as a supporting sidecar wrapper. It's almost like a plugin system; you can extend some base computation to have additional functionality for free. This becomes really obvious how much boilerplate can be "lift"ed in something like the Flare library for PureScript (https://david-peter.de/articles/flare/ https://david-peter.de/articles/flare/). Ex: lift2 ( * ) (int "a" 6) (int "b" 7) This generates 2 text fields, hooks up the change events, plumbs them into a third UI component that renders the product of the 2 fields. Fundamentally we are just saying we want "a * b" but the monad / sidecar does all the extra work to make UI components and hook up the change events.
- k__ 8y agoIt's a way to wrap values in a mathematically defined way. You can do it differently if you like, but you have to define the semantics yourself and find out about edge cases that math has already solved, which is the problem that is searched here, I guess...
- draw_down 8y agoIt was once said of philosophers, that they first raise a dust and then complain that they cannot see. I was reminded of this while reading some of the discussions here.
- nu-world-odor 8y agofuck it https://www.lrde.epita.fr/dload/papers/newton.16.monad.report.pdf https://www.lrde.epita.fr/dload/papers/newton.16.monad.repor...
- benadamx 8y agodoes haskell support bofa monads?
- chadcmulligan 8y agoI'd say the problem with all these Monad explanations is the people reading them are in general object oriented programmers (like me) and the problem that exists in functional programming just isn't an issue in C++ / Swift etc. Its never explicitly stated because functional programmers are just so used to it, its like air. So in a very naive way - here's the problem, When you apply a function and get a result you have no state - the function doesn't remember what happened. In object oriented programming you always have the state - its in the object, or other objects floating around, and methods can always reach out into surrounding objects to get bits of state it needs, pure functional programs can't do this - they have no state except the things that are passed as parameters. The haskellian way around this is to pass around the context of what just happened - vaguely the context is the surrounding bits of data you need to keep track of. Monads, functors and so on are the ways that haskell keeps and manipulates the context and the value. The rules also have a very nice mathematical theory which they fit into. Most haskell intros start from maths and shoe horn the code into this - its not really necessary. This clarified it for me http://blog.sigfpe.com/2006/08/you-could-have-invented-monads-and.html http://blog.sigfpe.com/2006/08/you-could-have-invented-monad...
- dan-robertson 8y agoI think one thing one should ask is: If monads are the solution to X, and we add a different solution to X to the language, then what are monads for? Let’s say you have imperative IO. Then promises are a Monad but then you could just add language support for async and promises are not really needed as a Monad anymore. A more functional replacement for monads could be something like algebraic effects. These could replace monads completely or they could be like multi core ocaml and only give you one shot continuations (so not allowing for list or other multi-shot monads). In this context I like to think about the continuation Monad. In Haskell: newtype Cont r a = Cont { runCont :: (a -> r) -> r } This means “some computation yielding a r which got paused part-done having just yielded an a. In some sense all monads are expressible in terms of Cont. I feel like this is largely the essence of why it is useful to have monads: they express computations which happen with some context, able to switch back and forth between things in the context and things in the normal computation. I don’t think sequencing evaluation is so important to the definition.
- galaxyLogic 8y agoThe question monads answer is: How do I make the components of my program recombinable in ways that the result is also a monad. Monads are like LEGOs, you can combine them in any way and what you get is something to which you can attach still more lego-pieces. It doesn't matter how exactly lego pieces are able to connect together. What matters is that you can combine any Legos together with that same mechanism and get as result something to which you can attach even more Legos. Maybe a better name for Monad would have been "Lego".