http://logic.cs.tsukuba.ac.jp/~sat/pdf/tfp2020.pdf
In short: algebraic effects.
Here’s a whole thesis on the cool things that can do done with this one simple trick: https://publikationen.uni-tuebingen.de/xmlui/bitstream/handl...
http://logic.cs.tsukuba.ac.jp/~sat/pdf/tfp2020.pdf
In short: algebraic effects.
Here’s a whole thesis on the cool things that can do done with this one simple trick: https://publikationen.uni-tuebingen.de/xmlui/bitstream/handl...
"Professors HATE HIM"
I gave a talk about it recently at Michigan Typescript: https://www.youtube.com/watch?si=Mok0J8Wp0Z-ahFrN&v=uRbqLGj_...
https://github.com/thefrontside/effection
https://github.com/neurosnap/starfx
With delimited continuations, we are able to express any async flow control, it is an incredibly powerful paradigm.
In general every Monad can be expressed "interpreting" the free monad. This relaxes the "one-shot" restriction, and can be implemented using delimited control. One-shot means faster performance though and is still useful for many things - and that can be implemented using coroutines.
If you acquire some understanding of what this means then you'll have a very good idea about the expressive power of what you can use coroutines (with nothing more) for, so it's very interesting.
A quick scan of https://okmij.org/ftp/Haskell/extensible/more.pdf doesn't yield much one way or another.
Haskell ends up being a bad place to talk about this (or a great place, depending on your goals) because due to laziness you get to write a lot of structures which look inductive but end up being able to express coinductive structures.
From memory and intuition, if you're working in a strict language (or better yet, something like Agda where the distinction becomes very sharp) then you end up finding that continuations make for good coinductive "free" structures and the free monad (and its ilk) make for good inductive free structures.
The distinction between inductive and coinductive types is fairly subtle and hard to see in most languages where those distinctions are blurred, but broadly you can think of inductive structures as ones that are, in principle, finite and coinductive structures as being those which may be, in principle, infinite.
For example, a linked list is inductive. If you're looking at one cons cell of it you can't prove that, you may have to chase pointers for longer than your patience allows, but at least in principle there is an end. A stream is coinductive, because it instead suggests a generative process.
It's just the way we force the results to appear that differs.
IO Monad, if you squint, is extremely similar.
Continuations are a different way to "store a procedure and state" (like a partially evaluated function in a lazy sense).
It's not totally obvious, but it's a lot of fun to think about how these things are all related.
In languages such as C++ and Java, raising an exception defaults to an unwinding only if not handled within the same function that raised it.
Then there are languages with resumable exceptions, and languages wherein unwinding is considered normal control flow as "shortcut returns".
A handler for a resumable exception is passed down the call stack, rather than an object being passed up the call stack to the handler. In some languages the handler can conditionally decide whether to resume back into or restart the block that raised the exception, or to cause an unwinding of the stack. In other languages, the action is fixed per exception type. (`try ... rescue`)
Exceptions are merely the record of the programmer's mistake. Essentially an error, except an error that could have been caught at compile time given a sufficiently advanced compiler.
Exception handlers provide a control flow for dealing with exceptions, which may include unwinding.
Exception handlers, while primarily intended for use by exceptions, are not necessarily restricted to exceptions. Often programmers use them to move other things around, most notably errors.
From an interface-contract point of view, exceptions model the case that an operation cannot complete normally. Any mechanism that enables an operation to complete normally after all upon encountering an error condition, should better be modeled with a separate language feature, for example with callbacks specific to the concrete error condition.
Instead, define effects that are actually relevant to your particular app. DB reads/writes, I/O to third party systems.
Model those as data-driven effects. Keep the rest pure.