1. 19
  1. 2

    This was a great post, as usual. A few thoughts I had while reading it:

    • It’s a shame null is still something that can be expressed even when it makes no sense. I guess this is from the C# roots.
    • The Result and Exceptions part reminded me a lot of what you get in Haskell or Ocaml with Either or Result + some monadic combinators. I guess there is also a line to draw between usability and “correctness”, but it always bums me out to see languages only let one express a subset of monadic expressions well. Why special case it? I don’t get it, but I’m rather happy with code that reads like foo () >>= fun x -> do_something.
    • I really wish we had lightweight processes in Ocaml. I’d love to have seen Midori done with Ocaml as the base language. Based on the blog posts, it feels like the Midori landed very close to Ocaml on a lot of big decisions, but the C# heritage kept it from going all the way. Maybe that’s the correct place to be, but Ocaml seems to really be a nice intersection of performance, simplicity, and expressivness that it feels like the authors were going after. Of course, this was a multiyear project with tons of people involved and just a few posts on it so maybe I’m reading in what I want to read.

    But to talk up Ocaml:

    Ocaml has, IME, the best error handling model of any language I have seriously used. What is so great about it is it lets you express exception-like things in a type safe way using return values. This comes from polymorphic variants, which I’m not sure of another language that has them. But roughly how it comes out is you can have code like: x >>= y >>= z and with polymorphic variants the type of that expression will be the union of every polymorphic variant that those values have. This makes it possible to compose functions that use return values but aren’t related to each other. It’s fantastic and incredibly powerful. I have heard some Ocamlers claim that they ran into issues with this, understanding the error messages with polymorphic variants can be tough, but I have never experienced this particular issue, I’ve had positive experiences with it.

    Once one has this setup going, adding new error cases that need to be handled is as simple as adding it to the return type of a function and the type spreads through the code like a wild fire and you can fix all callsites (assuming nobody is throwing out any return values) to handle the error. This is why I’ve been using Ocaml for all of the distributed systems work I can. The concurrency is fairly poor but the error handling is so great I’m willing to make that sacrifice.

    1. 1

      The recoverable/unrecoverable distinction makes a ton of sense to me. Error values make sense when there’s a decent chance I want to do something application/context-specific on error; a wart at each callsite reminds me to consider the error case. (Memento mori or something.) Exceptions make sense for errors it’s kind of hopeless to proceed past; may as well not clutter up the callsite when I’m not going to do anything useful anyway. (Just kick it up to a higher-level handler that produces an error {response,message,whatever}.)

      A rub is that unrecoverable/recoverable depends on context. For the kind of thing I write at work (in Python) if I can’t reach the database, I might as well give up. On the other hand, if I were working on the sort of distributed system where one Web request leads to a ton of internal requests, some will fail routinely and I have to think about that. Perhaps you start with values in low-level code and have ‘must’ wrappers that exception-ize the failures that your app that aren’t really possible to handle in your world.

      Stories with similar links:

      1. The Error Model [2016] via snej 2 months ago | 14 points | 3 comments