These are chat archives for jdubray/sam

22nd
Nov 2016
Jean-Jacques Dubray
@jdubray
Nov 22 2016 00:51
@edmulraney you have to be careful though, the model works a bit like a database (concurrency wise), but actions and state function work like a state machine. You don't have actions and state in a database. Mechanisms like WAL are below the present method, not above.
Jean-Jacques Dubray
@jdubray
Nov 22 2016 11:59

@edmulraney if we step back a second, in my humble and most profound opinion, we always have to go back to the state machine behind what we are trying to do. When you are writing:

action$.ofType(FETCH_USER)
    .mergeMap(action =>
      ajax.getJSON(`https://api.github.com/users/${action.payload}`)
        .map(fetchUserFulfilled)

You are merely writing a state machine. The problem stems from the fact that the second action (fetchUserFulfilled) may no longer be applicable.
Functional programming imposes no constraint on mutation, it only focuses on isolating side effects. How logical is that? shouldn't you be as precise as you can when you are mutating the application state? Imagine a database operating under the principles of functional programming?

You simply cannot afford to not define the proper boundaries between actions, model and state. Function composition is the worst concept ever invented in software engineering, no matter how you wire it.
Jean-Jacques Dubray
@jdubray
Nov 22 2016 12:04
I'd argue that it is simply wrong to substitute a function composition to a state transition.
Edward Mulraney
@edmulraney
Nov 22 2016 12:05
@jdubray the exact problem you describe exists when you do the equivalent in SAM:
function fetchUsers() {
  present(FETCH_USERS)
  ajax.get("url/users")
    .then(users => present(fetchUsersFulfilled(users)))
    .catch(error => present(fetchUsersFailed(error)))
}
fetchUsersFulfilled may no longer be applicable
it completely makes sense to remove mutations from functions where you can, regardless of whether your mechanism for managing a cache/model is via mutation or replacing an immutable model, this is not what the issue here is
also function composition clearly has obvious benefits in programming.... add(multiply(substract..))) etc.. you have to specify in what relation is it "the worst concept ever invented" in your opinion
not everything in programming is within relation to mutating a model ha
Edward Mulraney
@edmulraney
Nov 22 2016 12:18
data processing, transformations... etc etc
interestingly - the exact problem you described with RxJS above, is very easily solved with RxJS but very difficult in vanilla JS in SAM. we're concerned that fetchUsersFulfilled may no longer be applicable - we can define that conveniently and declaratively with takeUntil()etc.
Jean-Jacques Dubray
@jdubray
Nov 22 2016 13:25

I know, and I said it repeatedly, as long as it is compatible with the State Machine behind it, it's ok, especially when we are talking about the next-transition. When software is written as-function-compositions the state machine disappears completely and you can no longer reason about what's allowed to do in the current state.

function composition clearly has obvious benefits in programming.... add(multiply(substract..)))

yes, agreed, as long as it does not interfere with the state machine. The problem is when we mix the two.

Jean-Jacques Dubray
@jdubray
Nov 22 2016 13:31

fetchUsersFulfilled may no longer be applicable - we can define that conveniently and declaratively with takeUntil()etc

yes and no, because you know have to pull enough of the model inside TakeUntil to decide when to stop. There is no free lunch.
I am not sure we'll convince each other. Until someone expresses an exact equivalence between functional programming and TLA+ state machines, I'll stay on the TLA+ /State Machine side. Not sure anyone could argue otherwise, but it's not the first time I have seen our industry plow into a dead end, at full speed.

