It seems that industry programming languages such as Rust or Scala are studying the implementation of an effects system. Is there any Julep about it?
For reference:
- Rust:
Extending Rust's Effect System - Scala:
CanThrow Capabilities
It seems that industry programming languages such as Rust or Scala are studying the implementation of an effects system. Is there any Julep about it?
For reference:
Just to preempt any misunderstanding, OP isn’t talking about @assume_effects
, but about algebraic effects, effect handlers, etc, like in Koka and Effekt.
There was a nice announcement recently from @bertschi that they were working on a common lisp style condition system: [ANN] Package Conditions.jl
I’m not particularly knowledgeable about the topic, but my understanding is that lispy condition systems are essentially the same thing as the algebraic effect systems that are getting popular these days.
I hadn’t gotten around to figuring out what algebraic effects are all about but this blog post supports the notion that they’re a condition/restart system adapted for Hindley-Milner typed languages. Which Julia is not, so it’s better for this language to use the original terminology.
I’m quite stoked to see the new package work out all the kinks, and hope that condition/restart will migrate into the standard library, and start to be incorporated into parts of the code which currently throw errors, such that try/catch is just the base case for code without more specific handlers.
I might be missing something crucial, but I think we can already do this today with ScopedValues. For example, taking the error-handling example from the blog post @mnemnion linked, we can write this in Julia as
using ScopedValues
function getName(user)
name = user.name
if name === nothing
name = perform("ask_name")
end
return name
end
function makeFriends(user1, user2)
push!(user1.friendNames, getName(user2))
push!(user2.friendNames, getName(user1))
end
const arya = (; name=nothing, friendNames=[])
const gendry = (; name="Gendry", friendNames=[])
const HANDLER = ScopedValue{Union{Nothing, Function}}(nothing)
function perform(effect)
handler = HANDLER[]
isnothing(handler) && error("Could not perform effect $effect; no handler")
return handler(effect)
end
with(HANDLER => function(effect)
if effect === "ask_name"
return "Arya Stark"
end
error("No handler for effect $effect")
end) do
makeFriends(arya, gendry)
end
@show arya.friendNames gendry.friendNames;
which results in
arya.friendNames = Any["Gendry"]
gendry.friendNames = Any["Arya Stark"]
(ScopedValues are in Base in v1.11 due to this PR, and available in earlier versions via https://github.com/vchuravy/ScopedValues.jl.)
edit: I see Conditions.jl uses ScopedValues, so I guess their relevance here is old news
5 posts were split to a new topic: Naming of ScopedValues
As Conditions.jl
has been mentioned, a few comments:
While a condition system can be used to emulate some effects, it does not reify the continuation at the point a condition is thrown. In contrast, effects such as concurrency, e.g., via async/await
, require at least one-shot continuations whereas non-determinism requires full (delimited) continuations that can be called multiple times.
Further, implementing effects efficiently often requires some code-rewriting (see here, how Clojure implements go
blocks by compiling them into a state-machine aka one-shot continuations) or deeper compiler support (via stack fibers as in MulticoreOCaml), In addition, there are semantic differences such as shallow or deep handlers as well as the desire of tracking effects in the type system in statically typed languages.
This sort of thinking was what prompted my comment about Hindley-Milner type systems. It doesn’t really make sense to talk about algebraic effects in a language lacking an algebraic type system.
Since I find Julia’s type system both powerful and refreshing, I’m more than alright with the various programming features which an ML-family language might model as effects, being available in different forms as distinct features, such as condition-restart. There is a tradeoff between expressiveness and composability in some cases, continuations being a good example.
There’s a certain mentality in those circles about programming, which emphasizes mathematical clarity above any other consideration. I’m glad they exist and wish their practitioners all the best in life, but it’s a focus I don’t happen to share.
Martin Odersky, lead developer of Scala, talked about how Scala is tackling this issue.