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] > <javascript:>> 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/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. >>>> >>> >>> -- >> 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.