Edward Mulraney
@edmulraney
Nov 22 2016 13:51
"yes and no, because you know have to pull enough of the model inside TakeUntil to decide when to stop. There is no free lunch."
you can't actually pull any model data into takeUntil, it's only checking for an action CANCEL_FETCH_USERS for example
you'll never get an equivalence between "functional programming" and "TLA+ state machine", because one is a style and one is a set of semantics that don't hold a bearing on the style. e.g. you could have a state machine in C# or javascript or haskell
@jdubray i'm trying to learn rather than convince ... im not sure how you got that idea
Jean-Jacques Dubray
@jdubray
Nov 22 2016 13:58
Let's take the example of debounce, a traditional state machine would look like this:
blob
A TLA+ state machine would look more like that:
blob
illustrating, once more, that an action cannot decide on the end state
Jean-Jacques Dubray
@jdubray
Nov 22 2016 14:05
I understand how to achieve a similar state machine with RxJs, but sooner or later you'll start facing cases where the logic is a lot more complex than pick a changed value every 500 ms, that's why I feel more comfortable relying on the model making the decision of the end state after every action.
It's not surprising that the idea of a Reducer popped up in Functional Programming, intuitively that's the right direction. The problem is making the reducer a pure function, it is simply unnecessary.
Edward Mulraney
@edmulraney
Nov 22 2016 14:08
but in SAM we let an action decide the end state just as much as we do in rx/redux:
const incrementCounter = () => ({type:"INCREMENT_COUNTER"})
const incrementCounter = () => ({type:"INCREMENT_COUNTER"})
which one is sam which one is redux?
they're the same
Jean-Jacques Dubray
@jdubray
Nov 22 2016 14:08
writing state = r(state,action) brings no value to the programming model
as I said before it's a question of semantics, if you constrain an action to be solely a proposal (data structure) then it is not SAM
some actions can "do nothing", they can just pass the event to the model as a proposal
Edward Mulraney
@edmulraney
Nov 22 2016 14:10
sure - thats exactly the same as redux
in terms of actions
so we're not breaking action semantics
Jean-Jacques Dubray
@jdubray
Nov 22 2016 14:10
but that's not a desirable coupling because now you constrain the client/view to be knowledgeable about the structure of the model or vice versa
Edward Mulraney
@edmulraney
Nov 22 2016 14:11
sure - you dont have to do that in either SAM or redux

and also - rxjs makes it very easy to do what you just said:

I understand how to achieve a similar state machine with RxJs, but sooner or later you'll start facing cases where the logic is a lot more complex than pick a changed value every 500 ms, that's why I feel more comfortable relying on the model making the decision of the end state after every action

that's precisely the point - rxjs makes it a lot easier to perform complex logic not just delays

Jean-Jacques Dubray
@jdubray
Nov 22 2016 14:13
I'd be happy to be proven wrong, but I'd like you to elaborate.
say with the debounce example
Edward Mulraney
@edmulraney
Nov 22 2016 14:17
i dont understand your diagrams - not familiar with the symbols
Jean-Jacques Dubray
@jdubray
Nov 22 2016 14:22
that's what is hard in this type of this discussion
the rounded rectangle is a "state" (control state), the triangle is an action
in traditional state machines the actions connect the states.
Jean-Jacques Dubray
@jdubray
Nov 22 2016 14:27
With SAM, the model mediates the transition initiated by an action, to the resulting state (control state)
In the case of the traditional state machine, the "update" action is an automatic action which is initiated based on some conditional logic (the debounce logic)
Edward Mulraney
@edmulraney
Nov 22 2016 15:01
in redux the reducer mediates the transition initiated by an action to the resulting state
Jean-Jacques Dubray
@jdubray
Nov 22 2016 15:02
I know, Redux is close but not quite. It's not like you can pick and choose or make up semantics in a programming model.
Redux is half-way between FP and TLA+
FP puts too much constraint on mutation
requiring the reducer to be a pure function is unnecessary
an action is not a data structure
Edward Mulraney
@edmulraney
Nov 22 2016 15:13

I know, Redux is close but not quite. It's not like you can pick and choose or make up semantics in a programming model.

that's all people do. that's all leslie lamport did. his seem the best at the moment, but something better may come along - this is irrelevant

Redux is half-way between FP and TLA+

You keep using the term FP when really you mean something else. in this case you mean immuatable model i guess? I already mentioned this, you're comparing two very different things... in FP you can have mutable objects... you just don't need them. But again - this is besides the point.

FP puts too much constraint on mutation, requiring the reducer to be a pure function is unnecessary

sure, it may be unnecessary by your requirements but if someone wanted an optimized virtual dom then they could benefit from an immuatable model. unnecessary != wrong. making a helper function may be unnecessary but that doesn't make it wrong, it may simplify the life of the dev or allow composing better things.

an action is not a data structure

sure but we're talking about concepts here. whether Dan incorrectly named intents "actions" is beside the point, if i pretend Dan named them "intents" it doesn't change the pattern/flow

quite hard to discuss an issue when you start talking about all these other things ha. easier to stick to the issue in hand

