THAT Conference Texas 2024
David Khourshid ยท @davidkpiano
stately.ai
A Universal Modular ACTOR Formalism for Artificial Intelligence (1973)
Carl Hewitt, Peter Bishop, Richard Steiger
Alan Kay
Gul Agha
Alan Kay
State machines ๐ตโ๐ซ
๐ฉโ๐ป
๐ง
๐ฉโ๐ณ
โ๏ธโ
๐
๐ตโ
๐ต
๐โ
โ๏ธ
โ๏ธ
๐ฉโ๐ป
๐ง
I would like a coffee...
What would you like?
ย Actorย
I would like a coffee, please.
ย Actorย
๐ญ
๐ฌ
๐ฌ
Here you go. โ๏ธ
Thanks!
๐ค 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
๐
๐
{
orders: [/* ... */],
inventory: {
// ...
},
sales: 134.65
}
โ๏ธ
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
function createActor(idk) {
// ...
return {
send: (event) => {
// ...
}
};
}
const actor = createActor();
actor.send({ type: 'inc' });
function createActor(idk) {
let state = {
count: 0
};
return {
send: (event) => {
// ...
if (event.type === 'increment') {
state.count++;
}
}
};
}
const actor = createActor();
actor.send({ type: 'inc' });
function createActor(transition) {
let state = {
count: 0
};
return {
send: (event) => {
state = transition(state, event);
}
};
}
const actor = createActor(/* ... */);
actor.send({ type: 'inc' });
function createActor(logic) {
let state = logic.initialState;
return {
send: (event) => {
state = logic.transition(state, event);
}
};
}
const actor = createActor({
logic: (state, event) => {/* ... */},
initialState: {/* ... */}
});
actor.send({ type: 'inc' });
function createActor(logic) {
let state = logic.initialState;
const observers = new Set();
return {
send: (event) => {
state = logic.transition(state, event);
observers.forEach((observer) => {
observer.next(state);
});
},
subscribe: (observer) => {
observers.add(observer);
return () => {
observers.delete(observer);
};
}
};
}
const actor = createActor({
transition: (state, event) => {
if (event.type === 'inc') {
return { ...state, count: state.count + 1 };
}
return state;
},
initialState: { count: 0 }
});
actor.subscribe((s) => {
console.log(s);
});
actor.send({ type: 'inc' });
// => { count: 1 }
function createActor(logic) {
let state = {
status: 'inactive',
context: logic.initialContext
};
const observers = new Set();
return {
send: (event) => {
state = logic.transition(state.context, event);
observers.forEach((observer) => {
observer.next(state);
});
},
subscribe: (observer) => {
observers.add(observer);
return () => {
observers.delete(observer);
};
},
start: () => {
state.status = 'active';
observers.forEach((observer) => {
observer.next(state);
});
}
};
}
const actor = createActor({
transition: (ctx, event) => {
if (event.type === 'inc') {
return { ...ctx, count: ctx.count + 1 };
}
return ctx;
},
initialContext: { count: 0 }
});
actor.subscribe((s) => {
console.log(s);
});
actor.start();
// { context: { count: 0 } }
actor.send({ type: 'inc' });
// { context: { count: 1 } }
function createActor(logic) {
let state = {
status: 'inactive',
context: logic.initialContext
};
const observers = new Set();
const actor = {
send: (event) => {
state = logic.transition(
state.context,
event,
actor
);
observers.forEach((observer) => {
observer.next(state);
});
},
subscribe: (observer) => {
observers.add(observer);
return () => {
observers.delete(observer);
};
},
start: () => {
state.status = 'active';
observers.forEach((observer) => {
observer.next(state);
});
}
};
return actor;
}
const actor = createActor({
transition: (ctx, event, self) => {
if (event.type === 'load') {
const promise = new Promise((res) => {
setTimeout(() => {
res({ name: 'David' });
}, 1000);
});
promise.then((output) => {
self.send({ type: 'resolve', data: output });
});
return ctx;
} else if (event.type === 'resolve') {
return {
...ctx,
user: event.output
};
}
return ctx;
},
initialContext: { user: null }
});
actor.subscribe((s) => {
console.log(s);
});
actor.start();
// { context: { count: 0 } }
actor.send({ type: 'inc' });
// { context: { count: 1 } }
import { createActor } from 'xstate';
import { someLogic } from './someLogic';
const actor = createActor(someLogic);
actor.subscribe((snapshot) => {
console.log(snapshot);
});
actor.start();
import { fromTransition, createActor } from 'xstate';
const counterLogic = fromTransition(
// Behavior
(state, event) => {
if (event.type === 'inc') {
return {
...state,
count: state.count + 1
};
}
return state;
},
// Initial state
{ count: 0 }
);
const counterActor = createActor(counterLogic);
counterActor.subscribe(/* ... */);
counterActor.start();
counterActor.send({ type: 'inc' });
import { fromTransition, createActor } from 'xstate';
const counterLogic = fromTransition(
// Behavior
(state, event) => {
if (event.type === 'inc') {
return {
...state,
count: state.count + 1
};
}
return state;
},
// Initial state
// with input
({ input }) => ({
count: input.initialCount
})
);
const counterActor = createActor(counterLogic, {
input: {
initialCount: 100
}
});
counterActor.subscribe(/* ... */);
counterActor.start();
counterActor.send({ type: 'inc' });
import { fromPromise, createActor } from 'xstate';
import { fetchUser } from './fetchUser';
const promiseLogic = fromPromise(async ({ input }) => {
const user = await fetchUser(input.userId);
return user;
});
const promiseActor = createActor(promiseLogic, {
input: { userId: 'user42' }
});
promiseActor.subscribe((s) => {
if (s.status === 'done') {
console.log(s.output);
}
});
promiseActor.start();
import { setup, createActor } from 'xstate';
const counterMachine = setup({
actors: {
promiseLogic,
counterLogic
}
}).createMachine({
initial: 'gettingUser',
states: {
gettingUser: {
invoke: {
src: 'promiseLogic',
input: {/* ... */},
onDone: {
target: 'counting'
}
}
},
counting: {
invoke: {
id: 'counter',
src: 'counterLogic',
input: {
initialCount: 0
},
onSnapshot: {
target: 'reachedMaxCount',
guard: ({ event }) => {
return event.snapshot.context.count === 10;
}
}
},
on: {
inc: {
actions: sendTo('counter', { type: 'inc' })
}
}
},
reachedMaxCount: {
type: 'final'
}
}
});
const counterActor = createActor(counterMachine);
counterActor.subscribe((s) => {
console.log(s);
});
counterActor.start();
counterActor.send({ type: 'inc' });
Stately Inspect: Demo
import { createBrowserInspector } from '@statelyai/inspect';
const inspector = createBrowserInspector();
inspector.actor('speaker');
inspector.actor('listener');
inspector.event('speaker', 'question?', {
source: 'listener'
});
inspector.event('listener', 'answer!', {
source: 'speaker'
});
THAT Conference 2024
David Khourshid ยท @davidkpiano
stately.ai
THAT Conference 2024
David Khourshid ยท @davidkpiano
stately.ai