These are chat archives for jdubray/sam

28th
Jun 2016
Fred Daoud
@foxdonut
Jun 28 2016 10:52 UTC
This message was deleted

Right now in Meiosis I have:

  • actions.sendUpdate(update)
  • receiveUpdate(model, update)
  • nextUpdate(model, update, actions)

I'm thinking of changing them to:

  • actions.trigger(action)
  • stateTransition(model, action)
  • nextAction(model, action, actions)

What do you think, people?

Edward Mulraney
@edmulraney
Jun 28 2016 13:51 UTC
without the context, i think that terminology is an improvement
I like the sound of Meiosis so far, I just want to get a better understanding of SAM before I try it out tho
Riccardo
@riccardoferretti
Jun 28 2016 14:10 UTC
agreed, hard to tell w/o more context. is actions.trigger(action) what you would call from e.g. the click handler? and in that call is action a fn or data?
Fred Daoud
@foxdonut
Jun 28 2016 14:11 UTC
Yes that is correct. action is data, the same data you would use in model.present(data)
Riccardo
@riccardoferretti
Jun 28 2016 14:11 UTC
also, not sure how receiveUpdate maps to stateTransition. I thought after the chat above that it would be called something like receiveProposal (and state transition would happen afterwards)
oh, so is trigger the action fn?
Fred Daoud
@foxdonut
Jun 28 2016 14:13 UTC
@riccardoferretti I know. I keep debating. If we have receiveProposal, then actions.trigger would be actions.sendProposal.
Riccardo
@riccardoferretti
Jun 28 2016 14:13 UTC
as in, you’d have actions.pour(data), actions.drink(data), ..
receiveProposal(model, actions.pour(data)) ?
Fred Daoud
@foxdonut
Jun 28 2016 14:14 UTC
I thought of actions.propose and accept, but as we said, accept might refuse.. I thought of consider, but that isn't intuitive
I was thinking stateTransition because that is what that function does, it receives the current model and the proposed action/change, and mutates the data, thus it is responsible for changing the state.
@riccardoferretti not quite. Let me give more context:
If you look at the section Update flow
Edward Mulraney
@edmulraney
Jun 28 2016 14:16 UTC
@foxdonut for accept/reject: handle()?
Fred Daoud
@foxdonut
Jun 28 2016 14:16 UTC
the view, event handler, etc. calls what is now actions.sendUpdate(data)
Meiosis wires things together so that what you specified as the receiveUpdate function, automatically gets called with the current model, and data
that function returns the new model (whether you mutate or not is up to you)
then the view gets refreshed, and finally nextUpdate gets called in case you want to trigger a nap()
Edward Mulraney
@edmulraney
Jun 28 2016 14:18 UTC
result of actions.propose(...) gets passed to model.present(...) right?
propose/trigger
Fred Daoud
@foxdonut
Jun 28 2016 14:18 UTC
@edmulraney yeah I thought of handle too, but that seems.. overused and vague
@edmulraney no, when you call actions.propose(data), it is data that gets passed to model.present(data)
Edward Mulraney
@edmulraney
Jun 28 2016 14:20 UTC
what does propose do?
Fred Daoud
@foxdonut
Jun 28 2016 14:20 UTC
it's how you trigger an action
it's the equivalent of model.present
Riccardo
@riccardoferretti
Jun 28 2016 14:21 UTC
who calls actions.propose?
Jean-Jacques Dubray
@jdubray
Jun 28 2016 14:21 UTC
a sequence diagram would help
Fred Daoud
@foxdonut
Jun 28 2016 14:21 UTC
the diagram is there
in the link and section I indicated above.
Riccardo
@riccardoferretti
Jun 28 2016 14:21 UTC
so, it’s the handler that calls the propose?
in that case, the (e.g. dom) handler would be the action
in the SAM terminology
is that accurate?
Fred Daoud
@foxdonut
Jun 28 2016 14:22 UTC
blob
Riccardo
@riccardoferretti
Jun 28 2016 14:23 UTC
when a user clicks on a button, the handler for that event will perform some logic, then call actions.sendUpdate(data)?
Fred Daoud
@foxdonut
Jun 28 2016 14:23 UTC
correct
Riccardo
@riccardoferretti
Jun 28 2016 14:23 UTC
then I believe the handler is the action in SAM terms
in the case of the increment, the data you would send is
actions.sendUpdate({incrementBy: 1})
right?
Edward Mulraney
@edmulraney
Jun 28 2016 14:24 UTC
the action will be the mutation that occurs to the model if the proposal is accepted - the handler sends the intent
Riccardo
@riccardoferretti
Jun 28 2016 14:24 UTC
@edmulraney I agree
Fred Daoud
@foxdonut
Jun 28 2016 14:24 UTC
@riccardoferretti correct
yes
Riccardo
@riccardoferretti
Jun 28 2016 14:25 UTC
@foxdonut in that case see what @edmulraney said above :)
Fred Daoud
@foxdonut
Jun 28 2016 14:25 UTC
yes, as @jdubray it is also a state transition
Riccardo
@riccardoferretti
Jun 28 2016 14:25 UTC
feels like the handler should not call actions.sendUpdate({incrementBy: 1})
but should call actions.incrementCounter(), which would eventually call sendUpdate({incrementBy: 1})
Jean-Jacques Dubray
@jdubray
Jun 28 2016 14:26 UTC
@foxdonut if you plan to create a framework, I would definitely use a dispatcher as the event handler. One of the big value of SAM is the concept of "Step" and "allowed actions/proposals". Your framework should implement some of that like SAFE.
Fred Daoud
@foxdonut
Jun 28 2016 14:26 UTC
@riccardoferretti yes that is possible too.
@jdubray I agree :thumbsup:
Riccardo
@riccardoferretti
Jun 28 2016 14:28 UTC
right :)
Edward Mulraney
@edmulraney
Jun 28 2016 14:28 UTC
i think the separation of intent vs action makes things clearer
Riccardo
@riccardoferretti
Jun 28 2016 14:28 UTC
the dom handlers never really see sendUpdate, as their only interfaces with the system is actions.xyz
Fred Daoud
@foxdonut
Jun 28 2016 14:29 UTC
yes entirely possible
however
Riccardo
@riccardoferretti
Jun 28 2016 14:29 UTC
and the actions dictionary is the collection of available actions for a state
Fred Daoud
@foxdonut
Jun 28 2016 14:29 UTC
if sendUpdate is renamed trigger
you can still have actions.incrementCounter()
BUT
Jean-Jacques Dubray
@jdubray
Jun 28 2016 14:29 UTC
@riccardoferretti aboslutely, the view should have as little (ideally no) knowledge of the model
Fred Daoud
@foxdonut
Jun 28 2016 14:29 UTC
say you use UnionTypes
you might also have actions.trigger(Action.IncrementCounter())
there are different ways of encapsulating and hiding the details.
Jean-Jacques Dubray
@jdubray
Jun 28 2016 14:30 UTC
the whole point of modern front-end architectures is to create a "component" model where the view components can be designed independently and mounted in the application.
This should be goal #1
Fred Daoud
@foxdonut
Jun 28 2016 14:30 UTC
both ways work nicely.
Right now I have sendUpdate, receiveUpdate, and nextUpdate, I'm not convinced
Riccardo
@riccardoferretti
Jun 28 2016 14:31 UTC
@foxdonut what’s the advantage of the latter?
Jean-Jacques Dubray
@jdubray
Jun 28 2016 14:31 UTC