the point was that you said: "With SAM, the model mediates the transition initiated by an action, to the resulting state (control state)"
and i pointed out that redux follows this same convention
Jean-Jacques Dubray
@jdubray
Nov 22 2016 15:16
ok, sorry. I forgot the issue at hand.
:-)
Zach Dahl
@schtauffen
Nov 22 2016 15:20
IMO sagas and epics just utilize a little bit of "application state" similar to storing input state in the front end inside a view instead of forwarding it on to the model. If it is okay for the view to manage its own application state, I find it hard to argue that epics break SAM
Jean-Jacques Dubray
@jdubray
Nov 22 2016 15:21
I never said it was ok to store application state in the view components. I only said it was ok to store "config state" in the view components. It is of course, perhaps, the biggest anti pattern to store application state outside the model.
Zach Dahl
@schtauffen
Nov 22 2016 15:23
so when you have an input, would you forward it to the model on every change? or do you let the DOM component maintain the input state until it is required by an action?
Jean-Jacques Dubray
@jdubray
Nov 22 2016 15:24
That's not application state, that's the state representation...
that value is not expected to match whatever value is in the model
there is no data binding
If we go back to the debounce example, in SAM it would look like this:
actions.setRange = (data) => {
    let _data = data || {} ;
    _data.__action = 'changeValue';
    _data.lastTime = new Date().getTime();

    model.present(_data) ; // update the time in the model for debounce
    setTimeout(function() {
        _data.__action = 'updateValue';
        model.present(_data);
    },200) ;
    return false ;
} ;
In the model:
 if (data.__action === 'changeRange') { 
            model.data.value = data.value ;
            model.data.lastTime = data.lastTime;
            render = false ;
        } else {
            if (data.lastTime >= model.data.lastTime) {
                ... do what ever you want ... including getting ready to trigger a fetch action
            } else {
                render = false ;
            }
        }
