Live data from Hacker News

Algebraic Effects for the Rest of Us

overreacted.io

71–80 of 91 posts

Re: Algebraic Effects for the Rest of Us

#71
post #16

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?

Yes. dynamically scoped, and statically typed.

How can it be statically typed in JavaScript?

Re: Algebraic Effects for the Rest of Us

#72
post #38
post #33

(2019) It’s worth clarifying that for the most part, this article just discusses plain effects, ie. "reified side effects" or "resumable exceptions". Algebraic effects are about the composition (ie. algebra) of effects, exactly like algebraic data types are about the composition of types. This part is generally not meaningful in an untyped language like Javascript because effects are all YOLO and you never know what’…

I've never understood what's meant by "algebraic" in that sense, or what this algebra of combining effect is. Could you say more, or maybe share a link to a useful reference?

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 operations and a set of equational laws on those operations.

An algebraic effect consists of a set of effectful operations. In that sense, each algebraic effect (reader, state, etc.) defines its own algebraic structure. In theory, the operations of an effect should also be related to each other by laws. Here is an example for the state effect from Andrej Bauer's paper:

    lookup(ℓ,λs.lookup(ℓ,λt.κst)) = lookup(ℓ,λs.κss)
    lookup(ℓ,λs.update((ℓ,s),κ)) = κ()
    update((ℓ,s),λ_.lookup(ℓ,κ)) = update((ℓ,s),λ_.κs)
    update((ℓ,s),λ_.update((ℓ,t),κ)) = update((ℓ,t),κ)
Therefore, the state effect has an algebraic structure.

However, monads that cannot be defined in this equational style cannot be translated to algebraic effects. An example is the continuation monad: https://old.reddit.com/r/haskell/comments/44q2xr/is_it_possi... (I think you are involved in the Reddit comment chain that I'm linking to...)

AFAIK, another example is the "exception" monad, because the way that you interact with it is through the handler itself. I once saw a thread on r/Haskell discussing this, but can't find it.

Re: Algebraic Effects for the Rest of Us

#73

While the effect system seems way more general, it’s a bit sad the example only talk about logging or IO, both of which can be simply done with dependency injection

It's my belief that all effects can be done with dependency injection in some form, at least, I'm not familiar with ones that can't. Even arbitrary delimited continuations can be implemented by injecting a reference to the continuation prompt on the stack.

Re: Algebraic Effects for the Rest of Us

#74
post #61
post #38

Earlier quoted context omitted.

I've never understood what's meant by "algebraic" in that sense, or what this algebra of combining effect is. Could you say more, or maybe share a link to a useful reference?

This is my favorite one, short and understandable: https://philipnilsson.github.io/Badness10k/posts/2017-05-07-... I show it when I teach Haskell, and it's what usually makes it "click" for students. Probably because motivating examples are in normal imperative pseudocode.

One thing this page makes clear is that do-syntax could mean all kinds of things, which seems like a disadvantage for readability. Assuming you know the specialized syntax, elvis operators looking different from async code or a nested for loop seems like an advantage? The performance implications are entirely different.

Re: Algebraic Effects for the Rest of Us

#75
post #64
post #61

Earlier quoted context omitted.

This is my favorite one, short and understandable: https://philipnilsson.github.io/Badness10k/posts/2017-05-07-... I show it when I teach Haskell, and it's what usually makes it "click" for students. Probably because motivating examples are in normal imperative pseudocode.

Thanks, I definitely feel like I understand monads and their benefits, and even "effects", but I'm not sure what's "algebraic" in "algebraic effects".

The problem with monads is their composition. Ie. questions like how is the do notation supposed to work if I want to return:

* an Option or Async?

* both an Option and an Async (a product, or tuple type)?

* either an Option or an Async (a sum, or tagged union type)?

Monad transformers can be written for wrapping monads into other monads (as a simple example, an Option is equivalent a List with exactly zero or one elements), but they're something of an ad hoc solution and do not generalize well.

This is fundamentally an issue similar to the "function color" problem, or the fact that exceptions in most languages are a limited, ad hoc effect system and do not compose well. Java gave checked exceptions a bad name largely because of their lack of compositionality, but it's more that the particular implementation is poor.

To be fair, that was in 1994 and nobody had worked out these things yet even in the academia. Algebraic effects are the attempt to do just that.

Re: Algebraic Effects for the Rest of Us

#76
post #46

As the article notes, effects (at least as described in the article) are already possible in JavaScript using generators, as long as every calling function is a generator, which I assume the author finds problematic because generators are cumbersome. I was wondering how well TypeScript can type generator-based effects. My hypothesis is that TypeScript can let you compose functions with effects, but it is not possible…

Look into Effect TS.

https://effect.website/docs

Re: Algebraic Effects for the Rest of Us

#77
post #75
post #64

Earlier quoted context omitted.

Thanks, I definitely feel like I understand monads and their benefits, and even "effects", but I'm not sure what's "algebraic" in "algebraic effects".

The problem with monads is their composition. Ie. questions like how is the do notation supposed to work if I want to return: * an Option or Async ? * both an Option and an Async (a product, or tuple type)? * either an Option or an Async (a sum, or tagged union type)? Monad transformers can be written for wrapping monads into other monads (as a simple example, an Option is equivalent a List with exactly zero or one e…

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".

Re: Algebraic Effects for the Rest of Us

#78
post #72
post #38

Earlier quoted context omitted.

I've never understood what's meant by "algebraic" in that sense, or what this algebra of combining effect is. Could you say more, or maybe share a link to a useful reference?

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 other hand I think it would mean that things like Future and general IO wouldn't be algebraic effects.

Re: Algebraic Effects for the Rest of Us

#79
post #77
post #75

Earlier quoted context omitted.

The problem with monads is their composition. Ie. questions like how is the do notation supposed to work if I want to return: * an Option or Async ? * both an Option and an Async (a product, or tuple type)? * either an Option or an Async (a sum, or tagged union type)? Monad transformers can be written for wrapping monads into other monads (as a simple example, an Option is equivalent a List with exactly zero or one e…

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 written in direct style, not monadic style

Re: Algebraic Effects for the Rest of Us

#80
post #24

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?

That's what I was thinking. You could get almost all of this pretty directly in Javascript by putting a callback function in an AsyncLocalStorage instance or, in other languages, in a thread local variable.

I was thinking this through more and realized that a callback directly in a thread local variable isn't quite enough: You need to keep a stack in the thread local variable. Every try-handle block pushes a callback in and pops it when exited. (Javascript's AsyncLocalStorage is naturally nestable so you don't need to manage a stack yourself with it.)
Post reply on HN