These are chat archives for jdubray/sam

21st
Jun 2016
Jean-Jacques Dubray
@jdubray
Jun 21 2016 05:17

@riccardoferretti thank you for pointing out this sample and thank you to Peter for writing it. If Elm looks very elegant, it is nevertheless not implementing SAM's semantics (TLA+).

you get pretty much the SAM pattern

There is no notion of "pretty much" in the realm of semantics.

Elm is quite different from SAM.
1/ the signals / message express intent, not proposals. There is a fundamental reason (and many benefits) to decouple proposers from acceptors. Elm doesn't do that.

2/ the model should not trigger actions. The model accepts proposals and notifies learners when the model has mutated. So code like this when you say "Launched" (I assume it means Launched = true since Launched is not a message) is incorrect. The action must propose the value Launched = true and the model is responsible for accepting it based on other model property values. Similarly, it would be best for the model to be presented with a "decrementBy" proposal rather the model knowing which (control) state the system is in.

 ( Tick, Countdown c ) ->
            if c == 1 then
                Launched
            else
                Countdown (c - 1)

Whether an action is allowed to present data to the model should be decided outside the model. Actions should present the exact values you want the model to mutate to. It is key to keep these semantics as precise as possible. That has been my point all the along, they are not equivalent. I would assert that Elm semantics are wrong. Apologies for being so strong, but this is a very important point. I am not trying to create a case of I am right, they are wrong, I am asserting that you cannot arbitrarily come up with semantics and state they are "roughly equivalent". Small differences in semantics will decide how much boilerplate code you have to code to actually get to the right semantics.

Jean-Jacques Dubray
@jdubray
Jun 21 2016 06:05
blob
Let me expand on the presentation's example to explain SAM. Let draw a rectangle:
The traditional (event,callback) model looks like this:
function onMouseDown(event) {
 mouseDown = true
 rectangle = { from: event.position, to: event.position }
}

function onMouseMove(event) {
 if (mouseDown) {
   rectangle.to = event.position;
   draw(rectangle);
 }
}

function onMouseUp(event) {
 mouseDown = false
 rectangle = { from: event.position, to: event.position }
}
This code is compact and simple to reason about but suffers a number of problems from a software engineering, and we all know that approach does not scale to large code bases.
Here is what a SAM implementation would look like:
First the Actions (I kept event names, to make it easier to relate to the previous code:
function onMouseDown (event) {
 var proposal = { newStartingPoint: event.position }
 model.present(proposal) ;
}

function onMouseMove (event) {
  model.present({ newDiagonal: event.position }) ;
}

function onMouseUp(event) {
  model.present({complete: true, newDiagonal : event.position }) ;
}
Notice the difference? I don't see these states anymore: mouseDown, mouseUp,...
Jean-Jacques Dubray
@jdubray
Jun 21 2016 06:11
Why would the model know anything about the event? The action's role is to mediate the event/intent into a model mutation. That means that the same action and hence proposal dataset could be presented to the model when we wire different events (say touch events for instance) to the same action.
The action could also implement some application specific logic, such as we do not allow rectangle larger than max_width and max_height. The model should know nothing about these rules. If the validation fails, the proposal could be empty, or you could present an error condition, say that would result in drawing the rectangle in red, or whatever.
Then the acceptor code would look like this:
function present(data) {
  if (newStartingPoint) {
     model.rectangle = { from: event.position, to: event.position } ;
  }
  if (newDiagonal) {
     model.rectangle.to = event.position ;
  }
  state.render(model)
}
Finally the State function with mediate rendering the State Representation and triggering any automatic actions, such as "selectObjects"
function render(data) {
    state.representation(model) ;    
    state.nextAction(model)
}

function representation(model) {
    state.view.draw(model.reactangle) ;    
}

function nextAction(model)
    selectObjectsAction({objects: model.objects,area: model.rectangle}) ;
}
Which one do you prefer? Which one do you think would be less buggy? which one would be more maintainable?
Jean-Jacques Dubray
@jdubray
Jun 21 2016 06:17
The Elm implementation would not look like this at all, because Elm does not support a clear separation between proposers and acceptors, so you have to add boilerplate status codes to remember what you are doing.
This is true of any programming model that confounds intents with actions
Jean-Jacques Dubray
@jdubray
Jun 21 2016 06:31
The other extreme is when "actions" directly CRUD the model, they implement both the proposer and acceptor code (e.g the Redux reducer), in case, you have no choice but to give access to the entire model to the action.
The big issue comes when you are looking to deal with effects (such as API calls), then you don't know where you should implement these calls and conclude erroneously that they should stand on their own and the "pure" part of the code should make a request to realize an effect. Then you have no choice but again maintaining some ancillary state to remember that you requested an effect. Makes no sense to me.
Edward Mulraney
@edmulraney
Jun 21 2016 09:04