Jean-Jacques Dubray
@jdubray
Nov 22 2016 15:29
I have full control of the model, no need to use specialized pipes, the logic is rather trivial and I can implement anything I want in response to "change" or "update".
From what I can tell, when you take the basic Redux programming model (event,reducer,store), each time you need to solve a problem you need to introduce new concepts (Thunks, Sagas, Streams, Selectors...).
That alone should tell you that the Redux programming model is not adequate.
Edward Mulraney
@edmulraney
Nov 22 2016 15:35
theyre just functions - a thunk is no more a new concept than the equivalent sam async call:
function async() {
  present("FETCHING")
  ajax.get().then(present("FETCHED").catch(present("FAILED"))
}
that's all a thunk is
you dont need sagas/epics/etc. unless you want a nicer api around doing what we just did with the thunk
otherwise you end up writing code like what you just did... the whole point is convenience, maintainability, cleanness, simplicity, readability, less error prone, etc.
Jean-Jacques Dubray
@jdubray
Nov 22 2016 15:38
no one says you cannot encapsulate that code, but encapsulating it does not break any principle, like say Sagas do.
Edward Mulraney
@edmulraney
Nov 22 2016 15:39
well lets ignore sagas - i dont use them
Jean-Jacques Dubray
@jdubray
Nov 22 2016 15:41
Look, in the end, I am not saying you cannot implement SAM with Redux. I am simply questioning Redux semantics and suggest that there may be merit adopting SAM semantics of action/model/state vs action/reducer/store.
Edward Mulraney
@edmulraney
Nov 22 2016 15:49
and what im suggesting is that the semantics of SAM are already encapsulated in redux despite the names Dan has chosen and despite what 3rd party libraries people may have made for it
Jean-Jacques Dubray
@jdubray
Nov 22 2016 15:50
It depends what you mean by encapsulated, some are SAM aligned, some are not. I am just suggesting to stay away from the ones that are not.
Edward Mulraney
@edmulraney
Nov 22 2016 17:46
how would you make nap only execute what it needs to?
nap relating to component A doesnt need to run when component B is running
Jean-Jacques Dubray
@jdubray
Nov 22 2016 18:00
the semantics of nap are as follows:
   if (state(model) === status1) {

         // trigger actionX   
   }

  if (state(model) === status2) {

         // trigger actionY   
   }
you should tie it to a control state / status rather than a component
Fred Daoud
@foxdonut
Nov 22 2016 18:02
@edmulraney forgive me if I misunderstand, just catching up with the discussion (there is a lot there!)
so as you wrote, this is how I would do async:
function async() {
  present("FETCHING")
  ajax.get().then(present("FETCHED").catch(present("FAILED"))
}
Jean-Jacques Dubray
@jdubray
Nov 22 2016 18:02
I know it's a bit tedious, but well worth it overall.
Of course you would not want to implement it as I wrote it, it can be made very modular and optimized, but that's the idea
Fred Daoud
@foxdonut
Nov 22 2016 18:03
you said "that's all a thunk is"
return {
    type: "XHR_REQUEST",
    payload: {
      type: "SEND_EMAIL",
      url: "http://asdf.com/email",
    }
Edward Mulraney
@edmulraney
Nov 22 2016 18:06
that? no
Fred Daoud
@foxdonut
Nov 22 2016 18:06
but to me there is a much different implication here. In the first code snippet, the action does the async call and presents to the model. The model doesn't need to know or care about async. But in the second code snippet, suddenly you are embedding logic in the action's data structure, and you need the model to know, "oh, if the action's payload has a URL, that means I need to make an ajax call" or, IIRC something along the lines of "if the payload is a promise, then I need to..." and you are going down a slippery road of needing the model (or middleware, or what have you) to be tied to these "special" actions
Ok even if that is not it, nevertheless a thunk is a "special" kind of action and there needs to be code somewhere that knows what to do with that type of action.
Edward Mulraney
@edmulraney
Nov 22 2016 18:08

i was saying that this:

function async() {
  present("FETCHING")
  ajax.get().then(present("FETCHED").catch(present("FAILED"))
}

is the same approach in sam and redux (thunk)

this:
return {
    type: "XHR_REQUEST",
    payload: {
      type: "SEND_EMAIL",
      url: "http://asdf.com/email",
    }
is my attempt at an alternative approach
Fred Daoud
@foxdonut
Nov 22 2016 18:08
so what is the same approach but in a redux thunk?
Edward Mulraney
@edmulraney
Nov 22 2016 18:14
the same but present=dispatch
function async() {
  return function(dispatch) {
    dispatch("FETCHING")
    ajax.get().then(dispatch("FETCHED").catch(dispatch("FAILED"))
Jean-Jacques Dubray
@jdubray
Nov 22 2016 18:17
My concern with Redux thunks is that the dispatch decision is taken ahead of time, in SAM, I prefer having the action making the decision to present or not (or some middleware like SAFE).
Edward Mulraney
@edmulraney
Nov 22 2016 18:20
what do you mean its taken ahead of time? its identical as above
Fred Daoud
@foxdonut
Nov 22 2016 18:22
@edmulraney are we talking about the same redux thunk? I am talking about this middleware: https://github.com/gaearon/redux-thunk
Edward Mulraney
@edmulraney
Nov 22 2016 18:22
yes exactly that
what's the issue?
Fred Daoud
@foxdonut
Nov 22 2016 18:23
so the code you showed above, won't work unless you add the redux-thunk middleware?
Edward Mulraney
@edmulraney
Nov 22 2016 18:23
im talking about redux-thunk - indeed
Fred Daoud
@foxdonut
Nov 22 2016 18:24
why wouldn't that code just work as-is?
Edward Mulraney
@edmulraney
Nov 22 2016 18:24
due to not exposing dispatch globally
its purely wiring
you can wire sam the same
Fred Daoud
@foxdonut
Nov 22 2016 18:25
hmm ok
Edward Mulraney
@edmulraney
Nov 22 2016 18:26
i have to go but will check back here probably tomorrow :) cheers guys
Fred Daoud
@foxdonut
Nov 22 2016 18:27
cheers, thanks @edmulraney
Edward Mulraney
@edmulraney
Nov 22 2016 20:57

@jdubray

the semantics of nap are as follows:

   if (state(model) === status1) {

         // trigger actionX   
   }

  if (state(model) === status2) {

         // trigger actionY   
   }

you should tie it to a control state / status rather than a component

This means that I now need to tightly couple my components with control state. i.e. i need to track which component is currently loaded in the control state in order to make a decision about which actions to trigger

you don't want actionA to trigger if you've only loaded componentB
actionA only should trigger when componentA is loaded
i would put nap on a component level naturally. I don't see what benefit it has sitting at the top level and then having to use extra logic to work out which actions it truly should or shouldn't execute
Jean-Jacques Dubray
@jdubray
Nov 22 2016 23:04
@edmulraney sorry, I don't see it that way, the components being displayed (state representation) are tied to the (control) state and the next-actions are tied to the (control) state. I don't see a strong coupling between the view components and the next-actions.
There is a stronger coupling when you need to do some fancy JQuery stuff (data/time pickers) , but other than that, I never tie a next-action to the view components. That's inherent to JQuery.