[HN Gopher] Coroutines and effects
       ___________________________________________________________________
        
       Coroutines and effects
        
       Author : todsacerdoti
       Score  : 89 points
       Date   : 2024-04-20 14:39 UTC (1 days ago)
        
 (HTM) web link (without.boats)
 (TXT) w3m dump (without.boats)
        
       | nsm wrote:
       | I am not an expert on this either, although very interested. Note
       | that delimited continuations, which are a superset of coroutines
       | are identical to effects in the sense of yielding control to the
       | handler, not the caller. In languages like Racket/Scheme which
       | allows associating tags with handlers, you can also model the
       | same function having multiple effects.
       | 
       | In general, coroutines seem vastly easier to understand/type
       | compared to effects unless your language can do a lot of effect
       | inference.
        
       | angiosperm wrote:
       | After scrutiny this seems to be "Coroutines and Effects" in,
       | particularly, Rust.
        
         | saghm wrote:
         | Given that withoutboats is a long-time major contributor to
         | Rust, I suspect they titled the blog post with the audience of
         | regular readers of their blog in mind rather than a more
         | general audience like Hacker News
        
           | steveklabnik wrote:
           | It's more than that:
           | https://bsky.app/profile/without.boats/post/3kql3yr3goc23
           | 
           | > Btw this is the beginning of me trying to shift away from
           | blogging about rust to blogging about PL design in general. I
           | find that I have very little to say about Rust that I haven't
           | already said.
        
       | armchairhacker wrote:
       | What's the difference between an effect and a "coroutine" where
       | calls in the function body to other coroutines are implicitly
       | yielded? Or is that not a real coroutine?
       | 
       | That's what Kotlin's `suspend fun` does.
        
         | Rusky wrote:
         | Both effects and coroutines can (and typically do) work
         | implicitly across calls like that. The version with forwarding
         | operators like in this post is something of a Rust-ism, or more
         | generally an async/await-ism, or a stackless coroutine-ism.
         | 
         | The more fundamental difference between effects and coroutines
         | is that a `yield` in a coroutine always goes to the one unique
         | resumer, and carries a single type of value. On the other hand,
         | an expression may have several effects, each of which may be
         | handled in a different place, in the same way that distinct
         | exception types may be caught in different places.
        
       | rc_mob wrote:
       | I just upvoted this because I love the domain name.
        
       | mrkeen wrote:
       | I'm not really understanding the comparison (or isomorphism)
       | between coroutines and effects. Feels like comparing Lists with
       | functions, or promises with interfaces.
        
         | alephaleph wrote:
         | A coroutine is a computation that can `yield`, suspending
         | itself and passing control back up to the caller. It can then
         | be resumed at the caller's leisure.
         | 
         | An function with an effect (in this sense) is a function which
         | can ask a handler to `perform` some effect for it. This
         | suspends the function and passes control to whichever handler
         | is in scope for that call, allowing that handler to resume the
         | function at its leisure.
         | 
         | I suspect that you're misunderstanding what is meant by effect,
         | because despite buzz about them and backend support for them in
         | OCaml 5, they aren't yet implemented with syntax and type-level
         | support in any mainstream languages I'm aware of.
        
         | noelwelsh wrote:
         | I don't know your background, so I don't know at what level to
         | pitch an explanation. Here's an attempt that assumes some
         | knowledge of modern PLs.
         | 
         | Effects require 1) well-defined control flow (think of IO; you
         | need to know in what order output occurs) and 2) manipulation
         | of control flow (think of error handling or concurrency).
         | 
         | We can model effects as a back-and-forth between effect
         | handlers, which carry out effects, and the user program. The
         | user program passes control to the effect handler to carry out
         | some effect, and the effect handler passes control back to the
         | user program (potentially a different part of the user program;
         | think error handling) when the effect has been performed.
         | Continuations give complete control over control flow, so in
         | their full generality effects require continuations (or some
         | equivalent like monads). Coroutines are a slightly stilted form
         | of continuations, that you can model much, but not all, control
         | flow with.
        
           | zozbot234 wrote:
           | Aren't coroutines generally one-shot, whereas continuations
           | could potentially be resumed to multiple times? This seems to
           | be a relevant difference between these concepts.
        
             | dkjaudyeqooe wrote:
             | No, the whole point of coroutines is that you can resume
             | them multiple times, otherwise it's just a simple function
             | call.
        
               | Rusky wrote:
               | That's not what "resume multiple times" is referring to
               | here. You can typically only resume a coroutine once _per
               | yield,_ while a continuation generally allows you to
               | return to the _same place_ multiple times.
        
       | et1337 wrote:
       | Wow I've been thinking of something exactly like this! Sort of a
       | super charged, statically typed version of Go's context.Context.
       | It would allow you to describe the capabilities (or effects) of
       | every function, including IO, memory allocation, cancellation,
       | deadlines, concurrency, and whatever application-specific stuff
       | you need on there (like logging).
       | 
       | Then you could implement something like Google capslock [0] just
       | by looking at type signatures.
       | 
       | 0. https://github.com/google/capslock
        
       | amluto wrote:
       | This touches on something I'd like to see in more mainstream
       | languages, but it doesn't quite get there.
       | 
       | > For example, Koka has a "diverging" effect, which means that an
       | expression may diverge (that is to say, it may not finish
       | evaluating). An expression containing a diverging expression is
       | also diverging. So you can distinguish in the type system between
       | a function that is guaranteed to finish and a function that may
       | not finish (this is imperfect, of course, because of the
       | undecidability of the halting problem; some functions that do not
       | diverge will be marked diverging).
       | 
       | As I think about it (and I'm not a programming language theorist,
       | nor have I done much serious work in any language with any sort
       | of effect system), there are two vague categories of effect:
       | control-flow effects like exceptions, yields, async waits
       | (Pending, sleep, or however you feel like modeling it) and non-
       | control-flow effects (divergence, various forms of unsafety,
       | nondeterminism, impurity, reads or writes of global state,
       | syscalls in models that don't treat IO in and of itself as an
       | effect), etc.
       | 
       | I would like to be able to run and write code that is
       | _definitely_ free of certain kinds of effects. Xz should not be
       | unsafe or do IO, for example. Leftpad is an entirely pure, non-
       | diverging function. And I should be able to ask my language to
       | enforce that, ideally with trivial code. Maybe even by default.
       | 
       | But mainstream languages seem to mostly limit their use of
       | effect-like systems on the control flow part, like this:
       | 
       | > Overall, coroutines strike me as the most promising way to
       | handle many kinds of effectful functions because they seem to be
       | in the design sweet spot: They are statically typed, lexically
       | scoped, and unlayered.
        
         | noelwelsh wrote:
         | This is one of the motivations of algebraic effects: you can
         | have fine grained control over which effects are allowed.
        
         | Rusky wrote:
         | There are two aspects to effects as a language feature- first
         | there is the runtime behavior, and then there is the type
         | system. Ruling out categories of effects is the job of the type
         | system, and you don't have to commit to (or avoid) any
         | particular runtime approach to use it that way.
         | 
         | This is related to the vagueness of your two categories. While
         | exceptions, yields, and awaits don't really make sense without
         | a handler, all that matters to the type system is which
         | operations an expression might perform _in addition to_
         | producing a result of their primary type. Handlers only
         | interact with the type system in the sense that they remove an
         | effect from their handle-ee expression.
         | 
         | So even in a language that committed to coroutines as its
         | approach handling effects at runtime, the type system could
         | still track and rule out effects like divergence or system
         | calls. (For what it's worth, nondeterminism, state, etc. can
         | all also be defined in terms of handlers. And at the same time,
         | though, unsafety is not an effect because it is not entirely
         | captured by "can this expression perform operation X.")
        
         | newpavlov wrote:
         | Yeah, the whole blog post reads like "if you worked too much
         | with a hammer, everything starts looking like a nail". In other
         | words, it puts the cart before the horse.
         | 
         | Effects is a MUCH more general and powerful technique than
         | coroutines. An effect system can help greatly with implementing
         | a coroutine system (instead of dealing with the unfortunate
         | type-level hack Rust uses), but trying to implement an effect
         | system on top of a coroutine system will give you a very
         | limited emulation in the best case and another incoherent mess
         | in the worst.
         | 
         | It would've been really great if Rust had a proper effect
         | system, but it's very hard to introduce it into an existing
         | language similarly to how you can not easily introduce borrow
         | checker to C/C++. The messy "keyword generics" proposals only
         | serve as a good demonstration for this. So we probably have to
         | wait for Rust 2 or a different successor language.
        
         | klabb3 wrote:
         | > Leftpad is an entirely pure, non-diverging function.
         | 
         | `leftpad(string, int) -> string` needs to allocate so not fully
         | pure that way. You could pass in the resulting string but then
         | it would have to mutate, which is not really pure either.
        
           | atlintots wrote:
           | It would be pure in languages without manual memory
           | management
        
       ___________________________________________________________________
       (page generated 2024-04-21 23:00 UTC)