1. 68
    1. 10

      At least from the perspective of someone who is used to C that is considerably less safe, this seems like a risky pattern.

      error = sanity_check();
      maybeDo(continue_process(), error);

      eliminates my ability to stop worrying that input that is not sanity-checks is immediately kicked out. I have to now check that the entire future processing contains maybeDo, and if it’s missed one time, I’m probably handling input that hasn’t passed a sanity check and might have a vulnerability.

      1. 3

        I agree, and while the example looks nice, that’s not what real code looks like. If you’re going to copy/paste and call the same function 5x in a row just throw the call + error handling in a loop over the array of inputs.

      2. 2

        If maybeDo expects a function, then using continue_process() instead of continue_process typically would not compile.

        But your example is introducing non-monadic error handling again. It’s just another variation of having a possibility to forget to check a return value, and this can be solved with the type system:

        continuator = sanity_check();
    2. 7

      In the past I’ve done this, which gets around the issue with closures. It’s not ideal,but did clean up some pretty hairy stuff.

      1. 5

        You really need to rename that type to MaybeDoer like we talked about.

        1. 1

          I tend to not go back and update blog posts, but yeah, it’s one of my worst names, for sure.

      2. 1

        I did the same thing to help a co-worker deal with an overabundance of if err in his code

        I also used it to explain the Monad abstraction this was using.

    3. 18

      For the love of God, do we have to label 50 year old software methods with some misused term from Category Theory?

      1. 22

        The term “monad” does apply here though, albeit without it’s full power. We should be glad to see that old techniques are being understood using another angle. Understanding monads has helped me see lots of patterns in code I write every day. It’s not merely a useless theoretical construct.

        1. 6

          I’m interested in hearing about what insights you got from “monad”. To me, it’s just pretentious.

          1. 10

            For me, monads are a concept that lets me think about computation that carries baggage from one statement to the next. The baggage can be errors, a custom state object, or context related to IO or async computations. Different things, similar way it looks in code. Recognizing the monad concept helps me understand the contours of the implementation and also helps me generate better designs. It goes both ways. I do think there’s value in using the word. It’s a way to compact thought.

          2. 6

            You may have reasons to dislike Haskell, but that shouldn’t mean you need to deprive yourself of the benefits of these fundamental concepts. Take the signatures of the following functions:

            ($)      ::                                     (a -> b) ->   a ->   b
            (<$>)    ::                      Functor f =>   (a -> b) -> f a -> f b
            (<*>)    ::                  Applicative f => f (a -> b) -> f a -> f b
            (=<<)    ::                        Monad f => (a -> f b) -> f a -> f b
            foldMap  ::         (Foldable t, Monoid m) => (a -> m  ) -> t a -> m
            foldMap  ::     (Foldable t, Monoid (f b)) => (a -> f b) -> t a -> f b
            traverse :: (Traversable t, Applicative f) => (a -> f b) -> t a -> f (t b)

            These are all different ways of combining a function that may have some super powers (in a functor context, producing a functor context etc.) with some value that may have some super powers and obtain something with super powers. And note how you can combine them together to build a composite value. By the way, there are infinitely many like these, and new useful ones are discovered all the time.

            Having an intuition for these combinators combined with a knowledge of common types that satisfy the Functor, Applicative, Monad, Foldable, Monoid and Traversable helps you write reusable code. Instead of tying down the implementation to a specific combination of concepts, you recognize the actual pattern the first time, so your code becomes easier to understand* and reusable the first time with less typing!

            (*) Easier to understand for someone who has an intuition for these concepts. Because if you see a function with signature Applicative f => f A -> B -> f C, you immediately know that the function may decide to invoke the f effect you gave it (f A) a number of times and it may base that decision on B and it may also use your A and B to construct the C, but it’s impossible for it to use the A to decide on how to invoke the effects of f.

            Reading tips:

            • a :: b: a has type b
            • a -> b: a function from a to b
            • f a: Type constructor f (for instance List) applied to type a (to produce “list of a”)
            • a -> b -> c: Same as a -> (b -> c): a function that takes a and produces another function that will take b to produce c, a two-argument function in practice.
            • c a => ... a ...: a is any type that has an instance (implementation) of the typeclass (interface) c
            1. [Comment removed by author]

          3. 5

            most people don’t know this but the haskell version of monad is a crude simplification of the category theory definition of monad. it’s a lot less general.

            1. 4

              It’s always possible that I am wrong, but to me, “monad” as used in Haskell is partly just a pompous way of describing well known and trivial programming paterns and partly a painful effort to permit the absolutely necessary use of state variables within a system based on the incorrect theory that one should program statelessly.


              1. 11

                Full disclosure, I’m a monad-lovin’ fool, but to me the utility of the concept is to see that some (yes) well-known and trivial programming patterns are in fact conceptually the “same thing”. It unifies ideas, that many programmers only learn by convention, underneath a single concept which has more than just the weight of convention behind it.

                It’s totally possible to go overboard with it, or to over-rely on the flimsy “mathiness” of FP as a way of dismissing or trivializing real problems, but imo if you want a bedrock understanding of programming patterns that is based in something factual (as opposed to flavor-of-the-month tribalism or appeals to the current authority), FP is the way to go.

              2. 5

                I’m with you on the pompous and unenlightening terminology, but that’s a pretty unfair characterization of the role monads play in demarcating state-dependent code.

                1. 4

                  I think there are a couple of real insights in FP, but the “math” continues the basic CS confusion between meta-mathematics/axiomatic and working mathematics - which is often about processes and algorithms and state. And the project falls for a common CS error, in which we note that doing X is hard, messy, error prone, so we create a system which doesn’t do X because that’s clean/light/well-defined “easy to reason about” (even if not in practice) and then we discover that X is essential, so we smuggle it back in awkwardly. I just don’t see the insight that the Category theory brings to understanding state.

                  1. 4

                    My experience with describing stateful systems is that it’s much more elegant and bug-free in pure languages because you get to reason about it explicitly. I don’t see many imperative languages letting me statically prove propositions about the system state (“this resource exists in that state at that point in the code”). You need equational reasoning and powerful type systems for that. Monads help.

                    The point of purity isn’t about avoiding state and mutability, it’s about avoiding implicit state.

                    1. 2

                      That’s great. Do you have a link to a paper/post which works out a proof of some non-trivial program?

                      1. 1

                        The most complex I’ve seen is probably the state management in my video game, where it ensures proper initialization, resource management and deallocation. It’s very simple on the user side, it consists only in writing and complying with the state transition specifications in the types, e.g. quitRenderer : (r : Var) -> ST m () [remove SRenderer r]. This is a method in the Draw interface whose type says that it removes the renderer from the context m. SRenderer is some Type defined by the implementation, in my case a more complex composite type, and if I were to implement that method without properly handling those subcomponents, it wouldn’t compile. There is much more to the “complying” part though. ST tracks your state as you further develop your function, and you just print it out. It’s like a static debugger. You see how your states evolve as you unwrap and modify them without even running your code, and I cannot how rarely additional changes after you test are needed compared to other languages.

                        I haven’t worked on other serious Idris projects, I’ve just done the book exercises and went to code my game. I definitely recommend the book though.

                  2. 1

                    Yeah, I’m with you on the category theory, that could easily be obfuscatory math-envy (still looking for an example of a software situation where category-theory concepts carry more than their weight.) But separating out X as much as possible, and making it clear where X has to be mixed back in, that’s just modularization, usually a good policy.

                    1. 0

                      But modularization is hard and where module boundaries don’t work, creating a backdoor often just makes things less modular. To me, what would be more interesting than “pure functions” plus escape is to clarify exactly what, if any, state dependencies exist. I don’t want to bury e.g. file system pointer dependency in some black box, I want to know f(fd,y,z) depends on fd.seekpointer internally and fd.file contents externally, or, for a simpler example that f(x) has a dependency on time-of-day. I don’t see much serious work on trying to expose and clarify these dependencies.

                      1. 4

                        That’s exactly the role monads are supposed to play in Haskell.

                        1. [Comment removed by author]

              3. 1

                I don’t have much problem with naming the idea of monads, though I definitely agree that pure functional programming is not a good way to program.

              4. 1

                I read your linked post. Since you don’t provide commentary, it’s hard to understand what your “Oh” is meant to imply. I’m guessing that you’re trying to highlight a contradiction between “Why functional programming matters” and the “Unix paper”. Could you explain your idea here?

                1. 1

                  Hughes describes as a novel advance from FP something that was well known a decade earlier in systems programming - and described in one of the most famous CS papers ever published. The concept of programs streaming data over a channel, of course, also predates UNIX which is why Dennis Ritchie, who did know the literature did not make a claim as ignorant as Hughes’ - or the one in the paper discussed here.

                  1. 2

                    Hmm. I don’t know the literature either. Your critique that much of the first paragraph from “Why functional programming matters” quoted in your blog post isn’t novel in light of the “Unix paper” from more than a decade earlier seems true, given the context in your post.

                    That said, there are some novel things which lazy-evaluation brings to the table. Hughes hints at them in the section that you quoted:

                    1. It makes it practical to modularize a program as a generator that constructs a large number of possible answers, and a selector that chooses the appropriate one.

                    2. You can take this further: The “possible answers” may be large data structures which are expensive to compute, and lazy evaluation will only compute enough of the data structure to determine whether you want to use it, leaving the rest unevaluated when the “selector” rejects them.

                    Strict evaluation doesn’t give you either of these advantages unless explicitly constructed. You can get #1 with blocking semantics on reading & writing unix pipes, like Ritchie showed. You probably can’t get #2 in a general way without lazy evaluation.

      2. 19

        misused term

        Can we please stop fighting jargon? Do other industries have people constantly calling for things to be named differently? Like, does the Fashion Industry have people fighting to rename “balaclava” to “winter weather knitted headwear” because it’s headwear named after a Battle, and not really descriptive?

        If not “monad,” what’s a better alternative?

        1. [Comment removed by author]

          1. 17

            A monad is a “case statement”?

            That’s not at all generally true, though. An important aspect of the Maybe and Either monads is that they case on conditions of previous computations, for sure. But, the Writer monad doesn’t have a case statement at all.

            What they have in common is that they allow operations to compose while respecting certain properties. In Maybe, you compose a bunch of operations that can fail by not calling further down the chain if one of them failed. In Writer, you append to a running log of all previous operations. In the State monad, you weave some updatable value through all the operations.

            “case statement” doesn’t begin to describe this at all.

            1. [Comment removed by author]

              1. 9

                There’s no smuggling about it. Indicating that that’s what’s happening is kind of the point, in Haskell.

              2. 8

                You are describing one example of a monad, commonly referred to in the Haskell community as the State monad. There exist other monads too, which facilitate all sorts of other tasks: error handling, multiple possible outputs, continuation passing, logic backtracking.

              3. 5

                A monad is just a way of abstracting composable actions. It doesn’t do any “smuggling”.

                I think your argument that it is unhelpful to describe this abstraction with esoteric mathematics does hold some water.

                However, you seem to not actually understand what a monad is (potentially due to how they are described), and it reads like you are arguing against your own constructed idea of a monad, or even against the idea of abstracting common patterns in general.

                FWIW I think the blog post illustrates why being aware of these common patterns is useful. Once you identify this kind of common structure you can build an abstraction that works in the general case, and can be applied to a wide range of situations where the same pattern crops up. (Providing of course, that your language has support for the required constructs to express this abstraction).

              4. 2

                Okay lets say you pass the system state around a few mutually-recursive functions as an argument. Is that smuggling in state? That’s literally what the State monad does behind the scenes. No magic, no smuggling, turns out pure functions are not some fundamental limitation.

      3. 8

        It seems pretty clear to me that the purpose of this post is to shame rob pike, people already think he’s too stupid to understand the hindley-milner type system so this just adds onto that.

        1. 6

          I don’t think the post shames Rob Pike. I think it’s meant to point out a pattern that shows up in the go stdlib also shows up elsewhere.

      4. 2

        I think in the pursuit of knowledge it makes sense to name & build understanding around the things that we already do, or observe nature doing. That’s what applying a “misused term” to “50 year old software methods” accomplishes.

        1. 2

          I don’t think jargon confers knowlege.

          1. 1

            I’d rather we name concepts than ceaselessly describe them in imprecise terms. That’s what the article is doing, and it serves the function that people can understand the pattern if they’ve already used it elsewhere, that communication is clearer because it is more concise, and that mistakes can easily be identified.

            So, while jargon might not confer knowledge, it enables communication and facilitates thought.

            1. 1

              To me, there is nothing precise about the concept of monad as used in FP. It’s a collection of messy hacks. The Moggi paper is a travesty of Category Theory.

      5. 1

        You really need to relax.

    4. 11
      for(i=0; i < 3; i++)if( (n=write(fd,p[i],PAGE)) < PAGE){

      !Amazing! Note the use of cofunctors and Grothendeik primes.

      1. 9

        You know I can’t go a day without cofunctors and Grothendeik primes.

        1. 6

          I find that it is important to start each workday with a commuting diagram between categories of marketing terms where the buzz preserving morphisms in each category form can be grouped into a commuting diagram of covariant monadal functors.

    5. 4

      I don’t really know why Go didn’t just do the obvious thing to make exceptions concurrency friendly, which is have every goroutine point to a linked list of exception handlers established by the parent goroutine.

    6. 2

      Here’s the example code in zig:

      try fd.Write(p0);
      try fd.Write(p1);
      try fd.Write(p2);
      // and so on

      You’ll even get an error return trace if one of them fails.

    7. 2

      To me this speaks well of both Pike and monads.

    8. -1

      Repetition is not a sin, it can also be good in a language.

      So far, the only practical justification for monads I’ve seen was revolving around “reducing code duplication”.

      Generics and Monads are mistakes for some lean languages, such as Go and C. Generics were a mistake in Rust and made the language far more verbose. That was not safety that was gained then, only some limited comfort for the writer of the code. This is to the detriment of the reader, which should come first, always.

      What happens with monads when you have to respond to several types of errors differently? Genuine question, my guess is that the neat pattern is not so neat anymore, and the initial gain is lost.

      1. 9

        With monads, or rather any explicit encoding of errors as part of a normal return type (like Result<Ok, Err> or Either), you’d have to be able to stuff the various error types together, i.e. with data ThisError = ThisIOError IOError | ThisOtherError and the function would return Result<int, ThisError> (excuse me mixing languages). Then it’s just pattern matching on return values as normal.

        You can end up with loads of these types if you have functions that can fail in varying combinations, but that could be taken as a hint that you should structure things so that each function can only fail in a minimal number of ways.

        This issue with monads is exacerbated when you want to deal with more than one at a time (for instance, an IO monad, an async monad, and an error monad), in which case you may have to do a bunch of manipulation to massage things around. Effects, which I first saw in Idris, can be used as a more general idea of encoding side-effects like this, but I have only done some light reading on them.

      2. 7

        Repetition is not a sin, it can also be good in a language.

        I wrote a blog post that argues this position in a bit more depth, if anyone is curious: https://medium.com/@shazow/code-boilerplate-is-it-always-bad-934827efcfc7

        (Not related to the rest of the comment about the necessity of generics/monads. I am personally in support of generics to improve type safety, but I understand that it is a difficult challenge to implement them in a way that is compatible with Go’s goals as a language.)

      3. 5

        off-topic: why was this downvoted four times? this fits nothing from https://github.com/lobsters/lobsters/wiki/DownvoteGuidelines .. can we be more civilized again, please?

        1. 40

          I downvoted it for being blatantly incorrect. Generics in Rust are critical to its safety story. Without them, it’s much harder to build reusable safe interfaces to things that might use unsafe internally. This would, in turn, result in more unsafe being used, which would weaken Rust’s value proposition considerably. Describing generics in Rust as a “limited comfort for the writer” is a serious mischaracterization. (This isn’t to say that generics don’t come without their downsides, but there just wouldn’t be a Rust without them.)

          Normally I might respond to correct it, but based on their comment history, it doesn’t seem worth my time. You might consider asking them to be more civilized instead of their downvoters, e.g., https://lobste.rs/s/2sriyi/build_systems_are_stupid#c_uqithi

          1. 4

            i don’t know rust well, so i can’t say anything about this. i just think that downvoting was the ultima-ratio on lobsters, and that that comment didn’t fit any of the downvote categories. most of my comments got downvoted a few times recently (still, less than upvotes), instead of replying something. this is bad for the community, imho.

            maybe downvoting should reduce the downvoters karma too, it would fit the name karma ;)

            1. 11

              The comment is indeed incorrect as buntsushi said. Generics are key to it’s safety story. And incorrect is a valid downvote reason on this site.

            2. 6

              My experience is that downvoting is sparingly used on this site, especially when it comes to very technical discussions like this. A downvote for “incorrect” also weighs more than one for “troll” or “me-too”.

              In any case, complaining about downvotes (especially on other user’s comments) is rather unproductive. Either balance-upvote, or read up on the issue at hand and form your own opinion. If you agree with the commenter who is downvoted, add a comment of your own in (technical) support.

            3. 1

              It’s a tough line to walk, but replying also gives credibility to the opinion (it was sufficiently formulated to give a reply). If people find the statement not useful to discourse, a downvote is indeed the right signal, IMHO. I think this mechanism works well here, by and large.

              1. 2

                It’s a tough line to walk, but replying also gives credibility to the opinion (it was sufficiently formulated to give a reply). If people find the statement not useful to discourse, a downvote is indeed the right signal, IMHO.

                i just think that the respective comment wasn’t off-topic, incorrect (it was an opinion piece), troll, me-too or spam. it was downvoted for “i think thats wrong, but i don’t want to reply right now”. the standard here should be either to reply with reasons something is wrong or ignoring it, not knee jerk downvoting.

                I think this mechanism works well here, by and large.

                it’s just a feeling, but with more people it gets worse (because of the knee jerking). downvotes can be instant-gratification sometimes.

                1. 9

                  It is indeed incorrect, as burntsushi worked out quite well. The are fundamental to Rust safety sorry.

                  not knee jerk downvoting.

                  There’s also knee-jerk opinions, which this qualifies for, IMHO. It would be better with at least a little more effort of working out what they believe is a better design (or what’s wrong with the design).

                  it’s just a feeling, but with more people it gets worse (because of the knee jerking). downvotes can be instant-gratification sometimes.

                  I don’t share that feeling.

                  1. 2

                    It is indeed incorrect, as burntsushi worked out quite well. The are fundamental to Rust safety sorry.

                    for me, not knowing rust well, burntsushis comment was very valuable, a simple incorrect downvote isn’t as helpful.

                    but this is getting way too OT now :)

    9. 2

      The same is happening in the C++ community. But we still can’t have real monads…

    10. 0

      The article is well written.