@foxdonut I don't like this much:

You can send whatever you want as an update. You may decide that you prefer sending an action as an update, such as a Flux Standard Action, that is, an object with a type and a payload

Riccardo
@riccardoferretti
Jun 28 2016 14:31 UTC
as in, it allows the user of the fwd to blur the line
Jean-Jacques Dubray
@jdubray
Jun 28 2016 14:31 UTC
a proposal is a proposal, not an "action", you have to pick semantics and labels (words), mix and matching is not helpul.
Fred Daoud
@foxdonut
Jun 28 2016 14:31 UTC
@riccardoferretti if you are using UnionType, your receiveUpdate is nicer
Riccardo
@riccardoferretti
Jun 28 2016 14:32 UTC
(which I am not sure would help deliver the semantics of the framework)
Fred Daoud
@foxdonut
Jun 28 2016 14:32 UTC
instead of if (data.editTodo !== undefined)
Riccardo
@riccardoferretti
Jun 28 2016 14:32 UTC
but if the goal is to cater to different semantics/opinions, I think that works
Fred Daoud
@foxdonut
Jun 28 2016 14:32 UTC
you'd have something like Action.case({ EditTodo: function(todo) {...} })
Riccardo
@riccardoferretti
Jun 28 2016 14:33 UTC
but you can still do that from within the action function
to simplify your receiveUpdate
Fred Daoud
@foxdonut
Jun 28 2016 14:33 UTC
no, I'm talking about the model accepting the proposal
personally I don't think presenting just data scales very well, model.present becomes messy very quickly.
Riccardo
@riccardoferretti
Jun 28 2016 14:34 UTC
yeah, you could have actions.increaseCounter() { …. receiveUpdate(Action.IncreaseBy(1))}
in the end, the action needs to talk to the model through a shared api, whether it’s a data structure or a union type
Fred Daoud
@foxdonut
Jun 28 2016 14:35 UTC
@jdubray a proposal is a proposal, yes. I can rephrase that better.
Jean-Jacques Dubray
@jdubray
Jun 28 2016 14:35 UTC

model.present becomes messy very quickly.

not really if you think in terms of "units-of-work", it's ok to have the action suggest which unit of work to use, sometime you want a bit more loose coupling

