Generally, Nick is right, I primarily want to hear feedback about the specific API proposal I made.
However, I wrote the other day that "If there are alternatives *with currently available functions* to my proposal, very interested to hear it in this thread." So since Overmind seems to since have released some package/functions that are now available, it could be on-topic to see how/whether these do address points I try to address with the API proposal. Hence my questions (or "challenge" if you wish) in the previous message. 2016-08-10 18:13 GMT+02:00 Nick H <[email protected]>: > That is not what this discussion is about. Janis proposed a change to the > Keyboard API. The reason he started this thread was to get our thoughts on > his proposal. > > On Wed, Aug 10, 2016 at 8:37 AM, OvermindDL1 <[email protected]> > wrote: > >> How is it off-topic considering its whole purpose is to handle this exact >> kind of issue? The discussion is about filtering messages, potentially >> translating them to a a better message that is for a specific purpose >> without needing to worry about checking for valid input in update area. It >> is handled generically. >> >> >> On Wednesday, August 10, 2016 at 8:44:12 AM UTC-6, Nick H wrote: >>> >>> Overmind, we can all see the thread that you started. It is still >>> off-topic for you to bring it up here. >>> >>> On Wed, Aug 10, 2016 at 7:40 AM, OvermindDL1 <[email protected]> wrote: >>> >>>> Well for what it is worth I made a library that can filter messages >>>> before they are handled, so you could, for example, turn a keypress of 'w' >>>> into a message of 'GoForward' and 's' into 'GoBackward' or whatever. And >>>> if you cancel a message then it becomes entirely unhandled and you will >>>> never even see it appear in the event loop (update/subscription/view). If >>>> your convert a message then you never see the original, only your converted >>>> one. I am curious if anyone wants to test it with these issues as I think >>>> it would work very well as a, for example, key remapper. It is a >>>> nice-central place to put key mapping (since it has a model you could even >>>> setup a dynamic key mapper based on user settings with ease). Cleans up >>>> code in update as since you can expect to only receive valid messages if >>>> you filter them here first, vastly cleans up code there to only the actual >>>> work. The package is up at the elm package website (ProgramEx I think I >>>> named it). Still very in testing but it seems to be working utterly >>>> perfect in one of my big apps and has already cleaned up a lot of code. >>>> Here is how the filter code looks (this one 'delegates' to two different >>>> TEA-form libraries their messages and alters a Scroll event just like you'd >>>> expect to do with key remapping): >>>> >>>> ```elm >>>> >>>> filters : Msg -> Model -> ( Msg, States Model Msg )filters msg model = >>>> let >>>> log = >>>> debug_log (model.programFlags.debug_log |> Maybe.withDefault >>>> False) "filters" ( msg, model ) >>>> in >>>> case msg of >>>> Helpers helpersMsg -> >>>> ( msg >>>> , States.enableAll |> States.delegate (helpers_delegate >>>> Helpers helpersMsg) >>>> ) >>>> >>>> Mdl mdlMsg -> >>>> ( msg >>>> , States.enableAll >>>> |> States.delegate >>>> { key = "Mdl" >>>> , update = Just (\_ o -> Material.update mdlMsg o) >>>> , subscriptions = Just (\o -> >>>> Material.subscriptions Mdl o) >>>> } >>>> ) >>>> >>>> >>>> MesgList_Scroll scrolled -> >>>> case model.loc of >>>> RoomLocation rid _ -> >>>> let >>>> doLoad : Bool >>>> doLoad = >>>> (model.firstMessageReached == False) >>>> && (scrolled.pos < 16) >>>> && (model.isLoadingOlder == False) >>>> >>>> && ((Dict.size model.active_room_msgs) >>>> >= 10) >>>> >>>> in >>>> if doLoad then >>>> ( MesgList_LoadOlder rid, States.enableAll >>>> ) >>>> else >>>> ( msg, States.disableAll ) >>>> >>>> _ -> >>>> ( msg, States.disableAll ) >>>> >>>> >>>> _ -> >>>> ( msg, States.enableAll ) >>>> >>>> ``` >>>> >>>> On Tuesday, August 9, 2016 at 10:55:43 PM UTC-6, Nick H wrote: >>>>> >>>>> This would be a really nice improvement. I doubt there is anybody >>>>> using the keyboard API who wants to capture every key press. (But would >>>>> love to see a counter-example!) Typing is handled by the HTML library >>>>> >>>>> On Mon, Aug 8, 2016 at 11:01 PM, Janis Voigtländer < >>>>> [email protected]> wrote: >>>>> >>>>>> To up the proposal, here a revision. >>>>>> >>>>>> Instead of proposing to *add* new functions, I now propose to >>>>>> *replace* >>>>>> >>>>>> Keyboard.presses : (KeyCode -> msg) -> Sub msgKeyboard.downs : (KeyCode >>>>>> -> msg) -> Sub msgKeyboard.ups : (KeyCode -> msg) -> Sub msg >>>>>> >>>>>> by >>>>>> >>>>>> Keyboard.presses : (KeyCode -> Maybe msg) -> Sub msgKeyboard.downs : >>>>>> (KeyCode -> Maybe msg) -> Sub msgKeyboard.ups : (KeyCode -> Maybe msg) >>>>>> -> Sub msg >>>>>> >>>>>> It would still be easy to recover the old behavior if wanted in a >>>>>> specific situation, by throwing in Just. But actually I posit that >>>>>> one almost never really wants to capture all keys. Usually, one wants to >>>>>> be >>>>>> selective. That selectiveness has to be expressed somewhere, and doing it >>>>>> with the Maybe type that is designed for such purpose is generally >>>>>> better than going for something like extra NoOp or NothingHappened >>>>>> or NotBound constructors that then need to be handled in a special >>>>>> way in the app’s update logic, without help from the type system. Making >>>>>> consideration of the selectiveness part of the modelling up front would >>>>>> lead to better design. That’s at least the case in the >>>>>> projects/repositories I have pointed to. >>>>>> >>>>>> >>>>>> 2016-08-08 14:48 GMT+02:00 Janis Voigtländer <[email protected]> >>>>>> : >>>>>> >>>>>>> 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/don >>>>>>> tfall/blob/master/src/BaseStuff.elm): >>>>>>> >>>>>>> type GameMsg = NothingHappened | ... several other messages ... >>>>>>> >>>>>>> The subscriptions definition (in https://github.com/arpad-m/don >>>>>>> tfall/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. >>>>>> >>>>> >>>>> -- >>>> 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. >>>> >>> >>> -- >> 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. >> > > -- > 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. > -- 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.
