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.

Reply via email to