That was a great explanation of SAM. I have a few questions:

  1. In the SAM example, your event sends an intent/proposal with a payload to present (the acceptor), andpresent performs the change to state (mutation). In redux, you send an intent with a payload to the reducer (the acceptor), and the reducer performs the change to state (mutation).
    What is the difference?

  2. With regard to nextAction, what is model.objects? What is selectObjects? As a user what would this be doing for me?

  3. You call the present function "the acceptor". The terminology seems conflicting here since you called it present rather than accept. If the function name is "present", one would assume it's the presenter, not the acceptor?

  4. The SAM example is concerned with rendering and next actions. Is there a way to avoid this coupling?

  5. Is present the only function which can change state? How does one make this scale when dealing with lots of different models/pieces of state?

  6. model (app state/store) looks like a global, which makes it easier for you to access and directly mutate it. Does this not mean that anything can access model.rectangle and mutate it, from anywhere in the codebase?

  7. Where do you suggest putting the validation? e.g. is rectangle less than max_width? Is that in the event onMouseMove, or in the present function

  8. The SAM example doesn't track whether or not the mouse is down, in onMouseMove. How does it know whether the mouse is simple moving, or whether the user is drawing?

Jean-Jacques Dubray
@jdubray
Jun 21 2016 09:09
Peter Damoc has kindly provided the same example with Elm:
https://gist.github.com/pdamoc/e90b320a18cb77caaa54ee264fc5ba7a
Most interesting, you can run this example by copying the code here http://elm-lang.org/try (then, just draw a rectangle on the right end side)
Jean-Jacques Dubray
@jdubray
Jun 21 2016 09:28

@edmulraney

  1. What is the difference?

I know, it does not sound like much, but this decoupling leads to much healthier code. With SAM I can run actions wherever I like, even 3rd parties via Oauth. I can also run 3rd party API calls (getPostalAddress).

I can also start doing things like cancellation without the model ever knowing there was an action in flight (Action Hang Back). In Redux or Elm, you need to keep track of effects (the model needs to have something like fetching = true, then when the result comes back, you check it and move it back to fetching = false.)

2 what is model.objects

I was just trying to come up with an example of next-action, imagine you are drawing the rectangle to select objects within the rectangle. The model would keep a list of objects and the next action would be selectObjects( {objects, rectangle} ). That action would then present a list of selectedObjects.

3 you called it present rather than accept

Sure, present is more neutral, the acceptor can accept or reject, the action should merely present data to the model, it should no know what it does with it.

4 Is there a way to avoid this coupling?

I am not sure I understand your question, you can create any state representation you want (HTML, JSON, iOS / Android). The State function is here to decouple the model from the view. There are two aspects to achieve that decoupling:
1/ computing the state representation
2/ computing the next-action

5 Is present the only function which can change state

Yes, mutation is centralized (single state tree / unidirectional flow)
You can break down present, just like you would break down the reducer. Not sure if there are general ways. People complain about my if-statements. It's ok, union types can help.

It also enables the action to know very little about the model.

6 Does this not mean that anything can access model.rectangle and mutate it, from anywhere in the codebase

No absolutely no other part of the code can mutate the model. The state function is the only one getting to see the entirety of the model, to compute the current control state to derive next-action and state representation. The State function would then act as an adapter to invoke view components with model properties (without passing the model to the component, an obvious unwanted coupling)

7 Where do you suggest putting the validation?

It depends, you can put it in the proposer or acceptor, in general it's an easy determination. Business rules in the proposers tend to me more app/use case specific, while business rules in the acceptor generally control the integrity of the model.

8 The SAM example doesn't track whether or not the mouse is down

You don't need to!! I want to show that this is the benefit that SAM brings, you no longer have dedicated state machines like you have in MVI pattern, that is the beauty of decoupling proposers and acceptors. The semantics of that state machine are backed in the actions, not in the model. Same thing for API calls, SAM doesn't need to keep track if you are "fetching" something, it can be done in the action, it's only when you are ready to propose that the model mutates. Thats a big simplification. Not to mention that, that kind of code can bug easily....

