Concurrent System Programming with Effect Handlers

From 2017 cf. github repo of materials for CUFP 17 tutorial about it

Stephen Dolan Spiros Eliopoulos Daniel Hillerström Anil Madhavapeddy KC Sivaramakrishnan and Leo White

Abstract. Algebraic effects and their handlers have been steadily gaining attention as a programming language feature for composably expressing user-defined computational effects. While several prototype implementations of languages incorporating algebraic effects exist, Multicore OCaml incorporates effect handlers as the primary means of expressing concurrency in the language. In this paper, we make the observation that effect handlers can elegantly express particularly difficult programs that combine system programming and concurrency without compromising performance. Our experimental results on a highly concurrent and scalable web server demonstrate that effect handlers perform on par with highly optimised monadic concurrency libraries, while retaining the simplicity of direct-style code.

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.

Effects vs Capability Security Model

BTW what is logical relation of Effects and Capability Security Model?

I've recently looked at a number of presentations on tagless final that try to argue how they control possible effects, and from what I see all that could be done in capability security model in a cleaner form.

And capability security model is basically a good OOP with static external state interactions disabled. The possibly to interact with something external should be passed as parameter to method or constructor. In tagless final, effects are introduced in a structurally similar way, but with a way more ritual.

re: effects vs ocaps

I think the important difference is that references are pervasive and implicit for ocaps but optional and explicit for algebraic effects.

Although references are expressive and convenient, they are also troublesome for some scenarios, such as modeling mobile agents. References too easily entangle program state with the environment, support spooky action at a distance, hinder caching and incremental computing, complicate garbage collection and relationship tracking, etc..

I'm curious how you would

I'm curious how you would implement direct-style async/await as per the paper using capabilities.

async await with caps

The chosen model of effects handlers includes a continuation parameter.

Most ocap languages lack continuation capture. But there is no fundamental reason for this, e.g. we could model some runtime reflection objects that can capture continuations. Then we could similarly implement async/await.

Conversely, my glas language has effects handlers but no support for dynamic linking (no first class functions, no continuations, etc.). Thus, glas cannot directly implement the concurrency solution described in this paper.

Arguably, this paper is more about clever use of continuation capture to model concurrency than it is about using effects handlers to model concurrency.

Arguably, this paper is more

Arguably, this paper is more about clever use of continuation capture to model concurrency than it is about using effects handlers to model concurrency

The point I was trying to make is that capabilities implicitly reduce to reader/writer effects, but there exist many effects beyond this model, and you can express these other types of effects in an effect system without having to add more axioms (primitives in a runtime), like a capability-protected continuation capture primitive.

So per const's original point, capabilities seem simpler and more intuitive because programmers have lots of experience with reader/writer effects, and a framework that can capture more novel effects would look more cumbersome if all you're thinking about is effects that can be modeled using readers/writers.

effects with or without continuations

I agree that we shouldn't just consider effects suitable for one model when comparing frameworks. Otherwise comparison is too biased.

But I can't reasonably compare caps vs effects unless all else is equal. Including continuation capture in one effects framework is useful for modeling some effects that are difficult to model without them. But this clearly outside scope of a comparison of effects handlers vs ocaps a la carte.

I could implement ocap programs in a continuation passing style, i.e. where continuations are implicit object parameters. This would support continuation capture. No primitives needed, just pervasive syntactic sugar. Actors-based systems are ocap-compatible and essentially work this way: returns are message sends to a continuation actor.

AFAIK, the essential structural difference between effects handlers and object capabilities is that effects handlers have a rigid hierarchical structure. Effects of a subprogram are always routed through a local parent program, whereas effects with caps are routed through a global runtime.

So it's really a question of whether locality offers some benefits in contrast to its limitations. And it does - locality limits entanglement with the environment, which can be useful for modeling mobile code or scoped backtracking.

Instead of async/await, backtracking is perhaps a more interesting example of an effect that is relatively easy with handlers but absurdly difficult with caps.

I could implement ocap

I could implement ocap programs in a continuation passing style, i.e. where continuations are implicit object parameters. This would support continuation capture. No primitives needed, just pervasive syntactic sugar.

Sure, continuation capture for a program in CPS form is effectively a no-op, but I'd still count that as a runtime primitive because the continuation in your scenario would be a hidden, second-class value if not for this operation.

An effect system also would only need to translate a program into CPS form in specific circumstances, so it's strictly superior in terms of runtime performance.

My summary is: adding an effect system is akin to adding a more powerful type system in order to eliminate whole classes of dynamic type checks and guards. For some languages it's warranted, and for others it would add more complexity than it's probably worth, in which case capabilities and more cooperation between the compiler and runtime would be sufficient to get the desired result.

effects, types, and continuations

adding an effect system is akin to adding a more powerful type system in order to eliminate whole classes of dynamic type checks and guards

We could still use an algebraic effects system without types, and it is even useful and convenient to do so.

To claim the effects system extends the type system is backwards. Instead, the type-system must be extended to reason about the effects system.

An effect system also would only need to translate a program into CPS form in specific circumstances, so it's strictly superior in terms of runtime performance.

This claim seems optimistic. For example, if you have first-class functions that perform effects, a compiler often won't locally know which effects handler will handle those effects. Thus, they'd often be compiled for the general case.

When you have circumstances where you know exactly which handler will be applied, i.e. due to some partial evaluation and local static linking, then you could optimize away CPS support in some cases. But under the same conditions, there would be optimization benefits for other models, including ocaps.

continuation capture for a program in CPS form is effectively a no-op, but I'd still count that as a runtime primitive

That's fine. My point is that, for a fair comparison of effects handlers vs ocaps, this continuation capture runtime primitive should either be present in both cases or absent in both cases. Otherwise the expressiveness of continuation capture can easily overshadow choice of effects model.