Is it possible to implement effect handlers using Julia's coroutines?

Viewed 81

I am new to each of: Julia, coroutines and effect handlers, so what I am going to ask might be misguided, but is it possible to implement effect handlers using coroutines? I think that Scheme's coroutines would allow you to grab the rest of the computation block for later resumption which would allow implementing effect handlers, but Julia's coroutines seem to not have that functionality. Is that wrong, or is the only choice to do the CPS transform like the library I linked to and base the EH implementation on that?

1 Answers

There's a lot I don't know about your question, but in Julia the "low level" way to implement Task control-flow is via a Channel. There's a nice page on this in the manual. When constructed with size 0, channel manipulations are blocking until removal is completed. Here's an example:

julia> c = Channel{Int}()    # specify the type (here `Int`) for better performance, if the type is always the same
Channel{Int64}(0) (empty)

julia> sender(x) = put!(c, x)
sender (generic function with 1 method)

julia> receiver() = println("got ", take!(c))
receiver (generic function with 1 method)

julia> receiver()          # this blocked until I hit Ctrl-C
^CERROR: InterruptException:
Stacktrace:
 [1] poptask(W::Base.InvasiveLinkedListSynchronized{Task})
   @ Base ./task.jl:760
 [2] wait()
   @ Base ./task.jl:769
 [3] wait(c::Base.GenericCondition{ReentrantLock})
   @ Base ./condition.jl:106
 [4] take_unbuffered(c::Channel{Int64})
   @ Base ./channels.jl:405
 [5] take!(c::Channel{Int64})
   @ Base ./channels.jl:383
 [6] receiver()
   @ Main ./REPL[3]:1
 [7] top-level scope
   @ REPL[4]:1

julia> t = @async receiver()
Task (runnable) @0x00007f0b288f4d30

julia> sender(5)
got 5
5

julia> sender(-8)               # this blocks because the `receiver` task finished 
^CERROR: InterruptException:
Stacktrace:
 [1] poptask(W::Base.InvasiveLinkedListSynchronized{Task})
   @ Base ./task.jl:760
 [2] wait()
   @ Base ./task.jl:769
 [3] wait(c::Base.GenericCondition{ReentrantLock})
   @ Base ./condition.jl:106
 [4] put_unbuffered(c::Channel{Int64}, v::Int64)
   @ Base ./channels.jl:341
 [5] put!(c::Channel{Int64}, v::Int64)
   @ Base ./channels.jl:316
 [6] sender(x::Int64)
   @ Main ./REPL[2]:1
 [7] top-level scope
   @ REPL[7]:1

julia> t = @async while true receiver() end      # run the receiver "forever"
Task (runnable) @0x00007f0b28a4da50

julia> sender(-8)
got -8
-8

julia> sender(11)
got 11
11

In a real application, you should ensure that c isn't a non-const global, see the performance tips.

Related