Edward Mulraney
@edmulraney
Jun 21 2016 09:33
Thanks for answering so quickly :+1:
Jean-Jacques Dubray
@jdubray
Jun 21 2016 09:35
2:30 am in Portland, OR
Edward Mulraney
@edmulraney
Jun 21 2016 09:35
Did the presentation go well?
Edward Mulraney
@edmulraney
Jun 21 2016 09:53
About #1.. I don't see the difference between a proposal in redux (intent payload), and a proposal in SAM (intent payload)
Jean-Jacques Dubray
@jdubray
Jun 21 2016 09:57
Yes, I would say it went well, but the conference goes pretty fast so there is not a lot of room for discussions.
The difference is that the intent in Redux is wired to the view, the action is generally not a function. Of course that approach faced a number of issues and they introduced thunks/function generators.
With SAM event->action (proposal) -> model (accept)
With Redux event -> reducer -> store (which accepts everything)
Edward Mulraney
@edmulraney
Jun 21 2016 10:01
i can't see the difference
event -> action (== reducer), model (== store)
Jean-Jacques Dubray
@jdubray
Jun 21 2016 10:02
The store has no business logic, the reducer does the mutation
the store just says OK
It's really just a question of factoring, the role of the action is to compute the values of the properties you want the model to mutate to
the model takes solely the decision to accept or reject these values
These are hard semantics not subject to interpretation
You could implement them in Redux, of course, but this is not really how people use Redux.
The cobble together proposers and acceptors in the reducer. Just like Elm in the Update section.
Edward Mulraney
@edmulraney
Jun 21 2016 10:08
I've not used Elm yet so can't compare that example. But the reducer and the acceptor are the same thing. The proposer and the action-creator are the same thing
In redux: onMouseDown intent: RECTANGLE_NEW_STARTING_POINT, proposal: event.position
In SAM: onMouseDown: intent: newStartingPoint, proposal: event.position.
Then the reducer in redux can decide to accept the value or not, and perform validation
just like the acceptor in SAM
present can do validation against max_width etc.
concepts are equal, just seems the labels are different?
present does the mutation, reducer does the mutation
Edward Mulraney
@edmulraney
Jun 21 2016 10:13
in both redux and SAM, onMouseDown sends intent with proposed payload
Jean-Jacques Dubray
@jdubray
Jun 21 2016 10:38

the reducer and the acceptor are the same thing. The proposer and the action-creator are the same thing

Yes, I agree, but this is generally not the way people use Redux. They only use an action creator when they have to deal with Asynchronous "actions"

