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.
