Comment by sebstefan

8 hours ago

I swear I'm not trying to be inflamatory, but this is the _worst_ programming language feature I could ever imagine. I'm not trying to be hyperbolic, if I try to reason about it there is nothing I can come up with that I would dislike more in the realm of recent features that have been pitched in the PL community

I was already in the camp that try/catch is "considered harmful", I dislike the concept of having a second, hidden, control flow that might get sprung up upon function callers, because it has side effects buried in the implementation of a callee that are not defined in the parameters or the returns, and I am not 100% sold on the benefits of "Things in the middle don’t need to concern themselves with error handling.", which I guess informs this opinion.

Now since I hate that, I really, really would hate that on top of this, another programmer could write a hidden control flow upstairs that could, potentially, not just crash my code, but also do a lot of other things, such as coming up with default values for unexpected NULLs or whatever, which could THEN take something that would have crashed immediately, and turn it into something that crashes later down the line, away from the problem, with a varialble set to an inexplicable value that I have never put there myself

What a nightmare to debug! I mean, come on

I agree but I also think it's important to point out that Algebraic Effects typically refers both to a runtime feature (the try/handle delineated continuation stuff) and also a type system feature.

The latter is very important because in your example it would not really be hidden. If your function does not have the "exn" effect, it cannot call functions that throw exceptions, full stop. Same with any other effect including IO if you want.

Basically function coloring taken to the extreme. In a statically typed language with statically typed effects you actually cannot get surprised, which was your major complaint.

Type systems that support algebraic effects also typically support row polymorphic effects (fancy generics) so you can make a function generic over "color", avoiding the "function coloring" problem.

Now, having said that, why did I say I agree with you? Well because algebraic effects are a lousy user-facing feature. You almost never want to implement your own handlers, at best you'll plug in a custom handler for the IO effect and that's about it. (And for exceptions of course, but that's just exceptions with extra steps)

Where they shine is for the language implementer. They provide a framework on which exceptions, generators, async/await and even prolog-like backtracking can be implemented, while (very importantly) defining how they compose. That's really the bit that makes them so interesting from a research point of view and why they might make it into the mainstream languages, even if the language doesn't actually ever expose them for you to use.

> I dislike the concept of having a second, hidden, control flow that might get sprung up upon function callers, because it has side effects buried in the implementation of a callee that are not defined in the parameters or the returns

You might like my capability-based effect system for Haskell, Bluefin[1], then. If a Bluefin effectful function throws you can see it in the type system. If you want to have the capability to throw, you need to pass in an argument of type Throw. For example here "workWithThrow" can only throw an exception because it is passed the Throw capability.

    workWithThrow  ::
      (e1 :> es) =>
      Throw String e1 ->
      Int ->
      Int ->
      Eff es Int
    workWithThrow t x y = do
      let result = x + y
      when (result > 10) $ do
        throw t "Too big"
      pure result
    
    -- ghci> example
    -- Left "Too big"
    example :: Either String Int
    example = runPureEff $ try $ \t -> do
      workWithThrow t 5 7

[1] https://hackage.haskell.org/package/bluefin

Because you must statically declare dependency on an effect, it's opt-in.

To be clear, you still pay indirection cost: when you do opt in you have to hope the upstairs implementation is compliant to the contract. But that does also apply to interfaces/typeclasses.