Riccardo
@riccardoferretti
Jun 28 2016 14:35 UTC
to me that’s equivalent to actions.increaseCounter() { …. receiveUpdate({increaseBy: 1})}
Fred Daoud
@foxdonut
Jun 28 2016 14:35 UTC
BUT the proposal is NOT always of the same nature. Sometimes the data you propose is data that you just want the model to accept, and sometimes the data, like {incrementBy: 1} is... an action
Jean-Jacques Dubray
@jdubray
Jun 28 2016 14:36 UTC
@riccardoferretti yes, we have to accept that some proposal cannot be written in terms of value, but in terms of operation (I use operation instead of action)
Fred Daoud
@foxdonut
Jun 28 2016 14:36 UTC
@jdubray you say not to mix and match but you do it too :)
Jean-Jacques Dubray
@jdubray
Jun 28 2016 14:36 UTC
@foxdonut NO, please, do not use the word action
Fred Daoud
@foxdonut
Jun 28 2016 14:36 UTC
operation
I like that
Riccardo
@riccardoferretti
Jun 28 2016 14:36 UTC
I like operation
Jean-Jacques Dubray
@jdubray
Jun 28 2016 14:36 UTC
An action creates a proposal to the model
Edward Mulraney
@edmulraney
Jun 28 2016 14:37 UTC
i thought that was an intent?
Fred Daoud
@foxdonut
Jun 28 2016 14:37 UTC
ok so then like I've asked before, what do you call what the action proposes to the model?
Jean-Jacques Dubray
@jdubray
Jun 28 2016 14:37 UTC
For me event (click) -> intent (submit form) -> action (propose new address to the model)
the {incrementBy: 1} is an operation
Edward Mulraney
@edmulraney
Jun 28 2016 14:38 UTC
that's a proposal ?!
Riccardo
@riccardoferretti
Jun 28 2016 14:38 UTC
@foxdonut I would call it proposal
Fred Daoud
@foxdonut
Jun 28 2016 14:38 UTC
@jdubray so model.present(operation) ?
Edward Mulraney
@edmulraney
Jun 28 2016 14:38 UTC
a piece of data can't be an operation
Riccardo
@riccardoferretti
Jun 28 2016 14:38 UTC
an operation is a type of proposal
to address concurrency issues
Jean-Jacques Dubray
@jdubray
Jun 28 2016 14:38 UTC
That's really the problem that I am talking about, we have functions and data structures and we have to build precise semantics on top of that, we cannot say now a function is an action. An action here as a very precise meaning in the programming model, you cannot use an "action" anywhere else.
@riccardoferretti yes exactly
Riccardo
@riccardoferretti
Jun 28 2016 14:39 UTC
what would be the other type of proposal @jdubray
Jean-Jacques Dubray
@jdubray
Jun 28 2016 14:39 UTC
assignment
Riccardo
@riccardoferretti
Jun 28 2016 14:39 UTC
nice
Jean-Jacques Dubray
@jdubray
Jun 28 2016 14:39 UTC
sometime you still want an assignment even if you have concurrency issues
Riccardo
@riccardoferretti
Jun 28 2016 14:40 UTC
I was thinking e.g. if you have a reset button on the counter
you’d propose {counterValue: 0}
Jean-Jacques Dubray
@jdubray
Jun 28 2016 14:40 UTC
It's really the business logic which decides, but the programming model needs to give enough support to make it easy
@riccardoferretti yes, exactly
What I am saying is that the action may not know that the proposal is an assignment or an operation
only the model should decide because only the model should deal with concurrency (as a critical section).
Fred Daoud
@foxdonut
Jun 28 2016 14:42 UTC
so the data is a proposal
actions.trigger(proposal) ?
Riccardo
@riccardoferretti
Jun 28 2016 14:42 UTC
the data fed into the receiveUpdate fn
Edward Mulraney
@edmulraney
Jun 28 2016 14:42 UTC
ahh. you're not saying that the data structure {incrementBy: 1} is an operation, you're saying it's a proposal that leads to an operation, as opposed to a proposal which leads to an assignment {counter: 0}
Fred Daoud
@foxdonut
Jun 28 2016 14:43 UTC
receive(proposal)
Riccardo
@riccardoferretti
Jun 28 2016 14:43 UTC
sounds good
Fred Daoud
@foxdonut
Jun 28 2016 14:43 UTC
and nextAction
which may call actions.trigger(anotherProposal)
Riccardo
@riccardoferretti
Jun 28 2016 14:44 UTC
in your semantics of trigger, that’s correct
I personally am not sure if I like trigger inside actions
Fred Daoud
@foxdonut
Jun 28 2016 14:44 UTC
how do you mean?
Riccardo
@riccardoferretti
Jun 28 2016 14:45 UTC
just because I associate that to being an action then
that would be no different from actions.increment()
but actions.increment() -> Proposal
Fred Daoud
@foxdonut
Jun 28 2016 14:45 UTC
how about actions.propose(proposal)
Riccardo
@riccardoferretti
Jun 28 2016 14:45 UTC
so you could have actions.trigger(actions.increment())
Fred Daoud
@foxdonut
Jun 28 2016 14:46 UTC
that's not what I am going for.. if you have actions.increment() then you just call actions.increment() from the event handler.
Riccardo
@riccardoferretti
Jun 28 2016 14:46 UTC
but isn’t it model.propose(actions.increment())?
Fred Daoud
@foxdonut
Jun 28 2016 14:46 UTC
actions.increment() in turn calls actions.propose({incrementBy: 1})
Riccardo
@riccardoferretti
Jun 28 2016 14:46 UTC
yeah, because of the reactive style
but basically actions.propose is your exit point from actions
Edward Mulraney
@edmulraney
Jun 28 2016 14:47 UTC
that's where naming the action an intent makes it clearer I thought. you can intend to action a proposal, but you can't action an action
Riccardo
@riccardoferretti
Jun 28 2016 14:47 UTC
and I find it somewhat confusing that it lives next to them
Fred Daoud
@foxdonut
Jun 28 2016 14:47 UTC
hmm
Riccardo
@riccardoferretti
Jun 28 2016 14:47 UTC
basically, it’s a special action that shortcircuit having a function for an action
sorry, it’s not a “special action” - but it is a shortcut to proposing something without invoking an action
and yet it lives inside actions
that’s where my confusion comes from
Fred Daoud
@foxdonut
Jun 28 2016 14:48 UTC
ok so let's step back for a sec
in Meiosis, actions.propose(proposal) is your "entry point" to proposing something to the model
so instead of actions
what would you call it?
Edward Mulraney
@edmulraney
Jun 28 2016 14:49 UTC
intents
Riccardo
@riccardoferretti
Jun 28 2016 14:49 UTC
model.propose(proposal)?
Fred Daoud
@foxdonut
Jun 28 2016 14:50 UTC
because if you define increment() as encapsulating propose({incrementBy: 1})
Jean-Jacques Dubray
@jdubray
Jun 28 2016 14:50 UTC
@edmulraney an action is an intent to update the model
Fred Daoud
@foxdonut
Jun 28 2016 14:50 UTC
you'd call something.increment() from your event handler
something -> intents ?
Jean-Jacques Dubray
@jdubray
Jun 28 2016 14:50 UTC
the action itself cannot update the model directly
Fred Daoud
@foxdonut
Jun 28 2016 14:50 UTC
no
Edward Mulraney
@edmulraney
Jun 28 2016 14:50 UTC
@jdubray what do you call the thing that mutates the model? model.counter + incrementBy. "learner"?
Jean-Jacques Dubray
@jdubray
Jun 28 2016 14:50 UTC
The user has an intent which triggers an aciton
a mutation?
Fred Daoud
@foxdonut
Jun 28 2016 14:51 UTC
when you call something.propose(proposal) that just calls your receive(proposal) function, and that function actually decides if and what to do.
Riccardo
@riccardoferretti
Jun 28 2016 14:51 UTC
is it correct to say intent -> action -> proposal?
Jean-Jacques Dubray
@jdubray
Jun 28 2016 14:51 UTC
yes, in general I would prefer that
Riccardo
@riccardoferretti
Jun 28 2016 14:52 UTC
if that’s correct, for me the intent is the onClick function associated to the dom node
Edward Mulraney
@edmulraney
Jun 28 2016 14:52 UTC
@jdubray is that addressed at me? you'd call the mutation the learner?
Jean-Jacques Dubray
@jdubray
Jun 28 2016 14:52 UTC
@riccardoferretti yes, (user) intent -> action -> proposal
Riccardo
@riccardoferretti
Jun 28 2016 14:52 UTC
inside that function, you call the actions.increment()
Jean-Jacques Dubray
@jdubray
Jun 28 2016 14:52 UTC
@riccardoferretti yes
yes
Riccardo
@riccardoferretti
Jun 28 2016 14:52 UTC
inside the action, you call model.present(operation)
Jean-Jacques Dubray
@jdubray
Jun 28 2016 14:52 UTC
yes
Riccardo
@riccardoferretti
Jun 28 2016 14:52 UTC
so, @foxdonut, having actions.propose(operation) is like doing intent -> propose
and especially the fact that it lives inside actions makes it confusing
Edward Mulraney
@edmulraney
Jun 28 2016 14:53 UTC
so they are intents until you wrap it with model.present
Riccardo
@riccardoferretti
Jun 28 2016 14:53 UTC
because it is basically the identity function
Jean-Jacques Dubray
@jdubray
Jun 28 2016 14:53 UTC
yes, some actions can be just the identity function, but I would argue this is rare
Riccardo
@riccardoferretti
Jun 28 2016 14:54 UTC
and even when that is the case, why not just wrap them into a function that conveys the meaning of the action
and keep a consistent api for the user
(user == developer using the fwk)
Jean-Jacques Dubray
@jdubray
Jun 28 2016 14:54 UTC
yes, consistency is key, because if you need to add to biz logic then you'll be tempting to add it in the model rather than changing the type to a function
That's why I don't like the Redux model, because it gives just enough incentive to the developer to create spaghetti code on the reducer
Edward Mulraney
@edmulraney
Jun 28 2016 14:55 UTC
if you cleanly separate your code, you wont be putting model.present into your actions, you'd just be defining intents, which when triggered, get passed to model.present
Jean-Jacques Dubray
@jdubray
Jun 28 2016 14:56 UTC
Please understand that actions act as "adapters" between the view and the model
It is unlikely that the intent from the view component will be in the format that the model can understand
Riccardo
@riccardoferretti
Jun 28 2016 14:56 UTC
@edmulraney I think what you are calling intent is what we are calling operation?
Jean-Jacques Dubray
@jdubray
Jun 28 2016 14:56 UTC
I am truly expecting that SAM will enable "themes" of view components to emerge and therefore they will be designed without the knowledge of specific models
Riccardo
@riccardoferretti
Jun 28 2016 14:56 UTC
and we call intent the function where the action is called from
Jean-Jacques Dubray
@jdubray
Jun 28 2016 14:57 UTC
You really have to keep that architecture in place, it is very very important
Fred Daoud
@foxdonut
Jun 28 2016 14:57 UTC
So actions.increment() calls propose(proposal) calls receive(model, proposal) ?
Jean-Jacques Dubray
@jdubray
Jun 28 2016 14:57 UTC
This short cut has very negative consequences
@foxdonut that sounds one call to many
Edward Mulraney
@edmulraney
Jun 28 2016 14:58 UTC
@riccardoferretti im calling intent the function where the proposal is sent from
Riccardo
@riccardoferretti
Jun 28 2016 14:58 UTC
I see, we are calling that action
Fred Daoud
@foxdonut
Jun 28 2016 14:58 UTC
how do you mean @jdubray
Edward Mulraney
@edmulraney
Jun 28 2016 14:58 UTC
hmm
Jean-Jacques Dubray
@jdubray
Jun 28 2016 14:59 UTC
it is an action, anything that results in changing the model is an action
triggering an action is something different, it's best represented as an intent, if you need the distinction
Edward Mulraney
@edmulraney
Jun 28 2016 14:59 UTC
proposals: {incrementBy: 1}, {counterValue: 0}
intents: receiveEvent(eventData) returns proposal
actions: model.present({incremenBy: 1}), model.present({counterValue: 0})
Fred Daoud
@foxdonut
Jun 28 2016 15:00 UTC
I agree @edmulraney
intents: user clicks on +1 button -> onClick: function() {...}
Jean-Jacques Dubray
@jdubray
Jun 28 2016 15:00 UTC
Intents do not create proposal, they carry an event
like mouse position, user inputs,...
Edward Mulraney
@edmulraney
Jun 28 2016 15:01 UTC
that's just an event handler
Jean-Jacques Dubray
@jdubray
Jun 28 2016 15:01 UTC
Please be careful of semantics
Edward Mulraney
@edmulraney
Jun 28 2016 15:01 UTC
the intention is not the mouse click, but what the user wants to do (increment counter)
Jean-Jacques Dubray
@jdubray
Jun 28 2016 15:01 UTC
You cannot have the same semantics at two different levels in the programming model
Edward Mulraney
@edmulraney
Jun 28 2016 15:01 UTC
the event handler HAS to carry the event
Jean-Jacques Dubray
@jdubray
Jun 28 2016 15:01 UTC
yes, but the only payload the intent can carry is the event
Riccardo
@riccardoferretti
Jun 28 2016 15:02 UTC
@foxdonut propose === curry(receive)(currentModel) - if that makes sense
(from your msg above)
but I think we are on the same page on the intent @edmulraney @jdubray
intents: user clicks on +1 button -> onClick: function() {...}
Edward Mulraney
@edmulraney
Jun 28 2016 15:04 UTC
yeah that fits
Riccardo
@riccardoferretti
Jun 28 2016 15:04 UTC
then, inside that fn, you would call the action - e.g. actions.increment()
so, intent -> action
the action prepares the operation proposal (e.g. {incrementBy:1} or {counterValue: 0}), and presents it to the model
Edward Mulraney
@edmulraney
Jun 28 2016 15:05 UTC
what would actions.increment() do*?
Riccardo
@riccardoferretti
Jun 28 2016 15:05 UTC
nothing, in this reactive loop
Jean-Jacques Dubray
@jdubray
Jun 28 2016 15:06 UTC
nothing is ever return, yes it is a reactive loop
Edward Mulraney
@edmulraney
Jun 28 2016 15:06 UTC
correction - return/do
Riccardo
@riccardoferretti
Jun 28 2016 15:06 UTC
once the proposal is ready, the action will call the model with it
Edward Mulraney
@edmulraney
Jun 28 2016 15:06 UTC
awesome. we have the same understanding
Riccardo
@riccardoferretti
Jun 28 2016 15:06 UTC
model.present(operation)
Fred Daoud
@foxdonut
Jun 28 2016 15:06 UTC
@jdubray I understand but propose(proposal) doesn't only call receive, meiosis also re-renders the view and calls nextAction, as you said, it takes care of the reactive loop.
Jean-Jacques Dubray
@jdubray
Jun 28 2016 15:08 UTC
yes
Edward Mulraney
@edmulraney
Jun 28 2016 15:08 UTC
then the next part is: mode.present(operationProposal) receives the proposal, and has to handle it: if(proposal.incrementBy) { model.counter += proposal.incrementBy }
Riccardo
@riccardoferretti
Jun 28 2016 15:08 UTC
correct
Jean-Jacques Dubray
@jdubray
Jun 28 2016 15:08 UTC
yes, that's how it works
Edward Mulraney
@edmulraney
Jun 28 2016 15:09 UTC
what do you name this part: if(proposal.incrementBy) { model.counter += proposal.incrementBy } - the "learner"?
Jean-Jacques Dubray
@jdubray
Jun 28 2016 15:09 UTC
unit-of-work
this is implemented by the acceptor
Edward Mulraney
@edmulraney
Jun 28 2016 15:09 UTC
unit-of-work is a type of action ?
Jean-Jacques Dubray
@jdubray
Jun 28 2016 15:09 UTC
not the learner, the learner will then be able to access the new application state once the model says it's ok
noooo
Edward Mulraney
@edmulraney
Jun 28 2016 15:09 UTC
good :)
Riccardo
@riccardoferretti
Jun 28 2016 15:09 UTC
haha
Edward Mulraney
@edmulraney
Jun 28 2016 15:10 UTC
is unit-of-work the only CS term for this? I've only seen that used in DB transaction stuff
Jean-Jacques Dubray
@jdubray
Jun 28 2016 15:10 UTC
Not all semantics can be defined by "IS-A" relationships
Edward Mulraney
@edmulraney
Jun 28 2016 15:10 UTC
how would you define unit-of-work
Jean-Jacques Dubray
@jdubray
Jun 28 2016 15:11 UTC
semantics = IS-A + (HAS-A)+(Action)+(State)*
Edward Mulraney
@edmulraney
Jun 28 2016 15:11 UTC
purely a model mutation?
Jean-Jacques Dubray
@jdubray
Jun 28 2016 15:11 UTC
aka STAR
yes pure model mutation, the whole point of SAM (and TLA+) is to surface the way you should mutate application state
Of course you cannot write every line of code like that
Riccardo
@riccardoferretti
Jun 28 2016 15:12 UTC

