I do say that I wish there were some way to make some 'update' call as
no-view and/or no-subscription re-call needed. Like instead of returning
`( model, Cmd.none )` we could do `( model, Cmd.cancelView )` or `( model,
Cmd.cancelSubscription )` or both via `Cmd.batch`, or perhaps as a
three-argument tuple that defaults to `( model, Cmd.none, StateChanges.none
)` or so, which would default to calling everything. I have a lot of
update messages that are handled that do not change the view and/or
subscriptions and it would be nice to prevent calling those somehow. Hmm,
actually a backward compatible change would be to add a new Program
callback, in addition to the usual `init`, `update`, `subscriptions`, and
`view`, add another one that I will call just `blah` for now, optional in
the Program or so, but have it be like:
```
blah : Msg -> ( Msg, States )
blah msg ->
case msg of
MyMessageThree _ -> ( msg, States.batch [ States.cancelView,
States.cancelSubscriptions )
MyMessageNine arg0 _ -> ( MyMessageThree arg0, States.noChanges ) -- or
`States.none`?
MyRedrawMessage -> ( msg, States.cancelView )
MyMessageTwelve _ -> ( msg, States.onlyUpdate )
_ -> ( msg, States.noChanges )
```
And it would be called after `init` and before every call of `update`. It
would allow an easy way to translate one message type into another (maybe
even a way to decorate it more so it can get passed to another modules
update/subscription, this would be a **great** hooking location for
modules), as well as a way to specify what states should be updated for a
given message, a default no-op function could be supplied via
`States.defaultHandle` or so that you could pass to the program so people
do not have to override it in the easy cases.
Hmm, an idea for a module handler, one of the states could be something
like:
```elm
MaterialMessage msg -> ( msg, States.delegate { update=Material.update,
subscriptions=Material.subscriptions } )
```
Which of course the other module could simplify via a helper to:
```elm
MaterialMessage msg -> Material.blah msg
```
On Monday, August 8, 2016 at 6:49:22 AM UTC-6, Janis Voigtländer wrote:
>
> A while back there was a thread
> <https://groups.google.com/d/msg/elm-discuss/u-6aCwaJezo/fu-HMPy6CQAJ>
> about filtering subscriptions. The following is related, but can also (and
> probably better) be consumed and discussed independently. For those that do
> have that older thread as context in mind, the following differs in two
> essential ways:
>
> - Earlier, the discussion was about generic filtering of arbitrary
> subscriptions. The following involves no genericity whatsoever. It is only
> a proposal about the Keyboard API specifically.
> - The earlier thread was not rooted in practice, since very little
> stuff had been built yet with subscriptions. In the following, I point to
> how things have played out in practice, based on uses students have made
> of
> the current API in projects.
>
> ------------------------------
>
> So, on to the subject matter:
>
> The keyboard package
> <http://package.elm-lang.org/packages/elm-lang/keyboard> currently
> contains functions such as:
>
> Keyboard.downs : (KeyCode -> msg) -> Sub msg
>
> Common uses (I’ll point to several repositories below) are such that only
> some keys are relevant for an application. My proposal is to have functions
> such as:
>
> Keyboard.downsSelectively : (KeyCode -> Maybe msg) -> Sub msg
>
> where the semantics is that if a given KeyCode is mapped to Nothing by
> the tagger, then no message gets sent along the subscription; otherwise the
> Just is peeled off and the message gets sent.
> ------------------------------
>
> Let’s look at a practical case, https://github.com/arpad-m/dontfall. It’s
> a game, where the player uses the keyboard for part of the control.
> Important excerpts from the code are:
>
> The message type (in
> https://github.com/arpad-m/dontfall/blob/master/src/BaseStuff.elm):
>
> type GameMsg = NothingHappened | ... several other messages ...
>
> The subscriptions definition (in
> https://github.com/arpad-m/dontfall/blob/master/src/main.elm):
>
> subscriptions : GameData -> Sub GameMsgsubscriptions d =
> Sub.batch
> ([ Keyboard.downs (\c -> if Char.fromCode c == 'P' then PauseToogle
> else NothingHappened) ] ++
> if d.state == Running then
> [ AnimationFrame.diffs Tick
> , Keyboard.downs (\c -> if Char.fromCode c == ' ' then
> JumpDown else NothingHappened)
> , Keyboard.ups (\c -> if Char.fromCode c == ' ' then JumpUp
> else NothingHappened)
> ]
> else
> [])
>
> The main case distinction in the main update function (in
> https://github.com/arpad-m/dontfall/blob/master/src/main.elm):
>
> updateScene : GameMsg -> GameData -> (GameData, Cmd GameMsg)updateScene msg d
> =
> (case d.state of
> ...
> Running -> case msg of
> MouseMove (x,_) -> { d | characterPosX = min x d.flWidth}
> Tick t -> stepTime d t
> PauseToogle -> { d | state = Paused }
> JumpDown -> { d | jumpPressed = True }
> JumpUp -> { d | jumpPressed = False }
> _ -> d
> , Cmd.none
> )
>
> Given availability of the functions I propose above, the code could
> instead look as follows:
>
> type GameMsg = ... only the other messages, no NothingHappened ...
> subscriptions : GameData -> Sub GameMsgsubscriptions d =
> Sub.batch
> ([ Keyboard.downsSelectively (\c -> if Char.fromCode c == 'P' then
> Just PauseToogle else Nothing) ] ++
> if d.state == Running then
> [ AnimationFrame.diffs Tick
> , Keyboard.downsSelectively (\c -> if Char.fromCode c == ' '
> then Just JumpDown else Nothing)
> , Keyboard.upsSelectively (\c -> if Char.fromCode c == ' '
> then Just JumpUp else Nothing)
> ]
> else
> [])
> updateScene : GameMsg -> GameData -> (GameData, Cmd GameMsg)updateScene msg d
> =
> (case d.state of
> ...
> Running -> case msg of
> MouseMove (x,_) -> { d | characterPosX = min x d.flWidth}
> Tick t -> stepTime d t
> PauseToogle -> { d | state = Paused }
> JumpDown -> { d | jumpPressed = True }
> JumpUp -> { d | jumpPressed = False }
> , Cmd.none
> )
>
> Advantages:
>
> 1.
>
> simpler message type, no special role no-op constructor needed
> 2.
>
> no spurious update and render cycles while the game is running
> 3.
>
> less room for bugs in the update logic
>
> Some additional comments on the latter two of these points:
>
> Re 2., given the current implementation, whenever a key is hit that is not
> relevant, the update function is still called and produces an unchanged
> model, which is then rendered, which is extra/useless work. Since the game
> uses Graphics.*, no use can be made of Html.Lazy.* to avoid the
> re-rendering. Even if something like Graphics.Lazy.* were available,
> having to use it would not be as nice/pure as not causing those spurious
> updates in the first place.
>
> Re 3., given the current implementation, there is both more room for bugs
> in the now and in a potential later, when extending the game. In the now,
> the programmer has to make sure that NothingHappened does indeed not
> change the model. Concerning later, imagine that the programmer extends the
> message type for some reason. With the current version of updateScene,
> the programmer might forget to actually add a branch for handling the new
> message, and the compiler would not catch that, because of the _ -> d
> branch that will silently catch not only NothingHappened but also the new
> message which was actually supposed to make something happen. With the
> version of updateScene after the proposed change, the situation would be
> different. Since there is no _ -> d branch in that Running -> case msg of
> ... part anymore (thanks to NothingHappened not being a thing), the
> compiler will immediately complain if the message type is extended but the
> new message is not handled there. Bug prevented.
> ------------------------------
>
> It’s not only this single project. I have observed students applying
> different strategies to deal with “Not all keys are relevant to my
> program”. In each case, using an API with functions of type (KeyCode ->
> Maybe msg) -> Sub msg instead of (KeyCode -> msg) -> Sub msg would have
> been conceptually nicer and would have simplified things.
>
> Some more example repos:
>
> - https://github.com/chemmi/elm-rocket, uses type Key = Left | Right |
> ... | NotBound and keyBinding : KeyCode -> Key and then needs to make
> sure to correctly (non)-deal with NotBound in functions like
> updateKeyDown; whereas just not having NotBound, but having keyBinding
> : KeyCode -> Maybe Key and using that in a call to a (KeyCode -> Maybe
> msg) -> Sub msg function would simplify things with the same benefits
> as in the above more fully elaborated example case.
> - https://github.com/Dinendal92/Abschlussprojekt-DP2016, less complete
> project, but with same approach and issues as in the preceding example,
> using type Key = Space | Unknown and fromCode : Int -> Key. Here,
> since eliminating Unknown would turn Key into a type with only one
> constructor, even more conceptual simplifications would be enabled after a
> switch to the (KeyCode -> Maybe msg) -> Sub msg approach.
> - https://github.com/Shaomada/Elm-Project, quite elaborate project,
> structured according to TEA, uses no special Key type, instead maps
> with Char.fromCode in the calls to the keyboard subscriptions, then
> has to case dispatch on actual Chars at several places distributed
> over the update functions of the TEA subcomponents. Subscribing with
> (KeyCode
> -> Maybe msg) -> Sub msg functions should allow to eliminate branches
> at some of those places, removing redundancies and room for bugs.
> - https://github.com/Sulring/elmaction, similar story (without TEA)
>
>
>
--
You received this message because you are subscribed to the Google Groups "Elm
Discuss" group.
To unsubscribe from this group and stop receiving emails from it, send an email
to [email protected].
For more options, visit https://groups.google.com/d/optout.