Algebraic Effects for the Rest of Us

(overreacted.io)

48 points | by satvikpendem 3 days ago

9 comments

  • tome 1 hour ago
    I was completely baffled by "algebraic effects" for years. They looked far too confusing for me to want to spend my time on them, and took the "Don’t feel like you have to [get curious about them]" approach.

    But then at some point it struck me: underlying all these effect systems is just passing stuff in. So I developed my own effect system for Haskell, Bluefin[1], based on capabilities, which means the "capability to perform some effect" is represented by just passing stuff in (that is, a function can do some effect as long as it has been passed the capability to do it).

    From this point of view it's hard to understand the excitement over "resume with" and "the part you can’t do with try / catch. It lets us jump back to where we performed the effect, and pass something back to it from the handler". Programming languages have had that feature since forever: a "resumable exception" is a "function call". A dynamically chosen "resumable exception" is the call of a dynamically chosen function, i.e. the argument to a higher order function.

    So I don't know why people love the complexity around "algebraic effects". Maybe the mystique has a certain allure. But if you want the most straightforward possible approach I can recommend you try out Bluefin. I'm happy to answer questions on the issue tracker[2].

    (Caveat: Bluefin is able to simplify things dramatically by dropping support for "multi-shot" continuations. But mostly you don't want multi-shot continuations.)

    EDIT: I was too pessimistic, bazoom42 has noticed this :) https://news.ycombinator.com/item?id=48334067

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

    [2] https://github.com/tomjaguarpaw/bluefin/issues/new

    • jacobp100 6 minutes ago
      Continuations (“function calls”) reduce the amount of optimisations you can do around memory - both space wise and computationally
  • satvikpendem 1 hour ago
    Oh neat, I'm in the second chance pool.

    I submitted this because I've been getting really interested in effect systems, especially now that OCaml 5 has a working production quality example they'd been iterating on for years prior. I wanted to see what it'd look like in Rust too so maybe one day we can get rid of async function coloring, and with OxCaml by Jane Street maybe we could see how that would look in practice.

    Another reason for submitting this is that React actually has a quite robust effect system, that people don't necessarily realize they're using one every day if they use hooks.

  • u1hcw9nx 12 minutes ago
    Standard feature of Common Lisp condition system (over 30 years old).

    https://lisp-docs.github.io/cl-language-reference/chap-9/j-b...

  • Trung0246 1 hour ago
    For some funsie here's my fully working delimited continuation in C with effect handler example: https://godbolt.org/z/3ehehvo6E

    No ASM involved so technically portable (although it depends on built-in).

    Flix equivalent (copy paste to https://play.flix.dev/):

        eff Pick {
            def pick(): Int32
        }
    
        def body(): (Int32, Int32, Int32) \ Pick = {
            let a = Pick.pick();
            let b = Pick.pick();
            let c = Pick.pick();
            (a, b, c)
        }
    
        def handlePick(f: Unit -> a \ ef): List[a] \ ef - Pick =
            run {
                f() :: Nil
            } with handler Pick {
                def pick(_, resume) =
                    resume(1) ::: resume(2) ::: resume(3)
            }
    
        def main(): Unit \ IO =
            println(handlePick(body))
  • bazoom42 39 minutes ago
    So this looks like dynamically scoped callbacks. Instead of passing callbacks along as parameters they are declared as “handlers”, and any function down the call stack can invoke them. Is this a correct understanding?
    • savq 18 minutes ago
      Yes. dynamically scoped, and statically typed.
  • Epa095 53 minutes ago
    Previously discussed (with pretty decent comments) https://news.ycombinator.com/item?id=20496043
  • KolmogorovComp 55 minutes ago
    I’ve used effects in scala3 with cats-effects and haven’t been impressed. All in all, the way it was used was just to reimplement a very similar interface to exceptions.
    • noelwelsh 12 minutes ago
      Cats Effect is monadic effects. What is discussed here is sometimes called "direct-style" effects, and is an alternative representation.

      I think you've missed the point regarding effect systems. Concurrency and resource handling, implemented in a way that is composable and reasonably easy to reason about, are two of the big ticket features.

  • HeyImAlex 2 hours ago
    Do effect systems actually avoid colored functions? Don’t most typed effect systems require the used effects in the signature?
    • brabel 1 hour ago
      Yes, this article is doing a bad job at explaining why you would want effects, and one of the main advantages is exactly that it becomes part of the type system, essentially coloring every single function with a set of effects it needs to be called. As the article used JavaScript it shows what untyped effects would look like, which in my opinion is awful. If you want to use algebraic effects today, I highly recommend Unison. If you’re on the JVM, Flix is doing major advances with effects!

      https://www.unison-lang.org/

      https://flix.dev/

    • codebje 1 hour ago
      When you need to use an effect, you need it in the type. If you directly call a function using some other effect, it propagates into your function. So far, so colourful.

      But you can have generic effects. Your arguments and return type can specify "any effect", indicating your function can use a type with any effect safely, or can be used in any effect context safely.

      Passing an async value to a function doesn't mean that function must now also be an async function. It can be a "for all effects, do the thing" function. The code duplication problem is gone.

    • mrkeen 1 hour ago
      No, they are function colouring. That's the point.

      Someone writes a post lamenting red and blue functions, and everyone eats it up.

      Substitute colour for something meaningful and the idea becomes idiotic.

      "Top level function declares that it is non-blocking, but when I try to call a small blocking function from it, I have to change the declaration to blocking???"

      Yes, yes you do.

      Total functions can't call non-total functions.

      Deterministic functions can't call nondeterministic functions.

      Non-IO functions can't call IO functions.

  • epolanski 1 hour ago
    Everything he lists is solved by effect-ts [1] bar, obviously, the language support (effect has its own fiber-based runtime like ZIO's scala).

    I've been using it for 5+ years and my 4 men team can scale to supporting 6 different products (each running millions $ in business, sometimes daily), as we reuse the same patterns and architecture. This would not be possible without Effect, even though I'm lucky to have terrific engineers as colleagues, we just wouldn't be able to without the endless goodies from Effect.

    The amount of features is basically endless, as effects and runtimes weren't enough, from SQL to AI, from effectful schemas (encoders/decoders), first-class OTEL support, CLI, debuggers, editor extensions, and many others. There's still countless modules I have yet to see or use.

    Runtimes are available for each platform, including cloudflare workers.

    There's absolutely nothing in TypeScript land to have such a wide scope.

    v4 will also bring durable workflows (I'm already using v4 beta and that feature in prod) and many other goodies. That's quite important for us needing to have procedures that need to survive redeploys, crashes, etc.

    I would never go back to writing standard TypeScript.

    There is a learning curve, but you can adopt it incrementally. Nobody adopting it has ever gone back.

    That being said, it would be great if there was a proper effect-based language (I've seen few projects like Effekt, but there's way too many things missing) as TypeScript is verbose, and effect adds its own verbosity.

    [1] https://effect.website/

    • satvikpendem 1 hour ago
      Effect is pretty nice, I'm not sure how worth it it is for the frontend, but I've heard good things on the backend, but sadly I don't use TypeScript for backend work, mainly Rust, and would love to see something like that there. I'm not sure how much Rust's type system would make it possible though however.

      I know parts of Effect like its schema are incrementally adoptable but if you use it substantially with many of its features, isn't it viral in a sense? In that you need to do things the Effect way and wrap libraries into Effect functions?

      • epolanski 1 hour ago
        It does tend to naturally bubble upwards as you point out, but you can decide where to stop.

        E.g. you could describe a complex effect that has retry, scheduling, etc and run it only once with `Effect.runFork(yourEffect)`

        That's in general how teams adopt it, in general there's a champion in the team that sells using one feature, and as people get accustomed and the champion does a good work mentoring it slowly takes over whole projects.

    • mthewood 1 hour ago
      The learning curve is quite steep but once you get it you become effect pilled. You can completely separate the WHAT the application does from HOW it will do it.
    • IceDane 17 minutes ago
      [dead]