Behaviors
An actor's behavior is a ...
... function to express what an actor does when it processes a message. [1]
As an example we define two behaviors forward!
and stack_node
, the latter with two methods, dispatched on the user implemented messages Push
and Pop
.
forward!(lk::L, msg::M) where {L<:Link, M<:Message} = send!(lk, msg)
function stack_node(sn::StackNode, msg::Pop)
isnothing(sn.content) || become(forward!, sn.link)
send!(msg.customer, Response(sn.content))
end
function stack_node(sn::StackNode, msg::Push)
P = Actor(stack_node, sn)
become(stack_node, StackNode(msg.content, P))
end
Like any Julia function a behavior function can be implemented with different methods. The methods are dispatched ...
based on the number of arguments given, and on the types of all of the function's arguments [2].
Behavior Dispatch
In the above example stack_node
has two arguments: sn
and msg
. The first represents state, the second represents an event. An event happens when a message arrives. The actor already has the state argument and gets the event argument with the message.
- With its creation as
Actor(stack_node, sn)
the actor gets the first argumentsn
. - With a message
msg
it gets the second argument and callsstack_node(sn, msg)
. - Depending on whether the incoming message is a
Pop
or aPush
one of the two implemented methods is dispatched.
YAActL
allows to send arguments to actors:
- With
cast!
orcall!
you can send them any arguments. - Or you can
send!
them aRequest
or a user implemented message.
Then an actor combines its first arguments with the received second arguments and executes the behavior function.
Argument Composition
You can set!
YAActL
actors into
full
dispatch orstate
dispatch.
The Dispatch
mode determines how actors compose arguments and whether they use the returned value of the behavior function to update their internal state.
julia> mult2 = Actor(*, 2); # create a new actor to multiply by 2 in full dispatch
julia> [call!(mult2, i) for i in 1:10]
10-element Array{Int64,1}:
2
4
6
8
10
12
14
16
18
20
julia> set!(mult2, state); # set it to state dispatch
julia> update!(mult2, 2); # set its state to 2
julia> [call!(mult2, i) for i in 1:10]
10-element Array{Int64,1}:
2
4
12
48
240
1440
10080
80640
725760
7257600
julia> query!(mult2) # query its state
7257600
julia> set!(mult2, full); # back to full dispatch
julia> call!(mult2, 10) # again it multiplies by 2
20
The two dispatch modes cause quite different behaviors.
Full Dispatch
With Actor(stack_node, sn)
we used full
dispatch. The actor got sn
as the first argument and can use that to represent state. If it is mutable, the behavior function can change its content. When a message msg
arrives, the actor takes it as the second argument and composes them to execute the behavior.
To install a behavior function bhv
and its first arguments args1...
we have
The second arguments args2...
get delivered with
The actor then calls
bhv((args1..., args2...)...)
orbhv((args1..., msg)...)
respectively.
Empty arguments for args1...
or args2...
are allowed as long as their composition can be used to dispatch the behavior function bhv
.
State Dispatch
In state
dispatch an actor uses its internal state sta
as first argument to the behavior function. On a message msg
it calls the behavior as bhv(sta, msg)
and saves the returned value back to its internal state variable sta
. Thus it operates as a finite-state machine.
A behavior bhv
is installed without arguments[3] as
Actor state sta
can be set
- with
update!(act, args1...)
or - by an
init
function installed withinit!
.
The second arguments args2...
get delivered with
and the actor calls
bhv((sta, args2...)...)
orbhv((sta, msg)...)
respectively
and updates sta
with the returned value y
. At the next call the behavior bhv
gets dispatched with the updated status and the new message.
The actor updates sta
only if its behavior function returns something. If you want your behavior to not update sta
, let it return nothing
.
Keyword Arguments
In both modes an actor passes keyword arguments kwargs...
to the behavior function. Those are not dispatched on but they too represent state and therefore can change the function result.
Control of Actor Behavior
The described mechanisms allow a fine-grained control of an actor's behavior:
- Set an actor's behavior function at startup or with
become!
. - Control the dynamic dispatch of implemented behavior methods with
- the actor's dispatch mode,
- the first arguments
- either of the behavior function in
full
dispatch, - or the actor state
sta
instate
dispatch,
- either of the behavior function in
- the second arguments from the incoming message.
- Control the result of the dispatched function or method by setting the values of arguments and keyword arguments[4].
This allows actors to use Julia's full expressiveness with functions and methods.