Sorry, I always try to look for more generic solutions for specific 
problems.  :-)


On Monday, August 8, 2016 at 9:05:18 AM UTC-6, Janis Voigtländer wrote:
>
> Please don’t derail this thread. As its title says, and as my preface to 
> the opening post says, it is specifically about the Keyboard API.
>
> If you want to pursue a completely different direction with a more general 
> API for generic “skipping updates” stuff, please continue the other thread 
> <https://groups.google.com/d/msg/elm-discuss/u-6aCwaJezo/fu-HMPy6CQAJ> I 
> pointed to, or start a new thread.
>
> If there are alternatives *with currently available functions* to my 
> proposal, very interested to hear it in this thread. Also interested in 
> hearing suggestions for other changes to the Keyboard API that would 
> address my points in an alternative way. But suggestions that are dependent 
> on stuff that does not exist and that is not keyboard specific are 
> derailing.
>
> 2016-08-08 16:48 GMT+02:00 OvermindDL1 <[email protected] <javascript:>>:
>
> 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] <javascript:>.
>> For more options, visit https://groups.google.com/d/optout.
>>
> ​
>

-- 
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.

Reply via email to