I would argue that 90 % of the time, Redux is used in an MVI fashion, as
event/intent -> (propose/accept) -> store
Of all the frameworks, Redux is closest but the semantics are too loose and things like Sagas are a deal breaker.
With that in mind, Redux is trying to go the Elm direction, not SAM's.
So I would tend to disagree with your assessment.
Edward Mulraney
@edmulraney
Jun 21 2016 10:58
interesting. I use action-creators for everything, async or not. The last company I contracted for did it this way, but this may not be reflective of the genereal community, altho I have seen redux examples in this way
Jean-Jacques Dubray
@jdubray
Jun 21 2016 11:02
I don't know too much about the community, but most of the examples do not use action-creators. I would prefer if Dan could clarify that point.
My impression is actually quite the opposite, Dan favors not using action-creators for things other than long running actions.
He is focused on dev tools like Time Travel, and it's a lot harder to implement TimeTravel with action creators.
Now the next question is where do you see the state function in React/Redux? Do you use Sagas?
Edward Mulraney
@edmulraney
Jun 21 2016 11:08
I don't use sagas
could you provide a working example of the rectangle example above?
for example, state is undefined
Jean-Jacques Dubray
@jdubray
Jun 21 2016 11:34
yes, I will can't do it now
Edward Mulraney
@edmulraney
Jun 21 2016 11:54
thank you
devin ivy
@devinivy
Jun 21 2016 12:24
@jdubray have you looked at redux-loop?
it's interesting at least!
Edward Mulraney
@edmulraney
Jun 21 2016 12:36
@devinivy but its still describing the sequence of intents inside the reducer - which I think is the wrong place
my issue with redux-loop is that, given three actions, A, B, C, if you want an action that sequences A, B and C, you have to place B and C in the reducer for A
but that means B and C are now coupled to A, and can't be run independent. What if I just want to run A?
I can't see why you'd want to impose this limitation
It's also got a bit of buy-in to get it working Effects.batch, Effects.Promise etc etc. Not as much as sagas but still quite a bit of bloat
devin ivy
@devinivy
Jun 21 2016 12:40
sure! i don't think it's "the answer" but it's a very interesting approach
Edward Mulraney
@edmulraney
Jun 21 2016 12:40
agreed
devin ivy
@devinivy
Jun 21 2016 12:40
how do you orchestrate your actions? just out of curiosity.
i didn't read too far above, i apologize if you're repeating yourself!
Edward Mulraney
@edmulraney
Jun 21 2016 12:45
just made a piece of middleware which picks up actions with a type of SEQUENCE. the payload of a SEQUENCE action is a list of functions which return actions. the parameter to the functions is the result of the previous dispatched action, and the output must be a new action.
this gives you sequencing actions, but also the ability to make decisions based on the result of the previous action
e.g. you may only want to execute B after A, if A returns some particular value
i'll make an example
devin ivy
@devinivy
Jun 21 2016 12:55
that sounds nice
Jean-Jacques Dubray
@jdubray
Jun 21 2016 16:22
So here is my take on the rectangle sample with nap
https://github.com/jdubray/sam-samples/blob/master/js-rectangle/rectangle.html
It's a little fishing game, their are 10 fishes in the pound, when you draw a rectangle, the app count how many fishes you caught.
It probably needs a bit of cleanup, I am not been able to remove as much "status" as I would have hoped.
Edward Mulraney
@edmulraney
Jun 21 2016 16:27
nice one @jdubray !
Jean-Jacques Dubray
@jdubray
Jun 21 2016 16:35
yes, but I found a bug :-)
I also published it on HyperDev: https://plain-frog.hyperdev.space/
Jean-Jacques Dubray
@jdubray
Jun 21 2016 16:40
Corrected the bug.
Edward Mulraney
@edmulraney
Jun 21 2016 16:44
awesome
are you supposed to be able to see fish? i guess not, that makes sense
Jean-Jacques Dubray
@jdubray
Jun 21 2016 16:47
no :-(
Edward Mulraney
@edmulraney
Jun 21 2016 16:48
i imagined moving fish and casting a net to catch - but actually this obviously makes more sense - doh
Jean-Jacques Dubray
@jdubray
Jun 21 2016 16:49
I should limit the size of the net too
This is where proposer vs acceptor gives a nice decision point
if the model is shared amongst multiple types of games, then the rule is in the action/proposer
if this is a rule common to all games, then the rule must be in the acceptor
Fred Daoud
@foxdonut
Jun 21 2016 17:14

@jdubray maybe I misunderstood, but I find this misleading:

8 The SAM example doesn't track whether or not the mouse is down
You don't need to!! I want to show that this is the benefit that SAM brings

"You don't need to" is... false, you still need to somewhere... e.g. as per your example:
model.present({stopDrawing: true})
if (data.stopDrawing) {
    this.dragging = false ;
    this.countFishes = true ;
}
Jean-Jacques Dubray
@jdubray
Jun 21 2016 17:16
yes, spoke too fast... I'll check if I can find a better way.
in all fairness, the code only tracks mouseup
not mousedown
Fred Daoud
@foxdonut
Jun 21 2016 17:17
errr..
function mouseDown(e) {
    var rect = {} ;
    rect.startX = e.pageX - this.offsetLeft;
    rect.startY = e.pageY - this.offsetTop;
    model.present({newRectangle: rect}) ;
}
function mouseUp() {
    model.present({stopDrawing: true}) ;
}
Jean-Jacques Dubray
@jdubray
Jun 21 2016 17:19
what I meant is that there is no if mousedown ... anywhere
of course the event needs to trigger an action...
mouseup = stop drawing
Fred Daoud
@foxdonut
Jun 21 2016 17:21
well, I don't want to split hairs here, the logic still has to be somewhere. that being said, I do prefer where you put the logic in your example.
Jean-Jacques Dubray
@jdubray
Jun 21 2016 17:23
yes if course, I just want to show that SAM reduces the need to keep track of these ancillary state machines.
But I may be too optimistic
devin ivy
@devinivy
Jun 21 2016 17:26
@edmulraney is your SEQUENCE middleware open-source?
Fred Daoud
@foxdonut
Jun 21 2016 17:38
@jdubray @edmulraney in regards to the previous conversation comparing SAM and Redux. I too am not 100% clear on why Redux is incompatible with the SAM pattern. Now, bear in mind that I mean just Redux, not all of the other plugins, middlewares, and other libraries in the ecosystem. Also, I can see how JJ sees idiomatic Redux (all the examples out there) do not adhere to SAM principles. BUT, is it possible to apply the principles of SAM, using Redux? Even ignoring what Redux tells you to do (return immutable objects) -- it does not force you, you could just mutate the model. store.dispatch(data) with the same data as model.present(data), ignoring what Redux tells you about actions. Finally, the reducer being the equivalent to model.present, again not necessarily adhering to what Redux tells you to do. Is that possible?
I'm just asking, I'm just curious, and want to make sure I understand.
Not proposing this is a great idea ;)
Jean-Jacques Dubray
@jdubray
Jun 21 2016 17:41
I am at the nodepdx with very spotty internet access, as soon as I am connected I'll respond.
Fred Daoud
@foxdonut
Jun 21 2016 17:41
thank you in advance!
Jean-Jacques Dubray
@jdubray
Jun 21 2016 17:50
@foxdonut First we need to agree on the semantics, what redux calls an action, is not, it is an event or intent. Some actions may be pass through but this is rarely true.
SAM's actions are therefore implemented in the reducer (again assuming we use idiomatic Redux without action-creators).
If you want to separate proposers and acceptor(s), you can technically do that inside the reducer. In general people do not implement that separation, you have a big switch statement, and based on the "action" you execute some come code and pass the result to the store.
My understanding is that the store itself does not implement any business logic, the reason being that the store is connected to "react" and as soon as you change something in the store, your app "reacts" to the changes.
So I am not sure you could implement the present method in the store.
Last but not least API calls are a complete after thought in React/Redux and it would be strictly forbidden to call an API from the reducer (actions + model)
Fred Daoud
@foxdonut
Jun 21 2016 17:55
ok but I specifically said to ignore idiomatic Redux...
Jean-Jacques Dubray
@jdubray
Jun 21 2016 17:55
So technically, of course everything is possible, but it is not the general direction of where the Redux community is going
I'd say the biggest constraints are:
  • the connection between the store and react
  • the (pure) functional aspects of the reducer
