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.

Reply via email to