These are chat archives for jdubray/sam

26th
Oct 2016
devin ivy
@devinivy
Oct 26 2016 12:40
@metapgmr_twitter i'm watching your PDX SAM talk, and it's :thumbsup:– you're a good orator. when you liken SAM to Paxos are you thinking of it two-fold? 1. because our UI shares state with other UIs on other computers. and 2. the UI itself is distributed via some sort of view component architecture, i.e. views expressing themselves to the application without knowing "the rules."
it struck me that thinking of our UI as living in a distributed setting has value independent of sharing state across networks. really, the atomic "thing" that is distributed with respect to your application is a view component being acted on by a user, whether that component is on your screen or someone else's.
Jean-Jacques Dubray
@metapgmr_twitter
Oct 26 2016 12:43
not sure about that...
Ultimately you could think of some cases where the model itself is distributed, but I can't think of a really good one right now.
Yes, absolutely, that is atomic everything else has the potential to be a complex distributed system.
devin ivy
@devinivy
Oct 26 2016 12:45
on the web the model is usually distributed, no? like right now we're both mutating some common database that stores these chats.
Jean-Jacques Dubray
@metapgmr_twitter
Oct 26 2016 12:46
Well, yes and know, I would argue that there is just one instance of the model, I'd be surprised if they run 2 or 3 copies concurrently
For me, the priority number one today is to build an architecture where network events (from friends, IoT,...) are on the same level of user events.
devin ivy
@devinivy
Oct 26 2016 12:47
okay, i'm not trying to go there with it, exactly. but you might say that a faithful representation of the "actual" model is replicated in memory in both of our browsers.
i think we're on the same page but talking past each other a bit :P
Jean-Jacques Dubray
@metapgmr_twitter
Oct 26 2016 12:48
yes that I agree, but not in a Paxos sense
I was at a meet up yesterday and someone did a presentation on RxJS, it was the usual (no application state) mouse drag use case showing that you could wire the event to DOM manipulation.
I finally pinpointed what bothers me with Rx. When you use observables on the view components to detect changes you are surrendering a lot of control
What SAM introduces is a state representation that decides what to communicate to the view (say in a functional way, V = f(M)). The problem with Rx is that people (including Cycle.js) tend to wire the V to the Model.
devin ivy
@devinivy
Oct 26 2016 12:51
so your main issue with RxJS is that there's no intermediate state representation?
Jean-Jacques Dubray
@metapgmr_twitter
Oct 26 2016 12:51
It's the same on the other side where Streams are directly wired to handlers which mutate the state.
devin ivy
@devinivy
Oct 26 2016 12:51
what about atoms and computed values?
Jean-Jacques Dubray
@metapgmr_twitter
Oct 26 2016 12:52
Yes, it was very clear yesterday, people see value in wiring events to effects through the Rx pipes
I believe that's an anti-pattern. It might work well for some simple problem (where you truly have a stream of events, like mouse drag).
I am not familiar with Atoms. I am not a big fan of computed values because they do not define a proper boundary of application state mutation.
I really like the idea proposal -> mutation -> render
devin ivy
@devinivy
Oct 26 2016 12:54
i can appreciate that. this is one issue i had with polymer. you can use them to create a boundary, though.
they don't do it themselves.
usually the boundary would be a wrapper component
Jean-Jacques Dubray
@metapgmr_twitter
Oct 26 2016 12:55
I am not completely sure of that, you'd have to make that the computed property is an object
but in general you'd have overlapping boundaries.
devin ivy
@devinivy
Oct 26 2016 12:55
hmm the problem might be that in my head i'm really thinking of how one would use polymer's observers rather than rxjs
Jean-Jacques Dubray
@metapgmr_twitter
Oct 26 2016 12:56
In general I am concerned by constraints imposed by the programming model, unwittingly.
I am not familiar with Polymer yet, with Vue 2.0 they are on my list but too many things going on
a property "boundary" is a hell of a constraint
Jean-Jacques Dubray
@metapgmr_twitter
Oct 26 2016 13:01
Take a simple example like this horrible experience from Chrome on Windows, where it interprets most of my mousepad strokes as zoom in/out, how can you implement a complex correlation between events without a centralized application state mutation. You simply can never predict when and how boundaries will be defined. When you put an arbitrary boundary (that makes sense) but want to add use cases, you'll be stuck.
I prefer units of work to mutate the model to be 100% independent of the action (proposal) boundary and the state representation/view components boundaries.
I'd never surrender that, Rx forces me to surrender on both ends.
Edward Mulraney
@edmulraney
Oct 26 2016 13:16
@metapgmr_twitter I'm not quite following what bothers you with Rx. Take RxJS in redux for example (redux-observable), the observables makes no decision on what you communicate to the view, it's an alternative way of handling async
you can still provide whatever data you want to your view component
you gain a convenient API for handling async stuff
its fully SAM compatible isn't it?
Jean-Jacques Dubray
@metapgmr_twitter
Oct 26 2016 13:25
Let me revisit that. I don't have time this morning, but I'd like to get to the bottom of it.
Zach Dahl
@schtauffen
Oct 26 2016 13:25
@edmulraney 's part, by utilizing redux-thunk, -observable or -sagas you can put async actions on the same level as user interactions
sorry that was barely a sentence, just shaking off the sleep
Edward Mulraney
@edmulraney
Oct 26 2016 13:26
@metapgmr_twitter thanks - looking forward to it :)
Jean-Jacques Dubray
@metapgmr_twitter
Oct 26 2016 13:27
@edmulraney if you have a specific code sample in mind, that would give me an easier way to look at it.
Edward Mulraney
@edmulraney
Oct 26 2016 13:28
take your favorite person's example:
@schtauffen sorry I didn't follow that at all!
Edward Mulraney
@edmulraney
Oct 26 2016 13:33
@schtauffen do you agree that redux-observables are a good way of handling async/side-effects?
Zach Dahl
@schtauffen
Oct 26 2016 13:37
Yes. Observables (and it's kin) are great :)
Edward Mulraney
@edmulraney
Oct 26 2016 13:39
cool :)
Jean-Jacques Dubray
@metapgmr_twitter
Oct 26 2016 13:46

