Searching through the comments here, only one of them mentions SAM, which is the point of the article. Some nuanced thought about what's going on here is therefore warranted. So let's start with old-school Smalltalk's MVC: this was a message-passing system consisting of three types of concurrent processes:
* models maintain a list of "subscribers" that they regularly send certain
messages to, in response to the messages that they receive from the outside
world. This can as always involve the internal state that each process
maintains.
* views subscribe to models and then present a graphical description of the
messages they receive.
* controllers listen to user-interface events from the view, and use it to send
messages to models.
These have transformed mightily in the intervening years; a model these days often refers to some form of schema for data structures which are returned by services; controllers often query those services and provide them to the view; views may define their own service-queries. Often there is no clear notion of subscription except possibly subscribing to events from the GUI, which is the responsibility of the controller; the controller basically handles everything which isn't either the structuring of data or the organization of the display components.
SAM appears to be coming from the latter developments and is concerned with an associated explosion: Every view and interaction more or less gets its own service on the back-end, providing a sprawling codebase which resists refactors; they also get their own controllers which maintain them.
In the SAM idea, the model now reprises its earlier notion of managing subscribers and containing business logic: however it is now "dumbed down" from its potentially-very-general Smalltalk definition: the model is instead meant to hold and maintain a single data-structure. (Can there be multiple concurrent models?) The model apparently only receives requests for an all-at-once update, but it may decide to transform its state `new_state = f(old_state, proposed_state)`. Presumably then it again tells its subscribers about the new state if it is not identical to the old state. (Each view is expected to compute the diff itself, probably?)
A "state" in SAM appears to be identified with a "view": a "state-representation" is a function from a model to a DOM. Your GUI consists of a bunch of these, and hypothetically we can diff the DOM trees to better understand what needs to change on an update of the related model properties; the "view" is basically just a bunch of representations of the underlying state with some "actions." These "actions" are not actually parallel processes at all but do take the classical responsibility of the "controller", routing user-interface events to messages to send to the model. The apparent point here is that they should correspondingly be very "dumb" controllers: they just create a transformed version of the data that they received from the model and then send it back to the model as a "nomination" for a possible change.
Finally there appears to be a strange next-action callback which may be part of every "view update." (It's not clear where this lives -- in the "action"?) I am not entirely sure why this exists, but it may be that the algebra of actions without this callback is merely an applicative functor, not a full monad. The essential idea here is that the function apparently can compute a follow-up action if such needs to happen.
If I'm understanding this correctly, then a simple app which seems relatively hard to structure this way would contain:
* a counter stored in a database,
* a button which should ideally increment that counter,
* a display showing the current value of the counter,
* a notification window telling you when your click has updated the counter.
I'm using a counter since it's got a nice algebra for dealing with coincident updates; if someone else updates the counter then your update commutes with theirs, saving the client-side complexity.
Without a framework, we would simply define two endpoints: GET /count (or so) gets the current count; POST /increment will increment the current counter and will say "Success! The current count is now: ____", telling you what you changed the count to.
Here it seems like you need three models. First we have the server-side model of what's going on:
server model Counter:
private count, Subscribers
message increment(intended_count):
if intended_count == count + 1:
count += 1
Subscribers.notifyAll()
return ('success', count)
else:
return ('failure', count)
message count():
return count
The requirement that we only nominate new versions of the underlying data-structure means that we cannot just define the appropriate increment service which always succeeds, but must instead tell the client that the request has failed sometimes. Then there are two models on the client side: one holds a list of outstanding requests to increment (updated by actions, retrying any failures) and the other one holds the currently-cached value of the server-side data (because we need to update this by both the former client-model's update events as well as by some automatic process). You would probably condense these in practice into one model, however they are different concerns. The former model, however, is absolutely required, as it provides a way for the "notification window view" to appear and disappear when one of the requests has succeeded.
This seems unnecessarily complicated given the simple solution in terms of two API endpoints -- however it does indeed fulfill its desire for lower API bloat and some separation of concerns.
SAM appears to be coming from the latter developments and is concerned with an associated explosion: Every view and interaction more or less gets its own service on the back-end, providing a sprawling codebase which resists refactors; they also get their own controllers which maintain them.
In the SAM idea, the model now reprises its earlier notion of managing subscribers and containing business logic: however it is now "dumbed down" from its potentially-very-general Smalltalk definition: the model is instead meant to hold and maintain a single data-structure. (Can there be multiple concurrent models?) The model apparently only receives requests for an all-at-once update, but it may decide to transform its state `new_state = f(old_state, proposed_state)`. Presumably then it again tells its subscribers about the new state if it is not identical to the old state. (Each view is expected to compute the diff itself, probably?)
A "state" in SAM appears to be identified with a "view": a "state-representation" is a function from a model to a DOM. Your GUI consists of a bunch of these, and hypothetically we can diff the DOM trees to better understand what needs to change on an update of the related model properties; the "view" is basically just a bunch of representations of the underlying state with some "actions." These "actions" are not actually parallel processes at all but do take the classical responsibility of the "controller", routing user-interface events to messages to send to the model. The apparent point here is that they should correspondingly be very "dumb" controllers: they just create a transformed version of the data that they received from the model and then send it back to the model as a "nomination" for a possible change.
Finally there appears to be a strange next-action callback which may be part of every "view update." (It's not clear where this lives -- in the "action"?) I am not entirely sure why this exists, but it may be that the algebra of actions without this callback is merely an applicative functor, not a full monad. The essential idea here is that the function apparently can compute a follow-up action if such needs to happen.
If I'm understanding this correctly, then a simple app which seems relatively hard to structure this way would contain:
I'm using a counter since it's got a nice algebra for dealing with coincident updates; if someone else updates the counter then your update commutes with theirs, saving the client-side complexity.Without a framework, we would simply define two endpoints: GET /count (or so) gets the current count; POST /increment will increment the current counter and will say "Success! The current count is now: ____", telling you what you changed the count to.
Here it seems like you need three models. First we have the server-side model of what's going on:
The requirement that we only nominate new versions of the underlying data-structure means that we cannot just define the appropriate increment service which always succeeds, but must instead tell the client that the request has failed sometimes. Then there are two models on the client side: one holds a list of outstanding requests to increment (updated by actions, retrying any failures) and the other one holds the currently-cached value of the server-side data (because we need to update this by both the former client-model's update events as well as by some automatic process). You would probably condense these in practice into one model, however they are different concerns. The former model, however, is absolutely required, as it provides a way for the "notification window view" to appear and disappear when one of the requests has succeeded.This seems unnecessarily complicated given the simple solution in terms of two API endpoints -- however it does indeed fulfill its desire for lower API bloat and some separation of concerns.