semantics = IS-A + (HAS-A)+(Actions)+(States)*

can you expand on that?

Jean-Jacques Dubray
@jdubray
Jun 28 2016 15:12 UTC
So you have have to pick what you want to control from a mutation point of view
Edward Mulraney
@edmulraney
Jun 28 2016 15:12 UTC
this is good. it's quite easy to make a convention/blueprint for developing apps from these terms
Riccardo
@riccardoferretti
Jun 28 2016 15:12 UTC
(I don’t know how to read that formula)
Fred Daoud
@foxdonut
Jun 28 2016 15:13 UTC
Meiosis.createComponent({
  initialModel: { counter: 0 },
  actions: function(propose) {
    return {
      increment: function() {
        propose({ incrementBy: 1 });
      },
      decrement: function() {
        propose({ incrementBy: -1 });
      },
    }
  },
  receive: function(model, proposal) {
    model.counter = model.counter + proposal.incrementBy;
    return model;
  },
  view: function(model, actions) {
    var onIncrement = function(_evt) {
      actions.increment();
    };
    var onDecrement = function(_evt) {
      actions.decrement();
    };
    return (
      <div>
        Counter: {model.counter}
        <button onClick={onIncrement}> + </button>
        <button onClick={onDecrement}> - </button>
      </div>
    );
  }
});
Jean-Jacques Dubray
@jdubray
Jun 28 2016 15:13 UTC
semantics are a combinations of "relationships" (IS-A) and (HAS-A), sometimes, you need more too, such as some objects have specific actions and states, but that tend to be too specialized
The formula helps you understand that two concepts are distinct
Riccardo
@riccardoferretti
Jun 28 2016 15:14 UTC
@foxdonut where is the connection between receive and propose?
Jean-Jacques Dubray
@jdubray
Jun 28 2016 15:14 UTC
if they have a different set of relationships, action, state, they are not the same
Fred Daoud
@foxdonut
Jun 28 2016 15:15 UTC
@riccardoferretti Meiosis passes you propose to actions: function(propose)
Jean-Jacques Dubray
@jdubray
Jun 28 2016 15:15 UTC
properties of an object are generally not enough to distinguish semantics (like the color of a car, another color, doesn't change the semantics of the car)
Riccardo
@riccardoferretti
Jun 28 2016 15:15 UTC
right, but would it be wired with its own receive?
Fred Daoud
@foxdonut
Jun 28 2016 15:15 UTC
yes
Riccardo
@riccardoferretti
Jun 28 2016 15:15 UTC
oh ok
cool
Fred Daoud
@foxdonut
Jun 28 2016 15:16 UTC
then it automatically calls view
Riccardo
@riccardoferretti
Jun 28 2016 15:16 UTC
that snippet matches my understanding of SAM
nap?
Fred Daoud
@foxdonut
Jun 28 2016 15:16 UTC
yes. finally you could have nextAction: function(model, proposal, actions)
Jean-Jacques Dubray
@jdubray
Jun 28 2016 15:17 UTC
yes, it's pretty close (nap, theme, intents) are missing
Edward Mulraney
@edmulraney
Jun 28 2016 15:17 UTC
@foxdonut @riccardoferretti agreed
Fred Daoud
@foxdonut
Jun 28 2016 15:17 UTC
nap() is not missing, you can add it as nextAction
Riccardo
@riccardoferretti
Jun 28 2016 15:17 UTC
intents are the onIncrement and onDecrement fns
devin ivy
@devinivy
Jun 28 2016 15:17 UTC
the only thing i'm unsure of is the implicit coupling between model, actions, views, etc.
Jean-Jacques Dubray
@jdubray
Jun 28 2016 15:17 UTC
I meant from the sample
devin ivy
@devinivy
Jun 28 2016 15:17 UTC
just by all being in one "component"
Jean-Jacques Dubray
@jdubray
Jun 28 2016 15:18 UTC
I'd call it an "instance" rather than a component
Fred Daoud
@foxdonut
Jun 28 2016 15:18 UTC
@devinivy you can create multiple components
each one focusing on its own concern
Edward Mulraney
@edmulraney
Jun 28 2016 15:18 UTC
yeah the intents are onIncrement/onDecrement in my understanding
Fred Daoud
@foxdonut
Jun 28 2016 15:18 UTC
right @edmulraney intents are already there
devin ivy
@devinivy
Jun 28 2016 15:18 UTC
i understand that– but if i have two components there better be a really good reason the view is tied to that piece of state.
Fred Daoud
@foxdonut
Jun 28 2016 15:19 UTC
how do you mean @devinivy ? the view renders the model.
devin ivy
@devinivy
Jun 28 2016 15:19 UTC
it's not awful, i just think it could be abused very easily.
Jean-Jacques Dubray
@jdubray
Jun 28 2016 15:19 UTC
@foxdonut no, this is not how it would work
blob
devin ivy
@devinivy
Jun 28 2016 15:19 UTC
@foxdonut i think the name "component" is what messes me up.
Jean-Jacques Dubray
@jdubray
Jun 28 2016 15:19 UTC
The SAM instance is more an assembly
of loosely coupled components
devin ivy
@devinivy
Jun 28 2016 15:20 UTC
that "component" would have to be a self-contained SAM app for it to be kosher.
Jean-Jacques Dubray
@jdubray
Jun 28 2016 15:20 UTC
you can also have a parent/child relationship between instance
Edward Mulraney
@edmulraney
Jun 28 2016 15:20 UTC
@jdubray @foxdonut that's the only thing i've done differently in my examples. my component is a component with no properties that directly state the coupling. it's all decoupled and then linked up outside
Jean-Jacques Dubray
@jdubray
Jun 28 2016 15:20 UTC
but SAM is designed to "assemble" components such as theme(s), action(s), ...
Also SAM's component can span tiers -> isomorphic javascript
I have shown that you can be isomorphic by design
Fred Daoud
@foxdonut
Jun 28 2016 15:21 UTC
@foxdonut no, this is not how it would work
@jdubray could you be more specific
Jean-Jacques Dubray
@jdubray
Jun 28 2016 15:22 UTC
I like the general API that you came up with, but for me it should be an "assembly", an app could be lots of components, built by many different people, even 3rd parties.
Fred Daoud
@foxdonut
Jun 28 2016 15:22 UTC
@edmulraney @devinivy @jdubray that is all possible.
Jean-Jacques Dubray
@jdubray
Jun 28 2016 15:23 UTC
Now that we agree on the semantics, you should bring enough flexibility to create these assemblies
Fred Daoud
@foxdonut
Jun 28 2016 15:24 UTC
but for me it should be an "assembly", an app could be lots of components, built by many different people, even 3rd parties.
you could say that about every single simple example out there ;)
just because you show a simple example, not to overwhelm at first sight, doesn't mean you can't build more complex apps with more components.
Edward Mulraney
@edmulraney
Jun 28 2016 15:25 UTC
yes i can see how they would all tie together to create an app
Fred Daoud
@foxdonut
Jun 28 2016 15:25 UTC
this is flexible enough to create assemblies of components. Meiosis.createComponent returns V = f(M) which you can then use in other components' view to use them.
Edward Mulraney
@edmulraney
Jun 28 2016 15:25 UTC
i've built a similar thing
i need to do NAP
what is this defined as?
header, main, and footer are components
Edward Mulraney
@edmulraney
Jun 28 2016 15:27 UTC
yeah nice
i'm developing components in a way that allow you to not depend on the shape of the model, i.e. you can have components that function with only the data they need. or V.part = f(M.parts). basically a view-model from the model
header(...) and todoItem(...) would be concerned with different parts of the model
Fred Daoud
@foxdonut
Jun 28 2016 15:30 UTC
nothing stopping you from calling header(model.part) ;)
devin ivy
@devinivy
Jun 28 2016 15:31 UTC
@foxdonut i look forward to trying meiosis :)
Fred Daoud
@foxdonut
Jun 28 2016 15:32 UTC
I'm also using state and view-models.. but those can be implemented simply with functions, no need to burden the library with things when functions suffice
@devinivy thank you! :)
I will do some refactoring based on today's discussion -- a big thank you to everyone, this was interesting and helped clarify the... semantics!
Like I said, I didn't really like using update... I prefer what we discussed, i.e.
Riccardo
@riccardoferretti
Jun 28 2016 15:35 UTC
looking forward to it @foxdonut !
Fred Daoud
@foxdonut
Jun 28 2016 15:37 UTC
  • initialModel: {...}
  • actions: function(propose)
  • view: function(model, actions)
  • receive: function(model, proposal)
  • nextAction: function(model, proposal, actions)
  • postRender: function(view)
  • ready: function(actions)