@edmulraney so...

export default function searchUsers(action$) {
  return action$.ofType(ActionTypes.SEARCHED_USERS)
    .map(action => action.payload.query)
    .filter(q => !!q)
    .switchMap(q =>
      Observable.timer(800) // debounce
        .takeUntil(action$.ofType(ActionTypes.CLEARED_SEARCH_RESULTS))
        .mergeMap(() => Observable.merge(
          Observable.of(replace(`?q=${q}`)),
          ajax.getJSON(`https://api.github.com/search/users?q=${q}`)
            .map(res => res.items)
            .map(receiveUsers)
        ))
    );
};

I find this code highly inflexible. First, in essence, you are coding a state machine (with takeUntil). Then, you manipulate the model (replace) .

IMHO, this kind of code cannot scale (in complexity).
Edward Mulraney
@edmulraney
Oct 26 2016 13:54
  1. why do you think state machines don't scale, or are inflexible?
  2. the replace is just setting the router's navigation history - that won't touch the model (but also this is nothing to do with Rx, you can remove that line of code)
  3. what's an example where this kind of code can't scale in complexity
Jean-Jacques Dubray
@metapgmr_twitter
Oct 26 2016 14:16
  1. That's the foundation of SAM and the contribution of TLA+, the "state" cannot be decided from the action's point of view. That is a fundamental paradigm shift, action -> model -> state. Hard wiring actions together is simply the wrong thing to do. I know we have been taught otherwise, I spent 30 years believing in action -> state -> action.
