# The actor model

## ๐ฌ behind the scenes

THAT Conference Texas 2024

David Khourshid ยท @davidkpiano

stately.ai

# Flashback:

## Origin story

A Universal Modular ACTOR Formalism for Artificial Intelligence (1973)

Carl Hewitt, Peter Bishop, Richard Steiger

Alan Kay

Gul Agha

• Formalized the actor model

Actors: A Model of Concurrent Computation in Distributed Systems

Alan Kay

# Easier than you may think

State machines ๐ตโ๐ซ

๐ฉโ๐ป

๐ง

๐ฉโ๐ณ

โ๏ธโ

๐

๐ตโ

๐ต

๐โ

โ๏ธ

โ๏ธ

# Acting:

## The real world

๐ฉโ๐ป

๐ง

I would like a coffee...

What would you like?

ย Actorย

I would like a coffee, please.

ย Actorย

๐ญ

๐ฌ

๐ฌ

Here you go. โ๏ธ

Thanks!

# What is an actor?

๐ค 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

# What is an actor?

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'

# What is an actor?

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

โ๏ธ

# What is an actor?

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

๐

๐

# An actor's script

• ๐ฌ Send & receive messages
• ๐ญ Change its internal state
• ๐จโ๐ฉโ๐งโ๐ฆ Spawn child actors
``````{
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

# Rehearsal time:ย

## Making an actor

``````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' });``````

# Don't call us, we'll call you:

## Subscribing to actors

``````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) => {

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) => {

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 } }``````

# Special FX:

## Managing side-effects

``````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) => {

return () => {
observers.delete(observer);
};
},
start: () => {
state.status = 'active';
observers.forEach((observer) => {
observer.next(state);
});
}
};

return actor;
}

const actor = createActor({
transition: (ctx, event, self) => {
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 } }``````

# Guest appearance:

## stately.ai/docs

``````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' });``````

# Visual FX:

## Visualizing actors

Stately Inspect: Demo

## npm i @statelyai/inspect

``````import { createBrowserInspector } from '@statelyai/inspect';

const inspector = createBrowserInspector();

inspector.actor('speaker');

inspector.actor('listener');

inspector.event('speaker', 'question?', {
source: 'listener'
});

source: 'speaker'
});``````

# Actor critics:

## Pros and cons

• Easy to scale
• Fault tolerance
• Location transparency
• No shared state
• Event-driven
• "Microservice hell"
• Learning curve
• Indirection
• Unfamiliarity

THAT Conference 2024

David Khourshid ยท @davidkpiano
stately.ai

# End scene:

## Final credits

THAT Conference 2024

David Khourshid ยท @davidkpiano
stately.ai

# Improv time:

## Any questions?

#### The actor model, behind the scenes

By David Khourshid

• 158