Fred Daoud
@foxdonut
Jun 21 2016 17:56
so you would use action creators, you would separate proposers and acceptors...
that's a good question. maybe I'll experiment with some code.
Jean-Jacques Dubray
@jdubray
Jun 21 2016 17:56
It sounds like the most reasonable way to do it
the least disruptive
Fred Daoud
@foxdonut
Jun 21 2016 17:57
also Redux is not tied specifically to React. personally I don't like the Redux/React integration.
Jean-Jacques Dubray
@jdubray
Jun 21 2016 17:57
sure, it is not, but semantically it is designed to work with react
Fred Daoud
@foxdonut
Jun 21 2016 17:57
thanks @jdubray that clears some things up. if I come up with an example, I'll share here.
Jean-Jacques Dubray
@jdubray
Jun 21 2016 17:57
great!
Fred Daoud
@foxdonut
Jun 21 2016 17:57
again, this is for my understanding and curiosity
I still agree with you that far and wide how Redux is used is not following the principles of SAM.
Jean-Jacques Dubray
@jdubray
Jun 21 2016 17:59
The reason is because people are trying to separate the effects from the logic, IMHO, I believe that's not a viable solution.
Fred Daoud
@foxdonut
Jun 21 2016 17:59
and even if it's possible to "force Redux into submission" (maybe it's not even possible), I am not suggesting this would be necessarily a good idea or a good practice.
but perhaps it's not even possible. in which case your arguments hold even more weight.
sure, it is not, but semantically it is designed to work with react
that's a good point.
Jean-Jacques Dubray
@jdubray
Jun 21 2016 18:01
From what I can tell people seem to always report problems in scaling React/Redux to a large code base
It would be interesting to discuss with these people to see if SAM could help.
Fred Daoud
@foxdonut
Jun 21 2016 18:04
I found RxJS to suffer from the same problem
Jean-Jacques Dubray
@jdubray
Jun 21 2016 18:06
There are two problems when you scale FRP implementations:
  • Cerebral sees scalability issues when the size of the model increases --> immutable data structures do not work (SAM's model is of course not immutable)
  • Code complexity:
  • subscription management
  • micro-state machines to deal with effects
  • general decoupling (proposers, acceptors, learners)
I don't believe FRP will be able to overcome these issues
I love the joke: "functional programming cannot change the world..."
Fred Daoud
@foxdonut
Jun 21 2016 18:18
awesome :)
Edward Mulraney
@edmulraney
Jun 21 2016 18:22
:+1: good discussion - i'll reply to all this soon. one quick comment is that where @jdubray seems to find redux not normally compatible with the principles of SAM, I've found that redux complements the SAM patttern, or at least provides a convenient framework for it. I'm going to work on an example to demonstrate the stuff I've been talking about
Jean-Jacques Dubray
@jdubray
Jun 21 2016 18:23
happy to change my mind. It would be a big win-win.
I am watching a talk on RethinDB, that's stuff is awesome, very well aligned with SAM because you can define easily customer events you are interested in (e.g. get notified when a user profile has more than 30 likes)
Jean-Jacques Dubray
@jdubray
Jun 21 2016 18:28
devin ivy
@devinivy
Jun 21 2016 18:34
yeah, i've been checking out the developer preview of horizon
also gundb
Fred Daoud
@foxdonut
Jun 21 2016 18:54
@jdubray @edmulraney here is the SAM rocket launcher with Redux and plain JS: http://codepen.io/foxdonut/pen/BzQzBx?editors=1010
Some notes:
  • Redux forces you to have an object with a type when calling store.dispatch. So I wrapped it to always use {type: "PRESENT", data:data} to make it equivalent to model.present.
  • The modelPresent function gets the data out and ignores the type.
  • Redux dispatches an initial action with a special type, so we need to ignore that. if (!data) { return model; }
  • Otherwise, the rest is very much the equivalent of the canonical SAM rocket launcher example. Mutating the model and returning it does not seem to fluster Redux.
