1. 17

    An example of [Haskell’s incredible advancement over other languages] is the concept of resumable exceptions.

    I wonder how Common Lisp managed to pull off resumable exceptions for decades then?

    1. 11

      Your citation makes it seem like they claim only Haskell supports resumable exceptions, but I don’t see that at all in the original. They just state it makes them possible in a convenient way and from the context it’s clear they mean that relative to Go and Java as possible alternatives. So I don’t think you can conclude they are unaware of the history of resumable exceptions and of the fact that other languages support them.

      Even if they were, your comment runs the risk of leading people to disqualify the author and their arguments for this single mistake. I know I was tempted to do so.

      Disqualifying the entire article would be a plain fallacy (a mistake/error should bear upon the other arguments and there are multiple unrelated arguments in this case) and disqualifying the author is also unwarranted: not knowing the entire history of a particular language feature you like and have use for is not a reason to think someone is lacking in relevant knowledge. Knowing the history was mostly irrelevant for the choice.

      Charitably what they say can be read as: “One of the things we liked about Haskell, relative to the viable alternatives, was the easy availability of resumable exceptions”.

      N.B. I don’t mean to imply you intended any of this with your comment; just the first paragraph is a direct response.

      1. 2

        Exactly! Here’s what I wrote on HN:

        During Semantic’s interpretation passes, invalid code (unbound variables, type errors, infinite recursion) is recognized and handled based on the pass’s calling context. … And given Go’s lack of exceptions, such a feature would be entirely impossible.

        Nonsense! It’s completely possible — you just have to be willing to abuse a few of Go’s features in order to do it.

        First off, Go does have a standard calling-context (i.e. dynamic context) mechanism: context.Context. All you need to do in order to track stuff in the calling context is stash an object in the … context.

        Given this dynamic context mechanism, you next need a way to represent handlers. That’s easy: a handler is a function which takes an exceptional condition and either handles it or returns; handling consists of a transfer of control. Fortunately, Go has a control-transfer primitive as well: panic. So to handle a condition, a handler just panics — something higher up the call stack can use defer to catch the panic and continue execution.

        That leads to the next component necessary: a way to provide resumption points, or restarts. A restart is just a function which is invoked by a handler, potentially with arguments, and which when invoked continues execution from its establishment point. This can be done with defer.

        So it’s perfectly possible with tremendous abuse of Go’s panic/defer mechanism, no different from Java.

        See this gist.

        Honestly, I don’t even know if I’d call it tremendous abuse, although it is somewhat abusive. Abstracted in a library, it might even be somewhat useful.

        It’s off-the-cuff — I haven’t fully considered the semantics of the different context objects being passed around.

        If you take a look at the Gist, it’s just a simplified Go version of the concepts involved. It bites that Go doesn’t have macros to clean up some of the nasty nesting involved, but it’s still a workable solution.

        1. 4

          it’s worth being skeptical when someone says you can or can’t do things in a particular language. You can nearly always do anything in any language. It just sometimes gets laborious to do so and you might not desire to, but that doesn’t mean you “cant”.

      1. 11

        You have to drink a lot of typed Kool-Aid to consider this an acceptable way to program:

        string msg = sqrt(-1)
          .leftMap([](auto msg) {
            return "error occurred: " + msg;
          })
          .rightMap([](auto result) {
            return "sqrt(x) = " + to_string(result);
          })
          .join();
        
        1. 2

          It looks much nicer with the Coroutine TS. Hopefully something similar (but better designed) will land in C++ proper.

          In the mean-time, you can access the values in an unsafe manner using .isLeft(), .getLeft() etc.

          1. 2

            I love functional programming and this is abhorrent. Although, to its credit, C++ has never been big on esthetics.

            1. 2

              Isn’t that how a lot of JS functional programming is done?

              ducks

              1. 1

                I agree. I’m still in the process of re-learning the various C++ standards, and this code is practically unreadable to me. I would much prefer something I can read and mentally step through.

                1. 0

                  Even in languages with more concise syntax that’s a braindead way of programming if you ask me.

                  string msg = sqrt(-1).leftMap(msg => 'error occurred: #{msg}')
                                       .rightMap(result => 'sqrt(x) = #{result}')
                                       .join();
                  

                  In C++ it’s abhorrent. Not to mention that leftMap and rightMap should be left_map and right_map in C++. Using camelCase in C++ is like using lowercase_with_underscores in Java. It’s just wrong.