These are chat archives for jdubray/sam

22nd
Jun 2016
Edward Mulraney
@edmulraney
Jun 22 2016 00:21
I've tried to simplify the rectangle example, because I think it's a good one for understanding SAM. I removed canvas and just used a simple div (means you cant draw a backwards rectangle but that's fine), I simplified the terminology, removed some redundancy, and removed the next action http://jsfiddle.net/bD37R/79/
The standard JS comparison to this would be
Now I'll write the redux version which will have SAM principles
Edward Mulraney
@edmulraney
Jun 22 2016 00:27
well, tomorrow morning I will.
devin ivy
@devinivy
Jun 22 2016 03:17
@jdubray as far as using SAM on redux goes, would you agree that thunks make for pretty good actions?
devin ivy
@devinivy
Jun 22 2016 03:53
i wonder if nap() can be implemented as redux middleware
i think you can use microtask timing to let the state transition occur, then lookup some next actions to call!
Jean-Jacques Dubray
@jdubray
Jun 22 2016 04:23
@edmulraney Logically, you cannot write this code in the action, that is clearly part of the acceptor, actions should never be in the position to do something like in Redux, model.getState():
if (model.started)
        model.present({rectangleEndPosition: rectangleEndPosition})
Actions would not present data to the model only if its own internal validation rules lead to the conclusion that no mutation is necessary (not even an error message)
Jean-Jacques Dubray
@jdubray
Jun 22 2016 04:28
@devinivy I am not sure what's the difference between action-creators and thunks, I guess the part that I don't like about thunks is that they are "middleware". I would prefer seeing the actions to be completely independent, all they need is a reference to the model.present() method.
@devinivy logically, I would prefer making that nap() is called after the state representation is rendered, though there might be some use cases where you optimize the rendering until the nap() action has completed its mutation. So in theory nap() is observing the store.
Guillaume FORTAINE
@gfortaine
Jun 22 2016 08:03
@jdubray RFX Stack – Universal App Featuring: React and Feathers and MobX https://github.com/foxhound87/rfx-stack
Edward Mulraney
@edmulraney
Jun 22 2016 08:14
@devinivy you can use middleware in redux to achieve nap. originally this is how I solved the "side effects" problem. But it was quite limiting and it's a bit of a hack. I'll publish an example with SEQUENCE today
@jdubray this was the issue I was referring to. If we move the model.started conditional check into the acceptor (present), then we will be calling present when we don't need to. I'll move it into the acceptor, but I am conscious of how this would scale when there could be potentially a lot of calls to model.presentthat don't need to happen. Updated version: http://jsfiddle.net/bD37R/80/
Jean-Jacques Dubray
@jdubray
Jun 22 2016 09:38
@edmulraney sorry, I see your point now. This better solved with something like SAFE as allowed actions
the state function can compute the allowed actions in any given state and SAFE middleware should enforce them
that's even better BC because you are not even touching the action
Edward Mulraney
@edmulraney
Jun 22 2016 09:55
i like the idea of allowed actions
got a simple example I can reference?
Jean-Jacques Dubray
@jdubray
Jun 22 2016 10:04
if search for npm sam-safe you should see it
the way safe works is either the state returns an empty array which means any action is allowed or it returns an array of labels of the allowed actions. The dispatcher will enforce the rules.
it's a bit if state but it leaves outside the state function or the actions.
You can deploy SAFe (the state action fabric) I n the browser or in the server. On the server it would have to be session based.
Fred Daoud
@foxdonut
Jun 22 2016 12:08

@jdubray

I was able to eliminate these ancillary "states" from the model, now the present looks like this: [...] I know it's a bit artificial, but nevertheless, the states do not clutter the model. This sample shows clearly the decoupling potential of SAM, unlike the original where some of the handlers couple action, model and view, for instance.

Thank you, looks good to me and I agree, where things happen in SAM (where the code is located) makes sense.

I guess the part that I don't like about thunks is that they are "middleware". I would prefer seeing the actions to be completely independent, all they need is a reference to the model.present() method.

Excellent point! Each function only has a reference to what it needs. There is little to no magic involved.

Jean-Jacques Dubray
@jdubray
Jun 22 2016 12:18
@foxdonut that way you only need to pass a function to the action, it can even be executed by a third party. When you create a convoluted architecture (such as middleware) then you pay a heavy price. Middleware is good to build a flexible infrastructure, they are not very good for business logic/programming models.
Fred Daoud
@foxdonut
Jun 22 2016 12:31
@jdubray I agree. What I really like about the pattern is (as you have said) it is easy to implement, it doesn't really need complicated infrastructure. A simple library can do the wiring. That is what I am trying to do with Meiosis, and what you have done with SAFE (with other features), and what can even be done by using a couple of functions from Redux store. Being able to do that is a testament to how powerful this is -- power in simplicity. You do not get trapped deep into the bowels of a complicated framework.
Jean-Jacques Dubray
@jdubray
Jun 22 2016 12:40
thank you! yes, that's really the idea. You retain a lot of control that way, when you compare to setting up observables for instance. Look at all the wiring necessary for this Angular2-Reactive sample.
Edward Mulraney
@edmulraney
Jun 22 2016 13:01
@jdubray in your rectangle example your put some conditional checks in the state.render function
would those condition checks be better placed in the acceptor model.present
  var state = (function() {
      return {
        render: function(model) {
          if (model.rectangle.from) {
            view.display(model.rectangle)
          }
vs
      present(data) {
...
      if (this.rectangle.from) {
         this.state.render(model)
       }
Jean-Jacques Dubray
@jdubray
Jun 22 2016 13:26
sure, but it may not be practical for a UI that has many more components. Only the view or the components should know what they need to render.
The way I use the pattern these days is that the state representation is a JSON structure that contains the elements of the view in the current state:
ready(model: any, intents: any) { 
            // generate the representation of each component
            return ({ 
                appHeader: theme.header(model.startWith ,intents),
                peopleList: theme.list(model, intents), 
                filters: theme.filters(model.count, intents),
                dynamic: model.count
            });
        },
Jean-Jacques Dubray
@jdubray
Jun 22 2016 13:33
I would prefer if the view or the theme components do these checks, but it's not a hard rule. Common sense applies.
Here the state function identifies that we are in the "ready" state. It tells the view to render whatever the ready state is, the view orchestrate the theme components and returns a "state representation" which the state function "displays".
What you see above is that I can return json (dynamic), not just HTML (appHeader). The json can populate the model of an Angular2 component.
Which renders on its own.
You really don't want the model to be involved at all in the logic of rendering the view. The model should not know about the "states" either.
Conceptually a "state" is a range of values, like (count < -1) or (count > 0). That's why I like to express everything in the model in terms of property values. I am then free to define "state" semantics in the state function: dragging = (count < 0), stopDrawing = (count>0).
It's a bit artificial, maybe that's not the right way of doing it. All I can say is that a "state" is a range of values. Sometime the range is limited to just one value, but most often it's a range.
Edward Mulraney
@edmulraney
Jun 22 2016 13:49
what's your main reason for wanting to have the model mutable rather than just replacing it altogether?
Jean-Jacques Dubray
@jdubray
Jun 22 2016 13:51
performance, people who have used an immutable approach complain about performance (Cerebral for instance). Otherwise, there is no conceptual difference to use an immutable model.
Edward Mulraney
@edmulraney
Jun 22 2016 13:54
okay cool
devin ivy
@devinivy
Jun 22 2016 13:55
the immutability is really for react, to keep the vdom rerenders minimal
Jean-Jacques Dubray
@jdubray
Jun 22 2016 13:56
yes, that's my impression too.
Edward Mulraney
@edmulraney
Jun 22 2016 13:57
the main reason I prefer not mutating the model is to avoid managing this
devin ivy
@devinivy
Jun 22 2016 13:57
in other settings like multi-threaded environments immutability is useful.
but that's not the case here
Edward Mulraney
@edmulraney
Jun 22 2016 13:57
i feel managing this is another layer of complexity
we can just avoid it altogether
devin ivy
@devinivy
Jun 22 2016 13:58
@edmulraney repost from a couple days ago, but check this out https://github.com/cerebral/state-tree
it's a mutable state tree that can flush a description of the changes that occurred to it
Edward Mulraney
@edmulraney
Jun 22 2016 13:58
aha nice!
devin ivy
@devinivy
Jun 22 2016 13:58
you wouldn't necessarily be using this
Edward Mulraney
@edmulraney
Jun 22 2016 13:58
an api around state. thats another solution to it
i like that, and you get the performance gains
devin ivy
@devinivy
Jun 22 2016 13:59
yeah it's an API not dissimilar to immutablejs
it's nice! i haven't gotten to play with it yet, but i'm quite excited. it will work great with polymerjs and other libraries that don't rerender like the vdom.
probably will work nice with incremental-dom
i expect a highly performant SAM app (with a mutable model) will use something like this with something like incremental-dom
Edward Mulraney
@edmulraney
Jun 22 2016 14:04
I really like the look of cerebral state tree
devin ivy
@devinivy
Jun 22 2016 14:05
the one thing i'm not sure about is if it can describe splices on an array
Jean-Jacques Dubray
@jdubray
Jun 22 2016 14:05
That being said, because the State function is "state" based, computing which state you are in, is generally very efficient/light weight, then in a given state, you would know which part of the model that need to be rendered.
Edward Mulraney
@edmulraney
Jun 22 2016 14:06
@devinivy they list tree.splice('list', 0, 1, 'newValue'); in their examples - is that what you mean?
just means we cant mutate arrays or objects manually, it must be done through state tree api
Edward Mulraney
@edmulraney
Jun 22 2016 14:24
@jdubray how do you manage the present function when you've got a lot of components?
Edward Mulraney
@edmulraney
Jun 22 2016 14:31
also, in the rectangle example, state is connected to a single view component . and the model has a single state attached to it. should this single state.render handle rendering all the components, or should there be more than one state?
Jean-Jacques Dubray
@jdubray
Jun 22 2016 14:31
You would decompose it pretty much like a reducer. The main difference is that the point of entry of reducers is a list of actions, while the point of entry of the present method is a list of "units-of-work" (with a many-actions-to-one-unit-of-work relationship). So I am expecting to have fewer moving parts in a SAM model than in a Redux reducer
SAM encourages a multiplicity of actions to reflect the "application business logic"
From a state perspective, again SAM makes is relatively easy to sort it out. If you look at the Rocket example you have a list of states. There is also the Tic-Tac-Toe sample from @509dave16
Fred Daoud
@foxdonut
Jun 22 2016 14:32
@edmulraney that is one of the things I am trying to do with Meiosis, each component can have its own present function.
Jean-Jacques Dubray
@jdubray
Jun 22 2016 14:33
You have to be careful though to no break the "single state tree" concept
At a minimum the "trees" have to match units of work.
Fred Daoud
@foxdonut
Jun 22 2016 14:33
Absolutely. Everything is combined into a single model.
Jean-Jacques Dubray
@jdubray
Jun 22 2016 14:35
@edmulraney This is what a typically State implementation would look like:
        if (_ready(model)) {
            representation = _view.ready(model, _intents) ;
        } 

        if (_counting(model)) {
            representation = _view.counting(model, _intents) ;
        }

        if (_launched(model)) {
            representation = _view.launched(model, _intents) ;
        }

        if (_aborted(model)) {
            representation = _view.aborted(model, _intents) ;
        }

        _view.display({ container: representation}) ;
The "states" make it easy to reason as to what component needs to be displayed, and again the "representation" is generally a json structure that contains all the elements that can be mounted in the page. It's not about creating a single representation. There are lots of opportunities to implement simple optimizations.
Edward Mulraney
@edmulraney
Jun 22 2016 14:44
nice, I see. it may be that we want to display two or more views/components at the same time?
display two canvases capable of drawing independent rectangles
Jean-Jacques Dubray
@jdubray
Jun 22 2016 14:48
as many as you want. The state function streamlines the relationship between the view and the model. Otherwise it would be odd to put that kind of logic either in the view or in the model.
Here the example is very simple so the parameter of the view representations is "model" but in reality, you should start breaking down the model here.
From what I heard people who use immutable data structures tend to create very flat models for performance reasons
That makes it harder to pass parts of the models to different views and components
devin ivy
@devinivy
Jun 22 2016 14:52
@edmulraney i actually mean i don't think it flushes those splices very precisely
Edward Mulraney
@edmulraney
Jun 22 2016 14:53
its just tricky seeing how to wire up rendering to multiple components when the state render function is connected to a single component
unless we do state.render(props) rather than state.render(model)
that wouldnt work. its still attached to a single component
Jean-Jacques Dubray
@jdubray
Jun 22 2016 14:56
sorry, I am not sure I understand, that's how I do it:
given this angular2 template:
@Component({
  selector: 'my-app',
  providers: [ContactService,ModelService,SamService,CustomComponentBuilder],
  template: `
      <div id="parent">
      <h3>SAM Components (vanilla.js)</h3>
      <child id="test"></child> <br>
      <child id="appHeader"></child> <br>
      <child id="peopleList"></child> <br>
      <child id="filters"></child> <br>
      <h3>Angular2 Static Component</h3>
      <counter #mycounter [counterValue]="counterValue"></counter>
      <div #dynamicContentPlaceHolder></div>
      </div>`,
  directives: [ChildComponent, CounterComponent]

})
here is the render function:
// called by SAM display function
    render(sr: any, dispacther?: (event: any) => void) {


        // The State Representation is something like { appHeader: '...', peopleList: '...', ... }
        // Loop over the components being rendered 

        var props: string[] = Object.getOwnPropertyNames(sr) ;

        props.forEach( (prop) => { 
            var vc : any = compileToComponent(sr[prop], [ChildComponent,CounterComponent,AutoGrowDirective]) ;

            // mount dispatcher by default
            vc.prototype.dispatch = (event: any) => {
                console.log('dispatching from view component') ;
                this.sam.actions.dispatch(event) ;
            }

            if (sr[prop].length>0) {
              var dc = this.loader.loadAsRoot(
                          vc, 
                          '#'+prop, 
                          this.injector
                      )  ;

            } 

        }

        ) ;



            if (this.component.name === undefined) {
                this.component.name = sr['dynamic'] ;
            }
            this.component.entity = { description: sr['dynamic']} ;
            this.ref.detectChanges() ;
            console.log(this.component) ;

    }
The last "mount" (dynamic) is updating the model of an Angular2 component. The view then gets updated automatically via reaction.
Edward Mulraney
@edmulraney
Jun 22 2016 15:05
its a bit hard for me to map that to a simple example like the rectangle (i havent played with angular2 yet)
if we add:
<div id="container2">
  <div id="net2"></div>
</div>
Jean-Jacques Dubray
@jdubray
Jun 22 2016 15:08

This:

var dc = this.loader.loadAsRoot(
                          vc, 
                          '#'+prop, 
                          this.injector
                      )  ;

is like my_element.innerHTML = sr[prop] ;

The code makes a simple optimization: if (sr[prop].length>0)
This means that if no updates are required, we just return an empty representation, then nothing happens.
IMHO, I don't see a big need for virtual-dom technologies, but happy to be wrong.
I also need to handle "focus" in nap(), works quite well:
var _nextAction = (model: any) => {

        if (model.focus) {
            console.log('refocusing query field '+ model.focus) ;
            setTimeout(function(){
                var el: any = document.getElementById(model.focus) ;
                if (el) {
                    el.focus();
                    var val = el.value; //store the value of the element
                    el.value = ''; //clear the value of the element
                    el.value = val; //set that value back.  
                }
            });
        }

    } ;
SAM makes Angular2 work like React.js:
  • single state tree
  • unidirectional data flow
  • one (or more) dispatchers
  • reactive dynamic views or data binding
    ...
Edward Mulraney
@edmulraney
Jun 22 2016 15:14
nice
Jean-Jacques Dubray
@jdubray
Jun 22 2016 15:14
SAM removes the limitation of parent/child event handlers. I can direct the events wherever I want viat the intent concept (even mix and match):
intents: {
            edit: mount+'.actions.edit({',
            save: mount+'.actions.save({',
            delete: mount+'.actions.delete({',
            filter: mount+'.actions.filter({',
            search: `dispatch({__action:'search',`
        },
Here you see that search is wired to SAM's dispatchers, while the other actions are directly wired to the Angular2 components. No more parent/child insanity.
I'll publish the full sample over over the week-end.
I may actually like Angular2 more than React now...
Edward Mulraney
@edmulraney
Jun 22 2016 15:16
Jean-Jacques Dubray
@jdubray
Jun 22 2016 15:16
because it gives more options. I lose JSX, but I am sure someone can come up with it.
Edward Mulraney
@edmulraney
Jun 22 2016 15:16
i've rendered another "canvas" or conainer to draw a rectangle/net on
but without polluting the existing component/state code, its not obvious how to make this second canvas capable of drawing rectangles (nets). it should all exist inside one model still
Jean-Jacques Dubray
@jdubray
Jun 22 2016 15:22
well, yes and no, you could return a state representation that contains some closures such that the render function (which knows net and net2) would be able to execute them:
 function draw(net, rectangle) {
  return function(net) {
  let rect = reactangle ; 
  net.style.top = rect.from.y + "px"
  net.style.left = rect.from.x + "px"
  net.style.width = Math.max(0, rect.to.x - rect.from.x) + "px"
  net.style.height = Math.max(0, rect.to.y - rect.from.y) + "px"
}
}

then in display

props.forEach( (prop) => { 
     sr[prop](prop) ;
}

assuming the "prop" names matches between the div and the state representation

Edward Mulraney
@edmulraney
Jun 22 2016 15:26
sr = state.render - takes the div id which == prop?
Jean-Jacques Dubray
@jdubray
Jun 22 2016 15:28
stateRepresentation
it's the json structure that contains the elements that need to be displayed
you can have HTML, JSON, ... and functions
The virtual dom would not work well there...
Edward Mulraney
@edmulraney
Jun 22 2016 15:30
does model get messy as we need to manage rectangle1 and rectangle2and which rectangle we're receiving data from
Jean-Jacques Dubray
@jdubray
Jun 22 2016 15:31
there is no free lunch... at some point you can't remove all complexity.
Edward Mulraney
@edmulraney
Jun 22 2016 15:32
and so model.present needs to know which rectangle to target. which is simple enough
Jean-Jacques Dubray
@jdubray
Jun 22 2016 15:36
yes, that would be encoded in the action. You can use the "intent" mechanism to keep the UI Component generic (and reusable).
intents: {
          ...
draw_rect1: `dispatch({__action:'draw', rect: 'rect1',`,
draw_rect2: `dispatch({__action:'draw', rect: 'rect2',`,
...
}
and in the model you could have something like rects[data.rect] =
I'd say overall things are relatively painless with SAM, by I am biased. You can keep me honest.
Edward Mulraney
@edmulraney
Jun 22 2016 16:48
shouldn't draw belong to a component? so two components have their own draw function?
Jean-Jacques Dubray
@jdubray
Jun 22 2016 17:13
most likely it would be a method on a component. This is a closure, so there is only one "function" two instances.
Jean-Jacques Dubray
@jdubray
Jun 22 2016 17:24
I have implemented the Gone Fishing sample with a closure:
https://hyperdev.com/#!/project/plain-frog
function draw(x,y, w,h) {
    var x0 = x,
        y0 = y,
        w0 = w,
        h0 = h ;

    return function(ctx,canvas) {
      ctx.clearRect(0,0,canvas.width,canvas.height);
      ctx.fillRect(x0,y0,w0,h0);
    }
}
Jean-Jacques Dubray
@jdubray
Jun 22 2016 17:38
I structured the view components in a theme:
var theme = {

  draw(x,y, w,h) {
      var x0 = x,
          y0 = y,
          w0 = w,
          h0 = h ;

      return function(ctx,canvas) {
        ctx.clearRect(0,0,canvas.width,canvas.height);
        ctx.fillRect(x0,y0,w0,h0);
      }
  },

  fishes(count) {
      var disp = "" ;
      if (count>0) {
          var fishes = (count>1)? ' fishes!' : ' fish' ;
          disp = " you caught "+count+fishes ;
      } 
      return disp ;
  }

}
These components know nothing about the underlying application
The display of the view function then renders the "stateRepresentation"
display(stateRepresentation) {
                if (this.ctx) {
                    stateRepresentation.rect(this.ctx,this.canvas) ;
                }
                this.net.innerHTML = theme.fishes(stateRepresentation.fishes) ;
            }
The view is responsible for mounting the state representation in the browser.
Fred Daoud
@foxdonut
Jun 22 2016 18:02
@jdubray nice! :thumbsup:
another example of why I like the single state tree and single direction of data flow: a list of items with checkboxes next to them, with a checkbox at the top to checkall/uncheck all (for example, http://todomvc.com). The top checkbox not only checks/unchecks all, but should also change according to checking/unchecking individual items. Without SST and single direction of data flow, things can get a little messy.
Fred Daoud
@foxdonut
Jun 22 2016 18:07
But when views are just f(model), and triggering a change updates the SST and re-renders the view, everything is nicely in its place. the State object can have a simple "allChecked" function(model) -- it doesn't even have to be "checked", it could be e.g. "completed" in the todomvc example. So it's just data. Then, in the view, the checkbox's "checked" is set according to state.allCompleted. Single checkboxes trigger a change in the completed flag of the individual todo, the "All" checkbox changes the completed flag of all todos, and everything just works naturally.
Fred Daoud
@foxdonut
Jun 22 2016 18:13
Each view just uses the model to render the checkboxes, and triggers their events. No need to tie one to the other, no wondering "oh, a checkbox just changed, I better go and figure out if I need to change that 'All' checkbox..."
Jean-Jacques Dubray
@jdubray
Jun 22 2016 23:26

@foxdonut

Without SST and single direction of data flow, things can get a little messy.

Yes, I agree