David Khourshid - @davidkpiano
π©βπ»
π§
π©βπ³
βοΈβ
π
πΆβ
πΆ
πβ
βοΈ
βοΈ
π©βπ»
π§
I would like a coffee...
What would you like?
Β ActorΒ
I would like a coffee, please.
Β ActorΒ
π
π¬
π¬
Here you go. βοΈ
Thanks!
Actor π©βπ»
Actor π§
βοΈ
βοΈ
someActor.send({
type: 'greet',
value: 'Hei!',
from: self
});
// ...
switch (message.type) {
case 'greet':
message.from.send({
type: 'greet',
value: '...',
from: self
});
// ...
}
Send
Receive
A Universal Modular ACTOR Formalism for Artificial Intelligence (1973)
Carl Hewitt, Peter Bishop, Richard Steiger
Alan Kay
π€ All computation is performed within an actor
π© Actors can communicate only through messages
π₯ In response to a message, an actor can:
π¬ Send messages to other actors
π¨βπ§βπ¦ Create a finite number of child actors
π (π₯)
Behavior
State
π Change its state/behavior
A
B
βοΈ
βοΈ
C
βοΈ
π€ All computation is performed within an actor
π© Actors can communicate only through messages
π₯ In response to a message, an actor can:
π¬ Send messages to other actors
π¨βπ§βπ¦ Create a finite number of child actors
π Change its state/behavior
A
βοΈ
π€ All computation is performed within an actor
π© Actors can communicate only through messages
π₯ In response to a message, an actor can:
π¬ Send messages to other actors
π¨βπ§βπ¦ Create a finite number of child actors
π Change its state/behavior
A β A'
A
βοΈ
π€ All computation is performed within an actor
π© Actors can communicate only through messages
π₯ In response to a message, an actor can:
π¬ Send messages to other actors
π¨βπ§βπ¦ Create a finite number of child actors
π Change its state/behavior
B
βοΈ
A
βοΈ
π€ All computation is performed within an actor
π© Actors can communicate only through messages
π₯ In response to a message, an actor can:
π¬ Send messages to other actors
π¨βπ§βπ¦ Create a finite number of child actors
π Change its state/behavior
π
π
idle
making coffee
delivering coffee
βοΈ ordered
βοΈ made
βοΈ delivered
{
orders: [/* ... */],
inventory: {
// ...
},
sales: 134.65
}
Finite State (behavior)
Extended State (data)
Root (Guardian) Actor
cafe
cafe/manager1
cafe/manager1/employee1
Address
π«
π«
π«
π«
π«
π«
π«
βοΈ
state 1
βοΈ
state 2
βοΈ
βοΈ
processing...
state ??
βοΈ
βοΈ
βοΈ
βοΈ
βοΈ
state 1
βοΈ
βοΈ
βοΈ
βοΈ
state 1
βοΈ
state 2
βοΈ
βοΈ
βοΈ
state 1
βοΈ
state 2
βοΈ
βοΈ
state 3
βοΈ
βοΈ
state 1
βοΈ
state 2
βοΈ
βοΈ
state 3
βοΈ
state 4
βοΈβ
π»
π°
ActorRef
βοΈ
βοΈ
βοΈβ
π»
ActorRef
βοΈ
β
π»
βοΈ
const popup = window.open(/* ... */);
popup.postMessage(/* ... */);
function receiveMessage(msg) {
// do something with the message
}
window.addEventListener('message', receiveMessage);
function receiveMessage(msg) {
// do something with the message
msg.source.postMessage(/* ... */);
}
window.addEventListener('message', receiveMessage);
const popup = window.open(/* ... */);
popup.postMessage(/* ... */);
function receiveMessage(msg) {
// do something with the message
}
window.addEventListener('message', receiveMessage);
function receiveMessage(msg) {
// do something with the message
msg.source.postMessage(/* ... */);
}
window.addEventListener('message', receiveMessage);
merge(...)
scan(reducer, initial)
initial
next
pipe( )
Behavior
Actor
Parent
SEND
RECEIVE
Promise
Parent
SEND
RECEIVE
or
Observable
Parent
Nothing *
SEND
RECEIVE
* RxJS has Subjects for this
or
npm install xstate
import { createMachine } from 'xstate';
const machine = createMachine({
initial: 'idle',
states: {
idle: {
on: { SEARCH: 'searching' }
},
searching: {
on: {
RESOLVE: 'success',
REJECT: 'failure',
SEARCH: 'searching'
}
},
success: {
on: { SEARCH: 'searching' }
},
failure: {
on: { SEARCH: 'searching' }
}
}
});
import { createMachine } from 'xstate';
const machine = createMachine({
// ...
});
const searchingState = machine
.transition('idle', 'searching');
searchingState.value;
// => 'searching'
import { createMachine, interpret } from 'xstate';
const machine = createMachine({
// ...
});
const service = interpret(machine)
.onTransition(state => console.log(state))
.start();
service.send('SEARCH');
// State {
// value: 'searching',
// ...
// }
import { createMachine, interpret } from 'xstate';
import { from } from 'rxjs';
const machine = createMachine(/* ... */);
const service = interpret(/* ... */).start();
// Services are already subscribable!
const state$ = from(service);
state$.subscribe(/* ... */);
import { createMachine } from 'xstate';
const cafeMachine = createMachine({
// ...
states: {
// ...
makingCoffee: {
invoke: {
id: 'espresso',
src: espressoMachine,
onDone: 'deliveringCoffee'
}
},
deliveringCoffee: {
// ...
}
}
});
Invoke a new "actor"
with espressoMachine behavior
"done.invoke.espresso"
import { createMachine } from 'xstate';
const cafeMachine = createMachine({
// ...
states: {
// ...
makingCoffee: {
invoke: {
id: 'espresso',
src: espressoMachine,
onDone: 'deliveringCoffee'
}
},
deliveringCoffee: {
// ...
}
}
});
cafeMachine
espressoMachine
done.invoke.espresso
import { createMachine, send } from 'xstate';
const cafeMachine = createMachine({
// ...
states: {
// ...
makingCoffee: {
invoke: {
id: 'espresso',
src: espressoMachine,
onDone: 'deliveringCoffee'
},
on: {
CHANGE_ORDER: {
actions: send((_, event) => event, {
to: 'espresso'
})
}
}
},
// ...
}
});
cafeMachine
espressoMachine
CHANGE_ORDER
import { createMachine, forwardTo } from 'xstate';
const cafeMachine = createMachine({
// ...
states: {
// ...
makingCoffee: {
invoke: {
id: 'espresso',
src: espressoMachine,
onDone: 'deliveringCoffee'
},
on: {
CHANGE_ORDER: {
actions: forwardTo('espresso')
}
}
},
// ...
}
});
cafeMachine
espressoMachine
CHANGE_ORDER
import { createMachine, sendParent } from 'xstate';
const cafeMachine = createMachine({
// ...
});
const espressoMachine = createMachine({
// ...
states: {
// ...
broken: {
entry: sendParent('MACHINE_BROKEN')
},
// ...
}
});
cafeMachine
espressoMachine
MACHINE_BROKEN
import { createMachine, assign, spawn } from 'xstate';
const createOrder = (details) => {/* ... */}
const cafeMachine = createMachine({
context: {
orders: []
},
// ...
on: {
PLACE_ORDER: {
actions: assign({
orders: (context, event) => {
const order = spawn(
createOrder(event.details),
event.orderId);
return context
.orders.concat(order);
}
})
}
}
});
order 1
order n
cafeMachine
. . .
import { createMachine, send } from 'xstate';
const cafeMachine = createMachine({
// ...
on: {
CANCEL: {
actions: send('CANCEL', {
to: (_, event) => event.orderId
})
}
}
});
order 1
order n
cafeMachine
. . .
CANCEL
import { createMachine } from 'xstate';
import { invokeObservable } from 'xstate/invoke';
function createOrder(details) {
return /* ... */
}
const cafeMachine = createMachine({
// ...
invoke: {
src: invokeObservable(() => /* ... */)
},
// ...
on: {
ORDER: {
actions: assign({
orders: (context, event, { spawn }) => {
const order = spawn.from(
createOrder(event.details), event.id);
return context.orders
.concat(order);
}
})
}
}
});
(tentative API)
Returns an actor creator
import { createBehavior, createSystem } from 'xactor';
const counter = createBehavior((state, message) => {
if (message.type === 'add') {
return {
count: state.count + 1
}
}
return state;
}, { count: 0 });
const counterSystem = createSystem(counter, 'myCounter');
counterSystem.send({
type: 'add'
});
import { createBehavior, createSystem } from 'xactor';
const createTodo = (title) => createBehavior((state, message) => {
// ...
}, { title })
const todos = createBehavior((state, message, ctx) => {
switch (message.type) {
case 'todo.create':
const newTodo = ctx.spawn(createTodo(message.title), `todo-${state.todos.length}`);
return {
...state,
todos: [
...state.todos,
newTodo
]
};
}
}, { todos: [] });
const todoSystem = createSystem(todos, 'myCounter');
todoSystem.send({
type: 'todo.create',
title: 'Learn the actor model!'
});
David Khourshid - @davidkpiano