Algebraic Effects for the Rest of Us
71–80 of 106 posts
Re: Algebraic Effects for the Rest of Us
#72Re: Algebraic Effects for the Rest of Us
#73What is 'algebraic' about algebraic effects?
Maybe that's where they got the name from? Although performing the effects directly through a pause/resume mechanism doesn't sound algebraic to me.
Re: Algebraic Effects for the Rest of Us
#74Earlier quoted context omitted.
The article only gives a limited example of algebraic effects. The thing that is required for a condition system (ie good resumable exceptions) is lexical non-local transfer of control and doesn’t need algebraic effects (Common Lisp had a condition system but not algebraic effects in the late 1980s). Lexical non-local transfer of control is effectively the ability to write code which looks like (made up JS like synta…
One type of refactoring is to replace recursion with iteration. You could replace iterate_big_datastructure with an iteration class. I know, it will require dynamic memory allocation. But do not forget, that you can also run out of stack if you allow unlimited recursion. Usually, the heap is much bigger than the stack, especially if you are working with a lot of threads that all require their own stack. In a sense it…
A similar transformation would be converting to CPS with a big trampoline. Both of these differ from lexical goto and algebraic effects in a few ways:
- they require annoying manual/automatic code transformations that may make your code slower
- they don’t necessarily provide much safety
- they can be hard to type
- they can be hard to compose
These are issues which algebraic effects aim to solve.
Note that these techniques are strictly more powerful than lexical goto which only lets you unwind the stack to a particular tack frame (and recall unwinding means you forget about everything unwound).
Re: Algebraic Effects for the Rest of Us
#75The author ought to look into and write about the Common Lisp condition system, which allows error handlers to invoke restarts at different parts of the call stack. [1] The long-story-short on them is that they decouple the treatment of exceptional situations (or conditions ) into three orthogonal roles: signaling the condition (akin to “throwing”), handling the condition (akin to “catching”), and recovering from the…
Re: Algebraic Effects for the Rest of Us
#76Re: Algebraic Effects for the Rest of Us
#77Earlier quoted context omitted.
The article only gives a limited example of algebraic effects. The thing that is required for a condition system (ie good resumable exceptions) is lexical non-local transfer of control and doesn’t need algebraic effects (Common Lisp had a condition system but not algebraic effects in the late 1980s). Lexical non-local transfer of control is effectively the ability to write code which looks like (made up JS like synta…
Question about forking: isn't this essentially the same thing as restarts, after the garbage collector gets rid of the original (and now dangling) stack branch? Or is there something you can do with both of these branches simultaneously that's not expressible in conditions/restarts system?
A common example for an application of algebraic effects would be implementing async/await like mechanisms. The effect handler would take the continuations and put them into a scheduler, so it’s stack would effectively be heavily forked.
Re: Algebraic Effects for the Rest of Us
#78Where does the name come from? The word "effects" makes me think of "side effects", which is something I'd usually like to avoid.
Re: Algebraic Effects for the Rest of Us
#79The author ought to look into and write about the Common Lisp condition system, which allows error handlers to invoke restarts at different parts of the call stack. [1] The long-story-short on them is that they decouple the treatment of exceptional situations (or conditions ) into three orthogonal roles: signaling the condition (akin to “throwing”), handling the condition (akin to “catching”), and recovering from the…
Algebraic effects are more aimed towards statically typed languages like Haskell or Ocaml. They can replace most uses of monad transformers, which has both cognitive and performance benefits.
As you showed, there are better ways of solving the problem in lisps.
Tagless final algebras are another much more popular alternative that has been proven very effective in practical software. In tagless final, one writes composable DSLs (which are just records of functions) with the nature and interpretation of effects left abstract.
One then writes interpreters which interpret the DSL, giving meaning to the effects. This achieves the same fundamental goals as algebraic effects, but just using the ordinary language features of static FP languages.
[0] https://docs.racket-lang.org/reference/eval-model.html#%28pa...
Re: Algebraic Effects for the Rest of Us
#80Totally separate from stylistic quibbles, I also think effect systems are often oversold by folks who like them (usually, in my experience, folks from a functional background).
Fundamentally, a lot of the code we write is the effectful IO plumbing. By that I mean: there are very few complicated algorithms, or really any hand-written algorithms at all, in a large amount of code written for modern systems. Instead, the complexity and value of the code is in the way it coordinates different external IO sources/sinks. This is pretty well illustrated in the article's toy directory enumeration/file handling example: with the IO/system specific stuff extracted into the effect receivers (processors? handlers?), the remaining code is not just simple, it's vacuously simple. The complexity and trickiness of handling IO, dealing with error conditions, etc. all remains, though, in the effect receivers. This is subjective, but that seems more akin to the "over-extracting methods to the point where all you do is increase the line count" school of refactoring than the "improving the comprehensibility/maintainability of the code" school. Generifying IO interactions behind an effect system in a codebase that is primarily occupied with gluing together external systems results in moving so much of the code into effect receivers that nothing useful remains behind.
Put another way: often, what we're doing is intimately coupled with how: like, sure, I'm technically "piping data from a source into a sink with a transformer in between", but they don't pay me to write "source |> transformer |> sink", they pay me to write (for example) the SELECT statement in the source, the column mapping/reformatting logic of the transformer, and the POST-to-endpoint in the sink. If those things already existed, it would be the one-liner above, but they don't for the business domain, so we make them. Once they're written, by all means, modularize them and make them easily usable in a streamable, convenient way. But most of the interesting code, once you peel back the curtain on "source" or "sink" is still going to be in its effectfulness.
Then there's the argument from modularity/swappability: that you can replace effect handlers with equivalent handlers for doing other things. If you're writing a system with many swappable backends, this may be useful. However, most systems don't have that property. Datastores and effect receivers change much less often than the data flow itself. And past a certain point you end up with "old Java"-style modularity: things abstracted so far away in service to unneeded pluggability that the code becomes harder to follow and maintain (especially given that the code may be primarily/near-exclusively concerned with specifics of IO flow).
To be sure, there are some cases where effect systems can really help. I just don't think those are as numerous as FP proponents think they are.