Live data from Hacker News

Algebraic Effects for the Rest of Us

overreacted.io

81–90 of 91 posts

Re: Algebraic Effects for the Rest of Us

#81
post #78
post #72

Earlier quoted context omitted.

Here is an article by Andrej Bauer answering the exact question, "What is algebraic about algebraic effects and handlers?": https://arxiv.org/abs/1807.05923v2 Contrary to the claim from the comment you are replying to, according to Andrej Bauer, "algebraic" does not refer to the composition of different algebraic effects via composing their handlers. When you see "algebra," think "algebraic structure," i.e., a set of…

Ah, thanks, maybe this holds a clue! (Clearly I have been interested in getting to the bottom of this for a while.) So maybe an "algebraic effect" is one that's isomorphic to a free monad of a functor that itself is an algebraic data type. That seems to give an unambiguous specification for what it means to handle an effect (a natural transformation) and to take a "free product" of effects (sum the functors). On the…

This is the gist of Andrej Bauer's paper:

- An algebraic theory consists of a "signature" (specifying a set of operation symbols with their arities) and a set of equations about those operations. An algebraic theory is purely syntactic, and does not give meaning to the operations or equations.

- An interpretation of an algebraic theory maps each operation symbol to a concrete mathematical object. It maps syntax to semantics.

- A model is an interpretation in which the algebraic theory's equations hold.

- A free model of an algebraic theory, generated by a set, is basically a model that can be mapped to any other model of the theory generated by the set.

So, an example of an algebraic theory is a group (as in the algebraic structure itself, not the various instances of a group). Its signature consists of the binary operation of combining, the unary operation of inverse, and the nullary operation of identity. The models of a group are its instances, such as the integers under additions and subtraction, or permutation functions on a set. The integers are the free group (that is, the free model of the group) generated by the set {1}.

Algebraic theories can also describe the effects of a programming language (e.g. reader, writer, or state). The free model of an algebraic effect is given by the syntax trees describing its effectful computations. Effect handlers are maps from syntax trees, transforming computations into other computations within the programming language.

> On the other hand I think it would mean that things like Future and general IO wouldn't be algebraic effects.

For what it's worth, Example 2.3 in the paper states that IO is an algebraic effect, albeit one with no equations. What might be confusing is that the handler of the IO effect cannot have the level of concreteness to actually implement the IO operations. Effect handlers only map computations to computations within the "pure" programming language. From the paper:

> What we still lack is a mathematical model of computational effects at the level of the external environment in which the program runs. There is always a barrier between the program and its external environment, be it a virtual machine, the operating system, or the underlying hardware. The actual computational effects cross the barrier, and cannot be modeled as handlers. A handler gets access to the continuation, but when a real computational effects happens, the continuation is not available. If it were, then after having launched missiles, the program could change its mind, restart the continuation, and establish world peace.

Re: Algebraic Effects for the Rest of Us

#82
post #36
post #26

Earlier quoted context omitted.