3 - The example is when you have a more complex state machine and you need to make decisions on not just one action.
Zach Dahl
@schtauffen
Oct 26 2016 14:34
it is more action -> async function -> action -> model.present
Zach Dahl
@schtauffen
Oct 26 2016 15:06
if we call the redux "actions" events, then we would consider the entire sequence (event -> async-something -> event -> model.present) as the SAM action
with the bonus that we can also model.present off the first event in order to show a loading spinner / prepare otherwise for the async action to complete.
Edward Mulraney
@edmulraney
Oct 26 2016 15:13
@metapgmr_twitter
  1. in the above example, it is not the action SEARCHED_USERS that is deciding to dispatch future actions. It is a piece of business logic (searchUsers epic) which you could argue belongs to the model, as all epics have the model as the second parameter to the function. the decisions in the epic logic can be based off the model. your particular example highlights the fact that for fetching users from a server, you don't need to hit the model, until the async request is resolved and a new action receiveUsers is dispatched, at which point the reducer can handle the action and update the model if it wishes
Edward Mulraney
@edmulraney
Oct 26 2016 15:32
the SEARCHED_USERS action is simply: { type: "SEARCHED_USERS", payload: {query: "blah"}}
there's nothing in there about the next action
the epic, which logically resides within the domain of the model, makes decisions about which action to allow next
this is akin to SAM
you can transform your async result in that Epic logic however you like
Edward Mulraney
@edmulraney
Oct 26 2016 15:50
@schtauffen im not sure what you're saying. but you could show a spinner by setting isLoading=true in the reducer for SEARCHED_USERS, then set isLoading=false in the reducers for RECEIVED_USERS, CLEARED_SEARCH etc.
Jean-Jacques Dubray
@metapgmr_twitter
Oct 26 2016 15:57
Sorry, but you just can't have:
action$.ofType(ActionTypes.SEARCHED_USERS) ... .takeUntil(action$.ofType(ActionTypes.CLEARED_SEARCH_RESULTS)) in the same statement, that is the problem, not the solution.
In SAM, the state function computes the "state" from the model property values. It is the "state" (aka status) that decides from allowed actions, not to mention the concept of "step" we talked about yesterday which decides from an action "instance" perspective.
All I see in Rx is some bad code factoring that will not scale (in complexity).
Zach Dahl
@schtauffen
Oct 26 2016 16:30
I'm suggesting the event -> saga/epic -> event sequence is analogous to a SAM action. It fits within the pattern
Edward Mulraney
@edmulraney
Oct 26 2016 16:43
@metapgmr_twitter why cant you have that in the same statement? what about that statement is a problem?
Edward Mulraney
@edmulraney
Oct 26 2016 16:52
it's simply a succinct way of writing the equivalent logic to cancel an action. it wouldn't look too disimilar in vanilla... if (currentStep ... SEARCHED_USERS && nextAction == CLEARED_SEARCH_RESULTS) { xhr.abort() } terrible pseudo-code but you get the picture
this would belong in the logic relating to the model in SAM
Jean-Jacques Dubray
@metapgmr_twitter
Oct 26 2016 17:02
@edmulraney connecting the two actions is the problem, one cannot do that, that is what does not scale in complexity. That's the problem that SAM is solving.
The way you derive "what to do next / what's allowed next" is through the process proposal, mutation, derive new "state/status". You cannot decide at the action level what will happen. It works in the small scale, but TLA+ tells you that's not how you should think of it. I know we have been writing this kind of code for decades, but it is not the best way to code it.
Edward Mulraney
@edmulraney
Oct 26 2016 17:05
i think you're mistaking where that code resides. that code is not at the action level.
that code is not an action
Jean-Jacques Dubray
@metapgmr_twitter
Oct 26 2016 17:06
It's not a React action (which itself is merely an event), this is what SAM calls an action (triggered by an event).
Zach Dahl
@schtauffen
Oct 26 2016 17:14
Ok, so I think me and JJ are viewing it the same (event -> epic -> event) === SAM Action. What Edward is suggesting is that the epic is actually analogous to "state" code ?
Zach Dahl
@schtauffen
Oct 26 2016 17:21
so @metapgmr_twitter is saying just handle it in S function and not by creating a new S function/process for every event you want to handle asynchronously
???
Edward Mulraney
@edmulraney
Oct 26 2016 17:24
@metapgmr_twitter i would need to do some testing, but i imagine the semantic you mention: proposal -> mutation -> new state, is being followed in the example above. if you use the second parameter available to you (store, ->getState()) you would have the updated model
the above example is not an action by redux terms. it is middleware, which sits outside of actions completely. i think you may be confusing it for a redux action
Jean-Jacques Dubray
@metapgmr_twitter
Oct 26 2016 17:31
The semantics of "takeUntil" are pretty clear, take action1 until action2, just to be clear that's the issue that I have. The primitives/semantics of RxJS are action oriented like pretty much any programming paradigm. At a minimum the condition should be a function call like takeUntil(notAllowed(state,action1))
Rick Medina
@rickmed
Oct 26 2016 19:37
hello all! I'm learning sam! happy to be here
@metapgmr_twitter could you provide the equivalent sam code the rxjs code showed?
Fred Daoud
@foxdonut
Oct 26 2016 19:48
Welcome @rickmed !
Edward Mulraney
@edmulraney
Oct 26 2016 19:50
hi @rickmed. good idea. @metapgmr_twitter would be good to see the same trivial example in SAM so we can compare
Jean-Jacques Dubray
@metapgmr_twitter
Oct 26 2016 21:30
@rickmed Hi Rick, welcome!
Turns out that I had already written that code for an older version of Angular2 (beta4), but it does not matter since SAM is factored outside the framework.
The action looks like this :
 search: (data: any, present?: (data: any) => void)  => {
               present = present || _present ;
                let _data : any = {startWith: data.name} ;
               if (data.focus) {
                    _data.focus = data.focus ;
              }
              // next step of the reactive loop: present values to the model        
              present(_data) ;
                return false ;
        },
