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.

Reply via email to