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.