where postRender is anything you need to do after each render (don't need it most of the time, but in some edge cases like focus with plain HTML)
and ready is for any one-time initial code you need at startup
everything is optional, so you use what makes sense in e.g. a root "master" component vs a small view component
happy to hear it @riccardoferretti thank you!
Edward Mulraney
@edmulraney
Jun 28 2016 15:46 UTC
is nextAction (nap), simply another action from the available acitons?
that is always executed after what?
Michael Terry
@formido
Jun 28 2016 15:52 UTC
I like send and receive
that's what I named them a couple days ago in a SAM inspired phantomjs script I wrote
Fred Daoud
@foxdonut
Jun 28 2016 15:52 UTC
@edmulraney : nextAction(model, proposal, actions) always gets called, but in your function you determine whether or not to call actions.somethingElse() to automatically trigger another action. You use model and/or proposal to decide. Of course, you could use a state object and wrap the function call with it and call something like if (state.something(model) { actions.something(); }
Michael Terry
@formido
Jun 28 2016 15:52 UTC
that's the language used for message passing in erlang
Fred Daoud
@foxdonut
Jun 28 2016 15:54 UTC
const nextAction = state => (model, proposal, actions) => {
  if (state.counting(model)) {
    if (model.counter > 0) {
      actions.decrement(model.counter);
    }
    else if (model.counter === 0) {
      actions.launch();
    }
  }
};
Edward Mulraney
@edmulraney
Jun 28 2016 15:54 UTC
so, does this get called straight after render?
Fred Daoud
@foxdonut
Jun 28 2016 15:57 UTC
render -> postRender -> nextAction
Edward Mulraney
@edmulraney
Jun 28 2016 15:57 UTC
is postRender in JJ's SAM description, or something in Meiosis?
Fred Daoud
@foxdonut
Jun 28 2016 16:01 UTC
it's something in Meiosis. like I said, rarely needed.
Edward Mulraney
@edmulraney
Jun 28 2016 16:01 UTC
thanks @foxdonut :)
Fred Daoud
@foxdonut
Jun 28 2016 16:01 UTC
welcome @edmulraney !
Edward Mulraney
@edmulraney
Jun 28 2016 16:02 UTC
This message was deleted
it's permissable to fire multiple actions.blah() within a nextAction?
const nextAction = state => (model, proposal, actions) => {
  if (state.counting(model)) {
    actions.decrement(model.counter)
    actions.systemAlert(model.blah)
    ...
Fred Daoud
@foxdonut
Jun 28 2016 16:09 UTC
if it makes sense in your application
@edmulraney you could also have a sequence of actions (one after the other) but in that case your nextAction function would use state to determine which one to fire.
Edward Mulraney
@edmulraney
Jun 28 2016 16:12 UTC
how would you handle a situation where in your sequence of actions, the first action results in an asynchronous call, and the second action depends on the result of the first?
const nextAction = state => (model, proposal, actions) => {
  if (state.counting(model)) {
    actions.decrementAsync(model.counter)
    // need to wait for the above action to complete, then the next action is fired with the new model data
    actions.systemAlert(model.blah)
    ...
Fred Daoud
@foxdonut
Jun 28 2016 16:16 UTC
  • make asynchronous call
  • in the function that receives the result, call propose(proposal) according to the result
  • in receive, update the model as appropriate, given the proposal
  • in nextUpdate, call the next action based on the model and/or the proposal
Edward Mulraney
@edmulraney
Jun 28 2016 16:19 UTC
unit-of-work would make the asynchronous call, but in our nextAction we don't know when that's completed, and we can't .then() on actions
so it seems like we can only put multiple actions.blah() in nextAction if they result in a synchronous unit-of-work
any thoughts on this anyone?
Jean-Jacques Dubray
@jdubray
Jun 28 2016 16:29 UTC
@edmulraney you can never trigger multiple actions
the semantics is that an action is part of a step, a new action can be triggered at the end of the step
Edward Mulraney
@edmulraney
Jun 28 2016 16:30 UTC
how do you trigger a new action automatically, at the end of a step?
Jean-Jacques Dubray
@jdubray
Jun 28 2016 16:30 UTC
you cannot trigger an action without knowing the application state
if (condition) { actions.doThis() }
Edward Mulraney
@edmulraney
Jun 28 2016 16:30 UTC
if its synchronous then you know the application state, so you could do multiple
Jean-Jacques Dubray
@jdubray
Jun 28 2016 16:30 UTC
you can queue actions but you should try to avoid executing an action without knowing that it is allowed
Edward Mulraney
@edmulraney
Jun 28 2016 16:31 UTC
@jdubray that example is how to invoke an action, you've said you can't place that in NAP, so where does it go
Jean-Jacques Dubray
@jdubray
Jun 28 2016 16:31 UTC
that's what you put in nap()
Edward Mulraney
@edmulraney
Jun 28 2016 16:31 UTC
im talking about the 2nd action you want to do
not the first
Jean-Jacques Dubray
@jdubray
Jun 28 2016 16:31 UTC
nap is at the end of the step, to it's in the best position to know what to do
in nap() you'll have something:
if (condition1) { actions.action1(..) ; }
if (condition2) { actions.action2(..) ; }
condition2 will have to be designed to know that action1 has completed
You can also do a functional composition of actions
but logically you only execute one action (one step, one allowed action)
Edward Mulraney
@edmulraney
Jun 28 2016 16:33 UTC
what if you need to do two actions in one condition? if(condition1) { actions.action1() actions.action2()}
Jean-Jacques Dubray
@jdubray
Jun 28 2016 16:33 UTC
by definition you cannot
one action = one step
that's the semantics
Edward Mulraney
@edmulraney
Jun 28 2016 16:33 UTC
then how do I achieve this goal in SAM
of executing two or more actions
its a common use case:
  1. Action1: fetch something from API
  2. Action2: depending on result of API - do something..
Jean-Jacques Dubray
@jdubray
Jun 28 2016 16:35 UTC
as I said two ways:
  1. functional composition composite_action = action2(action1(data)) (action1 presents its data to action2 which presents it to the model)
  2. step/nap based , in nap:
    if (action1Completed) { actions.action2({ } ) ;}
Edward Mulraney
@edmulraney
Jun 28 2016 16:35 UTC
oh i see. I forgot nap would cause the loop to start again and retrigger nap
Jean-Jacques Dubray
@jdubray
Jun 28 2016 16:35 UTC
you cannot dissociate actions to a "Step", that's why an action is not just a function call
the nice thing about nap is that you achieve what Sagas do (some logic if action1 failed, ...) but in a stateless way
Edward Mulraney
@edmulraney
Jun 28 2016 16:36 UTC
if(action1WasSuccessful) where would you suggest storing this temporary state?
Jean-Jacques Dubray
@jdubray
Jun 28 2016 16:36 UTC
the application state remains 100% in the model
in the model
Edward Mulraney
@edmulraney
Jun 28 2016 16:37 UTC
does that not dirty the model a bit?
Jean-Jacques Dubray
@jdubray
Jun 28 2016 16:37 UTC
single state tree is something you never want to break
well, you can decompose the model into modular parts
Edward Mulraney
@edmulraney
Jun 28 2016 16:37 UTC
sure, but i mean storing temporary state
Jean-Jacques Dubray
@jdubray
Jun 28 2016 16:37 UTC
you can also create parent/child SAM instances
Edward Mulraney
@edmulraney
Jun 28 2016 16:37 UTC
state would become filled with temporary data
Jean-Jacques Dubray
@jdubray
Jun 28 2016 16:38 UTC
the alternative is something like Sagas, not really pretty
State is ugly in general, you can only make it less ugly
Edward Mulraney
@edmulraney
Jun 28 2016 16:39 UTC
model.feature.component.action-wasSuccessful
Jean-Jacques Dubray
@jdubray
Jun 28 2016 16:39 UTC
yes
Edward Mulraney
@edmulraney
Jun 28 2016 16:40 UTC
model.feature.component.action-responseData
the unit-of-work may not perform a model-mutation based on it's response, but we still want the response data. s i suppose it would go in the above temporary model property
okay that makes sense now. thanks @jdubray. I'm impressed with how few libraries you'd need to do this. say compared to redux
it just needs a strong blueprint/skeleton/folder-file organisation, and minimal wiring
Jean-Jacques Dubray
@jdubray
Jun 28 2016 16:43 UTC
yes, that's why I often say I can replace React+Redux+Sagas+Thunks+... with one line of code
I am exaggerating a bit of course, but you can see it too
Edward Mulraney
@edmulraney
Jun 28 2016 16:43 UTC
well we've always been able to do that, but redux was trying to organise project sensibly, this seems like a more sensible organisation, without the need of so many tools
Jean-Jacques Dubray
@jdubray
Jun 28 2016 16:44 UTC
yes exactly the assembly is very flexible because I believe it's based on an efficient factoring
yes !
the proposal/acceptance is very powerful
and then things like nap come handy when you need them
Edward Mulraney
@edmulraney
Jun 28 2016 16:44 UTC
I'm nearly ready to admit that I'm sold ;)
Jean-Jacques Dubray
@jdubray
Jun 28 2016 16:45 UTC
!!!!!! thank you!!
to be fair, you are really sold on TLA+, SAM is just one way this incredible piece of work can be helpful!
Edward Mulraney
@edmulraney
Jun 28 2016 16:45 UTC
sensible semantics/assembly means we wouldn't need a redux style eco-system. just write plain JS. big wins
Jean-Jacques Dubray
@jdubray
Jun 28 2016 16:45 UTC
YES!! YES!!
I have nothing against frameworks and libraries, but you have to know why you use them
Edward Mulraney
@edmulraney
Jun 28 2016 16:46 UTC
redux (done right), without redux
Jean-Jacques Dubray
@jdubray
Jun 28 2016 16:47 UTC
yes, again nothing against React/Redux, very innovative, very inspiring, but they need to clean it up
they cannot let it grow in such a chaotic way
SAM also show that your programming model should be agnostic to the way you wire your application and cross tiers (Isomorphic Javascript)
If wiring is central to your code and you pain writing Isomorphic JS, you are doing something wrong
IMHO
This is the same lesson for old Java (EJB for instance).
Your code cannot be tier specific or protocol specific, period.
Edward Mulraney
@edmulraney
Jun 28 2016 16:52 UTC
in a lot of examples, actions are global to window. is model preferred location for these?
Jean-Jacques Dubray
@jdubray
Jun 28 2016 17:12 UTC
they are just sample code to reason about, it's really your choice how you want to mount/wire the pattern
it's just a pattern!
Edward Mulraney
@edmulraney
Jun 28 2016 17:15 UTC
yep, as long as there are no issues with putting it in model, i'll do that
what term are you using to describe components/features/modules/"ducks" in SAM?
i.e. collection of intents/actions/view-components/control-states, for a single "component"
Fred Daoud
@foxdonut
Jun 28 2016 17:21 UTC
Agree @edmulraney @jdubray why I am sold also is that it's just simple JS
Meiosis is not a framework, just a small helper library to do the wiring. The initial code was very small. Now, it's not that it's much bigger, just that it's written in TypeScript (for those who like those benefits) and broken up better. It's still very simple. You might have the reaction "I could have written this myself" and to that I'd say "great! that's the idea! it's simple. no black-box esoteric angular magic here."
Jean-Jacques Dubray
@jdubray
Jun 28 2016 17:26 UTC
but it solves a very real problem which is being able to substitute the view if you need to. That's the kind of problems you want some help with.
@edmulraney I am not very good with naming things :-)
Edward Mulraney
@edmulraney
Jun 28 2016 17:27 UTC
i think it's not obvious that the redux-thunk+redux-blah approach is bloat until you see you can implement a redux style loop with clearer semantics and less boilerplate
Jean-Jacques Dubray
@jdubray
Jun 28 2016 17:28 UTC
thank you!
Edward Mulraney
@edmulraney
Jun 28 2016 17:28 UTC
although i did have to write something similar to what @foxdonut has written (mini framwork/blueprint)to gain this understanding
Jean-Jacques Dubray
@jdubray
Jun 28 2016 17:29 UTC
The big lesson I learned with SAM/TLA+ is that when your semantics are not aligned with what you are doing you quickly get lost in boilerplate
And we have not even started to discuss what the Paxos part can help do for you in an IoT/Wearables/Multi-User world
http://harry.me/blog/2014/12/27/neat-algorithms-paxos/
When you understand how you can weave paxos into our architecture, the sky is the limit.
But first you need to understand SAM, if I introduce Paxos, lots of people will run away
Jean-Jacques Dubray
@jdubray
Jun 28 2016 17:34 UTC
SEQUENCE NUMBERS
[The proposal] holds inside it the value being proposed, as well as what’s called a sequence number inside it. The sequence number is generated by the proposing process, and it declares that the receiving process should prepare to accept a proposal with that sequence number. This sequence number is key: it allows processes to differentiate between newer and older proposals. If two processes are trying to get a value set, Paxos says that value proposed last should take precedence, so this lets processes figure out which one is last, and thus who is trying to set the most recent value.
Edward Mulraney
@edmulraney
Jun 28 2016 17:35 UTC
nice blog post
Jean-Jacques Dubray
@jdubray
Jun 28 2016 17:35 UTC
Dr. Lamport spent 20-30 years to get these semantics right, they deserve our attention
Edward Mulraney
@edmulraney
Jun 28 2016 17:36 UTC
had Dr. Lamport thought about applying these principles to the UI?
Jean-Jacques Dubray
@jdubray
Jun 28 2016 17:36 UTC
no, he is focused a different kind of problems.
Edward Mulraney
@edmulraney
Jun 28 2016 17:37 UTC
did he think it was a good idea when you spoke to him?
Jean-Jacques Dubray
@jdubray
Jun 28 2016 17:37 UTC
He is very busy, so he delegated the review to someone else
they concluded it was not worth their time, but interesting enough to look at it
I respect that, I was already infinitely grateful to have a conversation to understand how TLA+ works
For instance, Dr. Lamport has spent a lot of work connecting Mathematics and Computing, I guess that's a lot more interesting than Front-End/Redux stuff
He is still working on the semantics of TLA+, that's also more important than finding more use for it
He should be looked at as the Alan Turing of our time.
Jean-Jacques Dubray
@jdubray
Jun 28 2016 19:26 UTC
Here is another article on the Paxos protocol: https://angus.nyc/2012/paxos-by-example/
Fred Daoud
@foxdonut
Jun 28 2016 19:38 UTC
@jdubray interesting, does SAFE implement Paxos?
Jean-Jacques Dubray
@jdubray
Jun 28 2016 19:46 UTC
A small variant, because it keeps track of the action instance id, when an action is triggered, it gets a unique step/action id which will come as part of the proposal, the SAFE middleware will filter "out-of-step" proposals without the Model knowing about it.
SAFE makes all this transparent to the action and model (the step/action id is injected at the dispatcher level)
Feel free to reuse the code (with credit), I am ok with it.
This is a great value add
SAFE is here to illustrate the power of the factoring, as I said, I think the sky is the limit. Imagine the kind of multi-user/concurrent experience you can create with that.