Jean-Jacques Dubray
@jdubray
Jun 21 2016 20:05
Yes, that works too. I guess Redux becomes just a way to wire the pattern then. It would allow any one to use dev tools (time travel...).
thank you for the sample
I'll add it to the pattern's page
Fred Daoud
@foxdonut
Jun 21 2016 20:13
great, thanks for having a look. we can now at least say that we've had a peek at that avenue.
Jean-Jacques Dubray
@jdubray
Jun 21 2016 20:13
We just got a demo of ScreenCat https://github.com/maxogden/screencat
Really cool tool for working remotely
Jean-Jacques Dubray
@jdubray
Jun 21 2016 22:13

@foxdonut I was able to eliminate these ancillary "states" from the model, now the present looks like this:

present(data) {
            data = data || {} ;

            this.countFishes = data.stopDrawing ;

            if (data.selectedObjects !== undefined) {
                this.selectedFishes = data.selectedObjects ;
            }

            if (data.newRectangle) {
                this.rect = data.newRectangle || {startX: 0, startY: 0} ;
                this.rect.w = 0 ;
                this.rect.h = 0 ;
                this.selectedFishes = 0 ;
            }

            if ((data.newDiagonal) && (this.selectedFishes === 0)) { 
                this.rect.w = data.newDiagonal.w - this.rect.startX || 0 ;
                this.rect.h = data.newDiagonal.h - this.rect.startY || 0 ;
            }

            this.state.render(model) ;
        }

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:
function mouseMove(e) {
  if (drag) {
    rect.w = (e.pageX - this.offsetLeft) - rect.startX;
    rect.h = (e.pageY - this.offsetTop) - rect.startY ;
    ctx.clearRect(0,0,canvas.width,canvas.height);
    draw();
  }
}
Jean-Jacques Dubray
@jdubray
Jun 21 2016 22:30
You should also take a look at cypress.io
Edward Mulraney
@edmulraney
Jun 21 2016 23:39
@jdubray you moved the conditional check for dragging out of mouseMove, at the cost of executing present when you don't need to. it's not so bad, but just something to keep in mind
personally I'd keep that check for whether or not you should present a new drag to the model
Jean-Jacques Dubray
@jdubray
Jun 21 2016 23:44
@edmulraney I agree that's a bit artificial. Don't you need to present data to the model? You need to draw the new rectangle once the mouse has moved. Otherwise I agree, if an action ends up not having to present data to the model, then you should not present anything.