Jean-Jacques Dubray
@metapgmr_twitter
Oct 26 2016 21:36
The model would look like this (note that any other event than search would not contains startWith and focus:
var _CRUD = function (data) {
        console.log(data);
        // CRUD
        // switch item to edit mode
        if (data.startWith !== undefined) {
            _data.startWith = data.startWith;
            _data.focus = data.focus;
        }
        else {
            delete _data.startWith;
            delete _data.focus;
        }
As pointed by @chuckrector "focus" is a bit difficult to implement outside a framework, here is how I do it with nap():
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.  
                }
            }, 20);
        }

    } ;
The state representation is assembled using view components (dynamic templates):
var  _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
            });
        } ;
Here is the view component filtering the records:
 list: (model: any, intents: any) => {

            var edited : any = null;
            // generate the item list
            var rows = model.people.map( function(person: any) {
                const deleted = person.deleted || false ;
                var invisible = false ;

                if (model.startWith) {
                    invisible = (person.name.indexOf(model.startWith)<0) ;
                }

                if (deleted || invisible) { return '' ; }

                if (person.edited) {
                    edited = person ;
                }

                return `<tr>
                 <td>${person.name}</td>
                 <td>${person.city}</td>
                 <td>${person.email}</td>
                 <td>${person.number}</td>
                 <td><button class="btn btn-danger" onclick="return ${intents['delete']}'id':'${person.id}'});">Delete</button></td>
                 <td><button class="btn btn-warning" onclick="return ${intents['edit']}'id':'${person.id}'})">Edit</button></td>
             </tr>` ;
            }) ;
Jean-Jacques Dubray
@metapgmr_twitter
Oct 26 2016 21:42
If you wanted to call an API rather filtering the existing record, that would happen in the view, the response of the API call would used as a proposal to the model and everything else would be pretty much the same. You can use SAFE steps to handle the case when a new query is triggered before the response of the current one has presented its data.
Hope this helps.