Well after taking a closer look, I have to say that I am a big fan of YAActL’s interface. It might be more alien to most people, but if it can be wrapped in a faimiliar skin then you can have your cake and eat it. I am thinking about some things I’d like to try with the library, but please bear with me because I’m always over contended xD
BTW perhaps it would be a good idea to create a JuliaActors GitHub group and move our projects there @pbayer @oschulz?
FYI, the following is how I implemented the Agha’s stack in my test suite and actuallly this is more like the OOP way, but perhaps still somewhat alien:
# Special Actor which represents the Actor system
struct StackPlay end
# An Actor's state
mutable struct Stack{T}
# an item on the stack
content::Union{T, Nothing}
# next item on the stack, note that Id's are not like Links in YActl AFAICT
link::Union{Id{Stack{T}}, Nothing}
end
Stack{T}() where T = Stack{T}(nothing, nothing)
# message handler, the Scene is a context object passed to all handlers, avoids task local variables.
# I was playing with symbols here, but you could separate this into multiple handlers and dispatch on type instead
function hear(s::Scene{Stack{T}}, msg::Tuple{Symbol, Union{Id, T}}) where T
(type, m) = msg
if type === :push!
# We have various accessor functions like my which always take the scene object
if !isnothing(my(s).content)
my(s).link = invite!(s, Stack(my(s).content, nothing))
end
my(s).content = m
elseif type === :pop! && !isnothing(my(s).content)
say(s, m, my(s).content)
my(s).content = nothing
isnothing(my(s).link) || forward!(s, my(s).link)
else
error("Can't handle $type")
end
end
# Genesis! is the first message sent to the actor system
function hear(s::Scene{StackPlay}, ::Genesis!)
stack = invite!(s, Stack{Symbol}())
say(s, stack, (:push!, :a))
@test ask(s, stack, (:pop!, me(s)), Symbol) == :a
say(s, stack, (:push!, :b))
@test ask(s, stack, (:pop!, me(s)), Symbol) == :b
stack = invite!(s, Stack{Int}())
for i in 1:5
say(s, stack, (:push!, i))
end
@test ask(s, stack, (:pop!, me(s)), Int) == 5
say(s, stack, (:push!, 6))
@test ask(s, stack, (:pop!, me(s)), Int) == 6
@test ask(s, stack, (:pop!, me(s)), Int) == 4
leave!(s)
end
# Actor based test set!
@testset LuvvyTestSet "Actors Stack" begin
play!(StackPlay())
end
Unlike YAActl in my library you must start the actor system (instead of simply creating the first actor with a link?) which is represented by a special actor called the ‘play’. This was probably a mistake although it helps with implementing actor addresses in the way which I did that. Each actor has one or more addresses which are resolved to a mailbox when a message is sent. I’m not sure how that compares to links in YAActl, but it (somewhat) obviates the need for an actor registry as you can simply give some actor’s a static address (address can be reused in case of failure).
TBH, my interface results in hear
being way overloaded, this is not good for multiple dispatch performance and just looks bad IMO. YAActl’s way is better although I can imagine it easily being abused and leading to confusing dynamical behavior, but I also think contracts and protocols can help with that. However my library also allows the user to override the default message loop which is:
function listen!(s::Scene)
@debug "$s listening"
for msg in inbox(s)
@debug "$s recv" msg
hear(s, msg)
end
end
You can just provide a more specific method for listen!
like:
listen!(s::Scene{Stack{T}}) where T = for msg in inbox(s)
do_something(s, msg)
end
This is very useful if an Actor also needs to listen for some I/O events, for example from my experimental web server:
function Actors.listen!(s::Scene{Server})
dir = my(s).dir
my(s).srv = srv = HTTPServer(nothing, listen(8888), "localhost", "8888")
say(s, dir, Listening!())
# start an asynchronous julia Task which plays nice with the Actor we are launching it from
async(s) do s
try
for sk in srv.server
say(s, dir, Connected!(sk, srv.hostname, srv.hostport))
end
finally
leave!(s)
close(srv)
end
end
for msg in inbox(s)
hear(s, msg)
end
end
I guess in YAActl this might be done with init!
, but it might also be necessary to allow the user to override the message loop in _act
. Either way I/O operations may not play nice with scheduling message execution. Perhaps I could try writing some sort of web service in YAActl and see what problems arise…