A real effect system allows you to do things like NOT continue execution after using the effect (like the error effect does - if you "implement" this by using Exceptions, you're not using effects at all, just using Exceptions with extra steps) or only continuing it after some asynchronous work happens (the Future effect), or even "continue" execution several times. That just cannot be done with "just passing stuff in…

Thanks for your response. Perhaps I'm missing some fundamental things. Could you help? > A real effect system allows you to do things like NOT continue execution after using the effect Right, Bluefin's Request allows you to do that too. For example here is an example of handling the request by continuing or not, depending on what the value yielded to the Request is. example :: Either String () example = runPureEff $…

I think the parent may be getting at the continuation aspect of effects? Effect systems make the stack a first class object you can reuse, I think a standard example is implementing a scheduler. I'm not familiar with your Bluefin library so maybe it already handles this:

  effect Sched =
    yield : unit -> unit
    fork  : (unit -> unit) -> unit
  end
  
  let mut run_queue = []
  let enqueue t = run_queue := List.concat run_queue [t]
  
  let dequeue () =
    match run_queue with
    | [] -> ()
    | t :: rest ->
      run_queue := rest;
      t ()

  let rec spawn task =
    handle
      task ()
    with
    | return _ -> dequeue ()
    | yield () k ->
      enqueue (fn () -> resume k ());
      dequeue ()
    | fork f k ->
      enqueue (fn () -> resume k ());
      spawn f

  let run main = spawn main

  let worker name steps =
    let rec loop i =
      if i > steps do ()
      else do
        print $"{name}: step {i}";
        perform yield ();
        loop (i + 1)
      end
    in
    loop 1
  
  let () =
    run (fn () ->
      print "main: starting";
      perform fork (fn () -> worker "A" 3);
      perform fork (fn () -> worker "B" 3);
      print "main: forked workers, now yielding";
      perform yield ();
      print "main: done")
output:

  main: starting
  A: step 1
  B: step 1
  A: step 2
  main: forked workers, now yielding
  B: step 2
  A: step 3
  main: done
  B: step 3

Re: Algebraic Effects for the Rest of Us

#83
post #16

Earlier quoted context omitted.

Yes. dynamically scoped, and statically typed.

How can it be statically typed in JavaScript?

An effect system is an extension of a type system where a function type encodes inputs, outputs and effects.

The article mentions that they handwaved all the typing stuff (when you do that, effect handlers are more like delimited continuations). But the types are important. If a function doesn't tell you it's effects, how do you know you have to handle them? You'd have to read the whole call graph. That's why exceptions suck.

Re: Algebraic Effects for the Rest of Us

#84
post #81
post #78

Earlier quoted context omitted.

Ah, thanks, maybe this holds a clue! (Clearly I have been interested in getting to the bottom of this for a while.) So maybe an "algebraic effect" is one that's isomorphic to a free monad of a functor that itself is an algebraic data type. That seems to give an unambiguous specification for what it means to handle an effect (a natural transformation) and to take a "free product" of effects (sum the functors). On the…

This is the gist of Andrej Bauer's paper: - An algebraic theory consists of a "signature" (specifying a set of operation symbols with their arities) and a set of equations about those operations. An algebraic theory is purely syntactic, and does not give meaning to the operations or equations. - An interpretation of an algebraic theory maps each operation symbol to a concrete mathematical object. It maps syntax to se…

> Example 2.3 in the paper states that IO is an algebraic effect

Oh, I meant what Haskell calls `IO`, which includes the ability to launch threads, use delimited continuation primops, abort the program, communicate with the FFI, and all sorts of other things that I would guess don't have an algebraic presentation.

Re: Algebraic Effects for the Rest of Us

#85
post #77

Earlier quoted context omitted.

Right, I understand the history (although I'm not sure I'd say that exception don't compose well) and I understand that "algebraic effects" are an attempt at something better. But I don't understand whether they're something that can be precisely defined or just informal terminology for "a better sort thing for dealing with effects".

You can precisely define any particular model, but not all work in the area shares the same model. I think you know about the capability-passing model, which is quite different to the algebraic effects (e.g. row types) models. The general ideas are: * effects are handled by handlers (called capabilities in the capability-passing model) * function signatures describe the effects that are used * effectful code is writt…

Thanks! This begins to make more sense to me

> effects are handled by handlers

OK, and in the general case a handler allows its body to "perform" an action, and when the action is performed it has the ability to "respond" to it in (in some cases) a very flexible way, running it never, or multiple times, or in a modified environment, or possibly even passing it out of the scope of the handler entirely.

> function signatures describe the effects that are used

Would you say this is not possible in an untyped language then?

> effectful code is written in direct style, not monadic style

I don't understand the distinction here

Re: Algebraic Effects for the Rest of Us

#86
post #36

Earlier quoted context omitted.

Thanks for your response. Perhaps I'm missing some fundamental things. Could you help? > A real effect system allows you to do things like NOT continue execution after using the effect Right, Bluefin's Request allows you to do that too. For example here is an example of handling the request by continuing or not, depending on what the value yielded to the Request is. example :: Either String () example = runPureEff $…

I think the parent may be getting at the continuation aspect of effects? Effect systems make the stack a first class object you can reuse, I think a standard example is implementing a scheduler. I'm not familiar with your Bluefin library so maybe it already handles this: effect Sched = yield : unit -> unit fork : (unit -> unit) -> unit end let mut run_queue = [] let enqueue t = run_queue := List.concat run_queue [t]…

Ah yes, OK, I missed the point that the timeout is applied to the entire continuation, not just the part of the computation until the next await. Bluefin can't currently do that. I think I could make it do that, using the same implementation strategy as awaitYield (fork a thread, communicate through an MVar) but I wonder what the point is, given that Bluefin allows you to run the continuation at most once. Is the use case of "run the continuation in a modified environment (e.g. with a timeout)" really that compelling? Maybe it is! But I don't see it yet.

On the other hand, I don't see any difficulty with implementing a scheduler using Await/Yield. I don't think it needs access to the full continuation.

Re: Algebraic Effects for the Rest of Us

#87
> How Is All of This Relevant to React?

> Not that much. You can even say it’s a stretch.

No mention of React's built-in Context? Because this is really similar. Not exact for sure, this particular example wouldn't work for a few reasons, but as a comparison this looks to just be a more generic version of that.

Re: Algebraic Effects for the Rest of Us

#88
post #85

Earlier quoted context omitted.

You can precisely define any particular model, but not all work in the area shares the same model. I think you know about the capability-passing model, which is quite different to the algebraic effects (e.g. row types) models. The general ideas are: * effects are handled by handlers (called capabilities in the capability-passing model) * function signatures describe the effects that are used * effectful code is writt…

Thanks! This begins to make more sense to me > effects are handled by handlers OK, and in the general case a handler allows its body to "perform" an action, and when the action is performed it has the ability to "respond" to it in (in some cases) a very flexible way, running it never, or multiple times, or in a modified environment, or possibly even passing it out of the scope of the handler entirely. > function sign…

> OK, and in the general case a handler allows its body to "perform" an action...

Yes, although not all systems allow this, as implementing full continuations is involved and can hurt performance.

> Would you say this is not possible in an untyped language then?

You can definitely implement the ideas of algebraic effects in an untyped language, but you lose one of the benefits.

> I don't understand the distinction here

Monadic code is code where the order of evaluation is specified by bind / flatMap. Direct-style just uses the language's built-in control flow. See https://noelwelsh.com/posts/direct-style/ for more

Re: Algebraic Effects for the Rest of Us

#89

Earlier quoted context omitted.

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…

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)` in a random place of your existing code. 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 do…

It seems Effect doesn't actually have resumable continuations, looks like it's mainly a type driven ease of use library rather than a fundamental algebraic effects system right?

Re: Algebraic Effects for the Rest of Us

#90
post #5

Earlier quoted context omitted.

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…

‘Non-IO functions can't call IO functions.’ How do you handle logging then? If f() calls g(), how can I add logging to g() without having to change or recompile f() (and everything in the call stack above it)? ‘You can’t’ is not an acceptable answer.

If `left_pad()` calls `send_env_vars()`, how can you add exfiltration to `send_env_vars()` without having to change `left_pad()` to expose the use of the network?

"You can't" should be the ONLY acceptable answer.

Post reply on HN