about the Keyboard API

364 views
Skip to first unread message

Janis Voigtländer

unread,
Aug 8, 2016, 8:49:22 AM8/8/16
to elm-d...@googlegroups.com

A while back there was a thread 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 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 GameMsg
subscriptions 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 GameMsg
subscriptions 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)

OvermindDL1

unread,
Aug 8, 2016, 10:48:26 AM8/8/16
to Elm Discuss
I do say that I wish there were some way to make some 'update' call as no-view and/or no-subscription re-call needed.  Like instead of returning `( model, Cmd.none )` we could do `( model, Cmd.cancelView )` or `( model, Cmd.cancelSubscription )` or both via `Cmd.batch`, or perhaps as a three-argument tuple that defaults to `( model, Cmd.none, StateChanges.none )` or so, which would default to calling everything.  I have a lot of update messages that are handled that do not change the view and/or subscriptions and it would be nice to prevent calling those somehow.  Hmm, actually a backward compatible change would be to add a new Program callback, in addition to the usual `init`, `update`, `subscriptions`, and `view`, add another one that I will call just `blah` for now, optional in the Program or so, but have it be like:
```
blah : Msg -> ( Msg, States )
blah msg ->
  case msg of
    MyMessageThree _ -> ( msg, States.batch [ States.cancelView, States.cancelSubscriptions )
    MyMessageNine arg0 _ -> ( MyMessageThree arg0, States.noChanges ) -- or `States.none`?
    MyRedrawMessage -> ( msg, States.cancelView )
    MyMessageTwelve _ -> ( msg, States.onlyUpdate )
    _ -> ( msg, States.noChanges )
```

And it would be called after `init` and before every call of `update`.  It would allow an easy way to translate one message type into another (maybe even a way to decorate it more so it can get passed to another modules update/subscription, this would be a **great** hooking location for modules), as well as a way to specify what states should be updated for a given message, a default no-op function could be supplied via `States.defaultHandle` or so that you could pass to the program so people do not have to override it in the easy cases.

Hmm, an idea for a module handler, one of the states could be something like:
```elm
    MaterialMessage msg -> ( msg, States.delegate { update=Material.update, subscriptions=Material.subscriptions } )
```
Which of course the other module could simplify via a helper to:
```elm
    MaterialMessage msg -> Material.blah msg
```

OvermindDL1

unread,
Aug 8, 2016, 11:01:54 AM8/8/16
to Elm Discuss
Yeah looking at https://github.com/elm-lang/core/blob/4.0.4/src/Native/Platform.js shows that it would be very easy to add that functionality to core.
<div title="MDH:PGRpdj48ZGl2PjxkaXY+PGRpdj48ZGl2PkEgd2hpbGUgYmFjayB0aGVyZSB3YXMgYSBbdGhyZWFk XShodHRwczovL2dyb3Vwcy5nb29nbGUuY29tL2QvbXNnL2VsbS1kaXNjdXNzL3UtNmFDd2FKZXpv L2Z1LUhNUHk2Q1FBSikgYWJvdXQgZmlsdGVyaW5nIHN1YnNjcmlwdGlvbnMuIFRoZSBmb2xsb3dp bmcgaXMgcmVsYXRlZCwgYnV0IGNhbiBhbHNvIChhbmQgcHJvYmFibHkgYmV0dGVyKSBiZSBjb25z dW1lZCBhbmQgZGlzY3Vzc2VkIGluZGVwZW5kZW50bHkuIEZvciB0aG9zZSB0aGF0IGRvIGhhdmUg dGhhdCBvbGRlciB0aHJlYWQgYXMgY29udGV4dCBpbiBtaW5kLCB0aGUgZm9sbG93aW5nIGRpZmZl cnMgaW4gdHdvIGVzc2VudGlhbCB3YXlzOjxicj48YnI+PC9kaXY+KiBFYXJsaWVyLCB0aGUgZGlz Y3Vzc2lvbiB3YXMgYWJvdXQgZ2VuZXJpYyBmaWx0ZXJpbmcgb2YgYXJiaXRyYXJ5IHN1YnNjcmlw dGlvbnMuIFRoZSBmb2xsb3dpbmcgaW52b2x2ZXMgbm8gZ2VuZXJpY2l0eSB3aGF0c29ldmVyLiBJ dCBpcyBvbmx5IGEgcHJvcG9zYWwgYWJvdXQgdGhlIEtleWJvYXJkIEFQSSBzcGVjaWZpY2FsbHku PGJyPjwvZGl2PiogVGhlIGVhcmxpZXIgdGhyZWFkIHdhcyBub3Qgcm9vdGVkIGluIHByYWN0aWNl LCBzaW5jZSB2ZXJ5IGxpdHRsZSBzdHVmZiBoYWQgYmVlbiBidWlsdCB5ZXQgd2l0aCBzdWJzY3Jp cHRpb25zLiBJbiB0aGUgZm9sbG93aW5nLCBJIHBvaW50IHRvIGhvdyB0aGluZ3MgaGF2ZSBwbGF5 ZWQgb3V0IGluIHByYWN0aWNlLCBiYXNlZCBvbiB1c2VzIHN0dWRlbnRzIGhhdmUgbWFkZSBvZiB0 aGUgY3VycmVudCBBUEkgaW4gcHJvamVjdHMuPGJyPjxicj4tLS0tLS0tLS0tLTxicj48YnI+PC9k aXY+U28sIG9uIHRvIHRoZSBzdWJqZWN0IG1hdHRlcjo8YnI+PGJyPjwvZGl2PlRoZSBba2V5Ym9h cmQgcGFja2FnZV0oaHR0cDovL3BhY2thZ2UuZWxtLWxhbmcub3JnL3BhY2thZ2VzL2VsbS1sYW5n L2tleWJvYXJkKSBjdXJyZW50bHkgY29udGFpbnMgZnVuY3Rpb25zIHN1Y2ggYXM6PGJyPjwvZGl2 PmBgYGhhc2tlbGw8YnI+S2V5Ym9hcmQuZG93bnMgOiAoS2V5Q29kZSAtJmd0OyBtc2cpIC0mZ3Q7 IFN1YiBtc2c8YnI+PGRpdj5gYGA8YnI+PC9kaXY+PGRpdj5Db21tb24gdXNlcyAoSSdsbCBwb2lu dCB0byBzZXZlcmFsIHJlcG9zaXRvcmllcyBiZWxvdykgYXJlIHN1Y2ggdGhhdCBvbmx5IHNvbWUg a2V5cyBhcmUgcmVsZXZhbnQgZm9yIGFuIGFwcGxpY2F0aW9uLiBNeSBwcm9wb3NhbCBpcyB0byBo YXZlIGZ1bmN0aW9ucyBzdWNoIGFzOjxicj48L2Rpdj48ZGl2PmBgYGhhc2tlbGw8YnI+S2V5Ym9h cmQuZG93bnNTZWxlY3RpdmVseSA6IChLZXlDb2RlIC0mZ3Q7IE1heWJlIG1zZykgLSZndDsgU3Vi IG1zZzxicj5gYGA8YnI+PC9kaXY+PGRpdj53aGVyZSB0aGUgc2VtYW50aWNzIGlzIHRoYXQgaWYg YSBnaXZlbiBgS2V5Q29kZWAgaXMgbWFwcGVkIHRvIGBOb3RoaW5nYCBieSB0aGUgdGFnZ2VyLCB0 aGVuIG5vIG1lc3NhZ2UgZ2V0cyBzZW50IGFsb25nIHRoZSBzdWJzY3JpcHRpb247IG90aGVyd2lz ZSB0aGUgYEp1c3RgIGlzIHBlZWxlZCBvZmYgYW5kIHRoZSBtZXNzYWdlIGdldHMgc2VudC48YnI+ PGJyPi0tLS0tLS0tLS0tPGJyPjxicj48L2Rpdj48ZGl2PkxldCdzIGxvb2sgYXQgYSBwcmFjdGlj YWwgY2FzZSwgaHR0cHM6Ly9naXRodWIuY29tL2FycGFkLW0vZG9udGZhbGwuIEl0J3MgYSBnYW1l LCB3aGVyZSB0aGUgcGxheWVyIHVzZXMgdGhlIGtleWJvYXJkIGZvciBwYXJ0IG9mIHRoZSBjb250 cm9sLiBJbXBvcnRhbnQgZXhjZXJwdHMgZnJvbSB0aGUgY29kZSBhcmU6PGJyPjxicj48L2Rpdj48 ZGl2PlRoZSBtZXNzYWdlIHR5cGUgKGluIGh0dHBzOi8vZ2l0aHViLmNvbS9hcnBhZC1tL2RvbnRm YWxsL2Jsb2IvbWFzdGVyL3NyYy9CYXNlU3R1ZmYuZWxtKTo8YnI+PC9kaXY+PGRpdj5gYGBoYXNr ZWxsPGJyPjwvZGl2PjxkaXY+dHlwZSA8c3BhbiBjbGFzcz0iIj5HYW1lTXNnPC9zcGFuPiA8c3Bh biBjbGFzcz0iIj49PC9zcGFuPiA8c3BhbiBjbGFzcz0iIj5Ob3RoaW5nSGFwcGVuZWQgfCAuLi4g c2V2ZXJhbCBvdGhlciBtZXNzYWdlcyAuLi48YnI+PC9zcGFuPjwvZGl2PjxkaXY+YGBgPGJyPjwv ZGl2PjxkaXY+VGhlIHN1YnNjcmlwdGlvbnMgZGVmaW5pdGlvbiAoaW4gaHR0cHM6Ly9naXRodWIu Y29tL2FycGFkLW0vZG9udGZhbGwvYmxvYi9tYXN0ZXIvc3JjL21haW4uZWxtKTo8YnI+PC9kaXY+ PGRpdj5gYGBoYXNrZWxsPGJyPnN1YnNjcmlwdGlvbnMgOiBHYW1lRGF0YSAtJmd0OyBTdWIgR2Ft ZU1zZzxicj5zdWJzY3JpcHRpb25zIGQgPTxicj4mbmJzcDsmbmJzcDsmbmJzcDsgU3ViLmJhdGNo PGJyPiZuYnNwOyZuYnNwOyZuYnNwOyZuYnNwOyZuYnNwOyZuYnNwOyZuYnNwOyAoWyBLZXlib2Fy ZC5kb3ducyAoXGMgLSZndDsgaWYgQ2hhci5mcm9tQ29kZSBjID09ICdQJyB0aGVuIFBhdXNlVG9v Z2xlIGVsc2UgTm90aGluZ0hhcHBlbmVkKSBdICsrPGJyPiZuYnNwOyZuYnNwOyZuYnNwOyZuYnNw OyZuYnNwOyZuYnNwOyZuYnNwOyZuYnNwOyZuYnNwOyZuYnNwOyZuYnNwOyBpZiBkLnN0YXRlID09 IFJ1bm5pbmcgdGhlbjxicj4mbmJzcDsmbmJzcDsmbmJzcDsmbmJzcDsmbmJzcDsmbmJzcDsmbmJz cDsmbmJzcDsmbmJzcDsmbmJzcDsmbmJzcDsmbmJzcDsmbmJzcDsmbmJzcDsmbmJzcDsgWyBBbmlt YXRpb25GcmFtZS5kaWZmcyBUaWNrPGJyPiZuYnNwOyZuYnNwOyZuYnNwOyZuYnNwOyZuYnNwOyZu YnNwOyZuYnNwOyZuYnNwOyZuYnNwOyZuYnNwOyZuYnNwOyZuYnNwOyZuYnNwOyZuYnNwOyZuYnNw OyAsIEtleWJvYXJkLmRvd25zIChcYyAtJmd0OyBpZiBDaGFyLmZyb21Db2RlIGMgPT0gJyAnIHRo ZW4gSnVtcERvd24gZWxzZSBOb3RoaW5nSGFwcGVuZWQpPGJyPiZuYnNwOyZuYnNwOyZuYnNwOyZu YnNwOyZuYnNwOyZuYnNwOyZuYnNwOyZuYnNwOyZuYnNwOyZuYnNwOyZuYnNwOyZuYnNwOyZuYnNw OyZuYnNwOyZuYnNwOyAsIEtleWJvYXJkLnVwcyAoXGMgLSZndDsgaWYgQ2hhci5mcm9tQ29kZSBj ID09ICcgJyB0aGVuIEp1bXBVcCBlbHNlIE5vdGhpbmdIYXBwZW5lZCk8YnI+Jm5ic3A7Jm5ic3A7 Jm5ic3A7Jm5ic3A7Jm5ic3A7Jm5ic3A7Jm5ic3A7Jm5ic3A7Jm5ic3A7Jm5ic3A7Jm5ic3A7Jm5i c3A7Jm5ic3A7Jm5ic3A7Jm5

Janis Voigtländer

unread,
Aug 8, 2016, 11:05:18 AM8/8/16
to elm-d...@googlegroups.com

Please don’t derail this thread. As its title says, and as my preface to the opening post says, it is specifically about the Keyboard API.

If you want to pursue a completely different direction with a more general API for generic “skipping updates” stuff, please continue the other thread I pointed to, or start a new thread.

If there are alternatives with currently available functions to my proposal, very interested to hear it in this thread. Also interested in hearing suggestions for other changes to the Keyboard API that would address my points in an alternative way. But suggestions that are dependent on stuff that does not exist and that is not keyboard specific are derailing.

--
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 elm-discuss+unsubscribe@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

OvermindDL1

unread,
Aug 8, 2016, 12:05:58 PM8/8/16
to Elm Discuss
Sorry, I always try to look for more generic solutions for specific problems.  :-)
To unsubscribe from this group and stop receiving emails from it, send an email to elm-discuss...@googlegroups.com.

Daniel Bachler

unread,
Aug 8, 2016, 6:14:52 PM8/8/16
to Elm Discuss
For what it's worth I like the proposal. Am I correct in thinking that this is something that would benefit everyone who wants to handle keyboard shortcuts in Elm?

Janis Voigtländer

unread,
Aug 9, 2016, 1:51:29 AM8/9/16
to elm-d...@googlegroups.com

Am I correct in thinking that this is something that would benefit everyone who wants to handle keyboard shortcuts in Elm?

If the scenario is:

  • You already have complete message and update logic.
  • You want to capture a selected few key inputs and map them to already implemented actions in the app.

then yes, that would be nicer to do with the proposed new functions rather than the existing ones, since you would not need to suddenly add a NoOp message constructor and provide (trivial) update logic for it, and possibly fix a number of other places in the program where case distinction on message constructors happens. Instead, you simply map the wanted keys to Just the relevant message constructors you already have, and all others to Nothing, and don’t need to touch any other part of your program.


--
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 elm-discuss+unsubscribe@googlegroups.com.

Janis Voigtländer

unread,
Aug 9, 2016, 2:01:48 AM8/9/16
to elm-d...@googlegroups.com

To up the proposal, here a revision.

Instead of proposing to add new functions, I now propose to replace

Keyboard.presses : (KeyCode -> msg) -> Sub msg
Keyboard.downs : (KeyCode -> msg) -> Sub msg
Keyboard.ups : (KeyCode -> msg) -> Sub msg

by

Keyboard.presses : (KeyCode -> Maybe msg) -> Sub msg
Keyboard.downs : (KeyCode -> Maybe msg) -> Sub msg
Keyboard.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.

Ian Mackenzie

unread,
Aug 9, 2016, 6:47:41 PM8/9/16
to Elm Discuss
I really like this version. It's effectively saying that in the same way that most subscriptions/HTML events take map function arguments since you'd otherwise almost always want to map the result yourself (using Html.App.map or Sub.map), the keyboard functions should take a filter-map function argument since you almost always want to filter and map the result. (With the slight wrinkle that Sub.filterMap doesn't actually exist, but that's getting back into the general subscription-filtering discussion...)

Nick H

unread,
Aug 10, 2016, 12:55:43 AM8/10/16
to elm-d...@googlegroups.com
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

OvermindDL1

unread,
Aug 10, 2016, 10:40:26 AM8/10/16
to Elm Discuss
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 )
```
To unsubscribe from this group and stop receiving emails from it, send an email to elm-discuss...@googlegroups.com.

Nick H

unread,
Aug 10, 2016, 10:44:12 AM8/10/16
to elm-d...@googlegroups.com
Overmind, we can all see the thread that you started. It is still off-topic for you to bring it up here.

To unsubscribe from this group and stop receiving emails from it, send an email to elm-discuss+unsubscribe@googlegroups.com.

OvermindDL1

unread,
Aug 10, 2016, 11:37:32 AM8/10/16
to Elm Discuss
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.

Nick H

unread,
Aug 10, 2016, 12:13:54 PM8/10/16
to elm-d...@googlegroups.com
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.

To unsubscribe from this group and stop receiving emails from it, send an email to elm-discuss+unsubscribe@googlegroups.com.

Janis Voigtländer

unread,
Aug 10, 2016, 12:20:33 PM8/10/16
to elm-d...@googlegroups.com

My opening post discussed the API proposal here in the context of several pieces of very concrete code using Keyboard (written as student projects not for the purpose of showing a particular point about Elm, but to simply get stuff done).

Please take one of them, preferably the one I elaborated on at some length in that message, https://github.com/arpad-m/dontfall. In the message I showed which code in that project would be affected by the proposed API change, and how the new code would look. Can you show how your proposal would affect that project? Which functions would be changed in what way? Then we could see whether your proposal addresses the points that I try to address (and mentioned as such) with my proposal.


To unsubscribe from this group and stop receiving emails from it, send an email to elm-discuss+unsubscribe@googlegroups.com.

Janis Voigtländer

unread,
Aug 10, 2016, 12:27:42 PM8/10/16
to elm-d...@googlegroups.com
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.

OvermindDL1

unread,
Aug 10, 2016, 12:59:40 PM8/10/16
to Elm Discuss
> If there are alternatives with currently available functions to my proposal, very interested to hear it in this thread.

That is why I looked at more options and started thinking about how it could also solve other filtering issues that I have, so a singular thing that could solve it all would be nice.  :-)


Given code from the dontfall game, the current top-post suggestion was to change subscriptions to be:
```elm
subscriptions : GameData -> Sub GameMsg
subscriptions 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
                [])
```

In the thing I proposed that can handle generic message filtering (not just subscription) would turn the above back into a fairly normal and readable style of:
```elm
subscriptions : GameData -> Sub GameMsg
subscriptions d =
    Sub.
batch
      [ Keyboard.downs HandleKeyboardDown
      , Keyboard.ups HandleKeyboardUps
      , if d.state == Running then AnimationFrame.diffs Tick else Sub.none
      ]
```
And a new callback (added in the main where init/update/etc are, in my package is called 'filters') would be something like this, this is a very simple example and `filters` can do a lot more, but for simple keyboard filtering:
```elm
filters : Msg -> Model -> ( Msg, States Model Msg )
filters msg model =
  if model.state == Running then
    case msg of
        HandleKeyboardDown c ->
            case Char.fromCode c ->
                'P' -> ( PauseToggle, States.enableAll )
                ' ' -> ( JumpDown, States.enableAll )
                _ -> ( msg, States.disableAll )
        HandleKeyboardUp c ->
            case Char.fromCode c ->
                ' ' -> ( JumpUp, States.enableAll )
                _ -> ( msg, States.disableAll )
        _ -> ( msg, States.enableAll )
  else
      ( msg, States.enableAll )
```
You could also delegate to other TEA-style modules.  You could filter out the Tick here instead of subscribing and unsubscribing (I like a simple 'subscriptions' callback).  Anything with `States.disableAll` will disable all callbacks for that msg, so update will not be called, subscriptions will not be called, and view will not be called (it returns the cached values from last time so an exact match test of === in javascript shows they are identical and thus Elm will do nothing).

With the above `filters` you will not ever get HandleKeyboardDown/HandleKeyboardUp in your `update` at all, ever, it is either translated to another message or entirely canceled.  Adding more message conversions is as simple as adding another case, so you could add, say a shooting state with like:
```elm
filters : Msg -> Model -> ( Msg, States Model Msg )
filters msg model =
  if model.state == Running then
    case msg of
        HandleKeyboardDown c ->
            case Char.fromCode c ->
                'P' -> ( PauseToggle, States.enableAll )
                ' ' -> ( JumpDown, States.enableAll )
                'z' -> ( StartShooting, States.enableAll )
                _ -> ( msg, States.disableAll )
        HandleKeyboardUp c ->
            case Char.fromCode c ->
                ' ' -> ( JumpUp, States.enableAll )
                'z' -> ( StopShooting, States.enableAll )
                _ -> ( msg, States.disableAll )
        _ -> ( msg, States.enableAll )
  else
      ( msg, States.enableAll )
```

Just as simple as adding a readable case to each the down and up to send the two StartShooting/StopShooting messages.  You could easily pull values from the model if the user can remap their keys and just do if's instead too.

But yes, the filtering of messages is done in one and only area, and if you filter everything well then your update does not even need to become a mess of nested case's for the various states and such but you know that you will only get a message when it is actually valid for your state, so you know that you will not get a, say, `JumpUp` message unless you are in the `Running` state, so you do not even need to check that.  Your operations become simple and direct.

OvermindDL1

unread,
Aug 10, 2016, 1:02:32 PM8/10/16
to Elm Discuss
For note, if you want to not get HandleKeyPress messages ever it should actually be:
```elm
filters : Msg -> Model -> ( Msg, States Model Msg )
filters msg model =
  if model.state == Running then
    case msg of
        HandleKeyboardDown c ->
            case Char.fromCode c ->
                'P' -> ( PauseToggle, States.enableAll )
                ' ' -> ( JumpDown, States.enableAll )
                'z' -> ( StartShooting, States.enableAll )
                _ -> ( msg, States.disableAll )
        HandleKeyboardUp c ->
            case Char.fromCode c ->
                ' ' -> ( JumpUp, States.enableAll )
                'z' -> ( StopShooting, States.enableAll )
                _ -> ( msg, States.disableAll )
        _ -> ( msg, States.enableAll )
  else
      case msg of
        HandleKeyboardUp _ -> ( msg, States.disableAll )
        HandleKeyboardDown _ -> ( msg, States.disableAll )
        _ -> ( msg, States.enableAll )
```

Though if you are never handling HandleKeyboardUp/Down in your update it does not matter anyway.
            case Char<span class="pl-k" style="box-sizing: border-box; text-shadow: none !important; box-shadow: none !important; color: rgb(133,

Janis Voigtländer

unread,
Aug 10, 2016, 1:22:47 PM8/10/16
to elm-d...@googlegroups.com

What will the GameMsg type look like?


To unsubscribe from this group and stop receiving emails from it, send an email to elm-discuss+unsubscribe@googlegroups.com.

OvermindDL1

unread,
Aug 10, 2016, 1:38:51 PM8/10/16
to Elm Discuss
It would need the HandleKeyboardDown/Up messages, however I've recently made a change to my Msg structure so I have something like this:
```elm
type alias MsgFiltered =
  { HandleKeyboardUp Char
  , HandleKeyboardDown Char
  }

type alias Msg
  = FilterOnly MsgFiltered
  | Other
  | Interesting
  | To
  | Update
  | Messages
  | Like
  | JumpDown
  | JumpUp
```
And it seems to be working well.  I just ignore the `FilterOnly` message entirely in my Update and can handle `FilterOnly` messages in the filter with ease (while still filtering others if I want).  It makes it easy to add messages that are only converted to others in the filter, then just subscribe to `(\c -> FilterOnly (TestFiltered c))` as normal.  That way you do not need to even have a case for them in your main update, just for the FilterOnly one, which you can just `( model, Cmd.none )` once.

Janis Voigtländer

unread,
Aug 10, 2016, 1:46:27 PM8/10/16
to elm-d...@googlegroups.com

Let me answer that myself, after looking some more at your proposal.

The GameMsg type would become more complicated than it was before, rather than becoming less complicated as was the case with my proposal/design.

Moreover, the update function would also be more complicated. You say that certain messages will never get there. But still the compiler will force you to do something about them, due to exhaustiveness checking. In my original post I argued that using the new API the update function would be simpler and more refactoring safe in case of future extensions. That advantage is lost with your approach (if you don’t agree with that assessment, please show how the update function would look, concretely).

Plus, the filters function adds conceptual complexity, whereas in my original post the goal was reduced conceptual complexity, and the changes I showed there on the concrete project demonstrate that such a reduction is indeed achieved.

So, your approach may have other uses, but as far as I can see it does not have the use I was after with the proposed API change, namely to simplify typical keyboard handling code as exemplified by those five different projects of different people.

Janis Voigtländer

unread,
Aug 10, 2016, 1:51:36 PM8/10/16
to elm-d...@googlegroups.com

Okay, saw this only after sending my previous message. Still, the FilterOnly message to be handled in update functions now is like a return of the NothingHappened message that I was so happy to have eliminated. Plus, the filters function’s added complexity. I still stand with “I’d rather have the Keyboard API changed in the way I proposed, than to have to use this more complicated machinery.”


To unsubscribe from this group and stop receiving emails from it, send an email to elm-discuss+unsubscribe@googlegroups.com.

OvermindDL1

unread,
Aug 10, 2016, 2:00:59 PM8/10/16
to Elm Discuss
Yeah I've been looking for potentially a way to have 2 message types altogether.  I am leaning toward just Msg having an UpdateMsg and a FilterMsg subtypes.  It would be easy to split on either then and even hoist them into their request function.

OvermindDL1

unread,
Aug 10, 2016, 2:08:11 PM8/10/16
to Elm Discuss

On Wednesday, August 10, 2016 at 11:51:36 AM UTC-6, Janis Voigtländer wrote:

Okay, saw this only after sending my previous message. Still, the FilterOnly message to be handled in update functions now is like a return of the NothingHappened message that I was so happy to have eliminated. Plus, the filters function’s added complexity. I still stand with “I’d rather have the Keyboard API changed in the way I proposed, than to have to use this more complicated machinery.”

Given this, how would that balloon the API for something like `onScroll` to only handle when it is in certain areas (like the top/bottom to load more things)?  Or balloon the API for listening for, say, a "@\b[^\b]*\b" in a string in a textfield so you only get a message when one exists?  Etc... etc...  The Keyboard API would get larger with such an API change and the same style would have to be copied into everything that could potentially be subscribed to else no filtering in those either except in update (which would still call subscriptions/view).  It just seems like a very slippery slope to me.  If such an API was added to Keyboard then someone could point to it as an example of why it should be added to on a Scroll message, or handling input, or etc...  `filters` may be a new callback but it gathers all of those needed things in to one place while keeping update/subscriptions highly readable, as well as filters being highly readable as it only handles one concept, filtering, instead of having spaghetti code all over the place to do this testing.  With `filters` my code has shrunk in size, a lot of 'if' and 'case's are just gone (not moved to filter, a lot are entirely *gone*).  It handles this case, and others without starting down the slippery slope of API ballooning.

Daniel Bachler

unread,
Aug 10, 2016, 2:20:46 PM8/10/16
to elm-d...@googlegroups.com
I like your general solution, OvermindDL1, but I don't think it is mutually exclusive with improving the keyboard api as Janis suggested. For general cases, your solution is great. But as several people already said, with Keyboard it is practically always the case that you want to filter, ergo it makes sense to "bake it into the api" of keyboard.

So from me +1 for Janis revised proposal for this specific case, but also a thumbs up to the general approach to filtering. OvermindDL1, do you want to write a post about this showing it to newcomers in more detail?



--
You received this message because you are subscribed to a topic in the Google Groups "Elm Discuss" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/elm-discuss/Ud7WzAhRsqE/unsubscribe.
To unsubscribe from this group and all its topics, send an email to elm-discuss+unsubscribe@googlegroups.com.

Janis Voigtländer

unread,
Aug 10, 2016, 2:21:45 PM8/10/16
to elm-d...@googlegroups.com

I don’t understand your concern. The Keyboard API would not get larger, because I have proposed to replace three old functions by three new functions.

The rest of your concerns build on that concern (ballooning), so seem immaterial to me at the moment.

Let’s judge the proposed change to the Keyboard API on its own merits, not on some feared implicit outreach to other APIs.

Other issues like onScroll etc. may have their own API problems. They are not directly affected by the change to the Keyboard API, though. Maybe you want to discuss those other API problems in separate threads, with concrete examples as well?

Generally, if you haven’t yet, you might want to go back and read the original “generic subscription filtering” thread I pointed to in the first post, in full. And I may add that since then I have been on the lookout for specific cases where subscription filtering is desired. I haven’t found enough cases to convince myself that a generic mechanism is called for. In fact, I found exactly the one, keyboard handling, that I am addressing here. I naturally have looked in only specific places, but let me say that I have had more than those five students coding Elm projects.


To unsubscribe from this group and stop receiving emails from it, send an email to elm-discuss+unsubscribe@googlegroups.com.

OvermindDL1

unread,
Aug 10, 2016, 2:46:04 PM8/10/16
to Elm Discuss
On Wednesday, August 10, 2016 at 12:20:46 PM UTC-6, Daniel Bachler wrote:
I like your general solution, OvermindDL1, but I don't think it is mutually exclusive with improving the keyboard api as Janis suggested. For general cases, your solution is great. But as several people already said, with Keyboard it is practically always the case that you want to filter, ergo it makes sense to "bake it into the api" of keyboard.
 
True yeah.  Actually for a keyboard a state-mapper would probably be best.  Being able to register keys to an action and being able to re-map them with ease.  Could make an easy set of functions for that.


On Wednesday, August 10, 2016 at 12:20:46 PM UTC-6, Daniel Bachler wrote: 
So from me +1 for Janis revised proposal for this specific case, but also a thumbs up to the general approach to filtering. OvermindDL1, do you want to write a post about this showing it to newcomers in more detail?




On Wednesday, August 10, 2016 at 12:21:45 PM UTC-6, Janis Voigtländer wrote:

I don’t understand your concern. The Keyboard API would not get larger, because I have proposed to replace three old functions by three new functions. 

Ah entirely replace them.  An action manager like mentioned above could make it very easy then, especially if it was a state-based action mapper as is common in many windowing systems.


On Wednesday, August 10, 2016 at 12:21:45 PM UTC-6, Janis Voigtländer wrote:

The rest of your concerns build on that concern (ballooning), so seem immaterial to me at the moment.

Just many experiences over many decades of seeing once good API's explode and become a mess, bit paranoid about it...  >.>

 
On Wednesday, August 10, 2016 at 12:21:45 PM UTC-6, Janis Voigtländer wrote:

Generally, if you haven’t yet, you might want to go back and read the original “generic subscription filtering” thread I pointed to in the first post, in full. And I may add that since then I have been on the lookout for specific cases where subscription filtering is desired. I haven’t found enough cases to convince myself that a generic mechanism is called for. In fact, I found exactly the one, keyboard handling, that I am addressing here. I naturally have looked in only specific places, but let me say that I have had more than those five students coding Elm projects.

Only one?  I've cleaned out a half-dozen messages from my big app and still have plenty more to work through as I notice them.  It has changed the code count by (checking git) total of -77 lines (everything is `elm-format`'d as well), which is fairly significant for only handling 6 message types so far.

But of the filtering I do so far, I have two TEA-modules hooked in (my own Helpers and elm-mdl), handle the Scroll event (I only load older messages if the scroll hits within 16 pixels of the top of the div), as well as 3 notification ones that I ignore if the relevant room is not visible at the moment (the primary path already registers others to the top notification listing).  I am really liking how it looks though, flattened 'update' significantly in some hairy sections.  :-)


Janis Voigtländer

unread,
Aug 10, 2016, 3:01:10 PM8/10/16
to elm-d...@googlegroups.com

Ah entirely replace them

Yes, that’s what I proposed in an earlier message in this thread.

An action manager like mentioned above could make it very easy then, especially if it was a state-based action mapper as is common in many windowing systems.

An action manager would be something else, and not needed for the cases I showed. Concerning extending the Keyboard API in a different direction (stateful), see the “conditions” set out by Evan in the README: https://github.com/elm-lang/keyboard/blob/master/README.md.

Just many experiences over many decades of seeing once good API’s explode and become a mess,…

You may find that what you experienced in other languages and frameworks about how APIs develop over time does not translate to how things happen in the Elm sphere, due to the way Evan manages things. In particular for something like the Keyboard API, which depends on native code and thus will continue to stay under his control.

Only one?

Yes, indeed. Note what I wrote there. It talks about filtering subscriptions. All the examples you mention do not seem to be about subscriptions, but instead about updates that come from other sources, probably from HTML events. Where (except keyboard handling) have you wanted to apply a filter function to a Sub-typed thing? (Leading us away from keyboard, so maybe this is more suitable to continue in the other thread about generic subscription filtering.)


OvermindDL1

unread,
Aug 10, 2016, 4:21:37 PM8/10/16
to Elm Discuss
On Wednesday, August 10, 2016 at 1:01:10 PM UTC-6, Janis Voigtländer wrote:

Only one?

Yes, indeed. Note what I wrote there. It talks about filtering subscriptions. All the examples you mention do not seem to be about subscriptions, but instead about updates that come from other sources, probably from HTML events. Where (except keyboard handling) have you wanted to apply a filter function to a Sub-typed thing? (Leading us away from keyboard, so maybe this is more suitable to continue in the other thread about generic subscription filtering.)

For actual subscription filters that I am using I have a lot of ports (integration with javascript code that I am not allowed to get rid of) and sometimes the port calls are useful and sometimes they are not.  My Helpers library has both a `msg_ignore` and a `cmd_ignore` that I use heavily for such purposes (which my Helper library swallows thankfully).  My subscriptions used to be fairly large and hairy because of all the checks, that is since gone and now replaced with:
```elm
subscriptions : Model -> Sub Msg
subscriptions model =
    Sub.batch
        [ port_MessageOptionCB (\{ text } -> MessageOptionCB text)
        , onInfoConnect (\{ msg } -> InfoConnect msg.uid)
        , onRoomListInit (\{ msg, cb_data } -> RoomList_Init cb_data msg.roomlist)
        , onRoomListJoined (\{ msg, cb_data } -> RoomList_Joined cb_data msg.roomlist)
        , onRoomListParted (\{ msg, cb_data } -> RoomList_Parted cb_data msg.roomlist)
        , onRoomListNotif (\{ msg, cb_data } -> RoomList_Notif cb_data msg.msgs)
        , onRoomClosed (\{ msg, cb_data } -> Room_Part cb_data msg)
        , onRoomInfo (\{ msg, cb_data } -> Room_Info cb_data msg)
        , onRoomMsgsInit (\{ msg, cb_data } -> RoomMsg_Init cb_data msg)
        , onRoomMsgsNew (\{ msg, cb_data } -> RoomMsg_New cb_data msg)
        , onRoomUserInit (\{ msg, cb_data } -> RoomUser_Init cb_data msg)
        , onRoomUserJoined (\{ msg, cb_data } -> RoomUser_Joined cb_data msg)
        , onRoomUserParted (\{ msg, cb_data } -> RoomUser_Parted cb_data msg)
        , onRoomNotif (\{ msg, cb_data } -> RoomMsg_Notif cb_data msg.msgs)
        , onRoomSyncState (\{ msg, cb_data } -> RoomMsg_SyncState cb_data msg)
        , onRoomSyncJoin (\{ msg, cb_data } -> RoomMsg_SyncJoin cb_data msg)
        , onRoomSyncLeave (\{ msg, cb_data } -> RoomMsg_SyncLeave cb_data msg)
        , {- snip a *lot* more -}
```

Although that call is in a module dedicated to ports and subscriptions so it is mostly out of sight, but when I had to touch it before it was quite a mess (and yes I should re-arrange the order of the cb_data, but meh, this way was more natural in the filter and update calls).

Also, since my last email, more than 100 more lines total are gone.  ^.^

Janis Voigtländer

unread,
Aug 10, 2016, 4:42:02 PM8/10/16
to elm-d...@googlegroups.com

For actual subscription filters that I am using I have a lot of ports

Yes, I can see that that would be an additional case. Ports have not been on my radar, since they were not relevant to the projects considered.

So Keyboard and ports.

For ports, that could motivate a similar change, where

port name : (Type -> msg) -> Sub msg

becomes

port name : (Type -> Maybe msg) -> Sub msg

But let’s not make this part of the current proposal.

In any case, that’s still only two cases (since all ports would be handled equally), nothing spreading to all APIs.

Also, since my last email, more than 100 more lines total are gone.

That’s nice. But let’s not confuse it with anything meaningful about the proposal at hand. You are obviously removing a lot of stuff. There’s no way really for us to know why that stuff was there and what other strategies might be applicable to remove it or to have prevented it from being there in the first place. If that’s to be discussed, it belongs in the other thread, about your “generic filtering of all kinds of events” proposal.

For the five concrete projects I pointed to, I say with confidence that the Keyboard API proposal I brought up here achieves something which that other proposal does not, in terms of simplifying the code.


Gábor Varga

unread,
Aug 10, 2016, 5:42:46 PM8/10/16
to Elm Discuss
I like the proposed changes to keyboard API, they would definitely make my life easier.

Also, that is the level where I want to do filtering in this case: as close to the event source as possible. Since the idea is that I am not interested in a bunch of events: I do not want them to be sent to my application at the first place.

OvermindDL1

unread,
Aug 10, 2016, 6:31:38 PM8/10/16
to Elm Discuss
Yeah they would be useful.  I went ahead and tried my hand at editing the code a bit to make it what I think is more readable, although the entire project gets formatted exceedingly differently if elm-format is run over it, so I did not and followed the existing style.  I'd not messed with a game in Elm yet, that was fun.  :-)

But yeah, just a little keypress state action mapper, nothing special, easy to read but a bit longer (could put most of it 'in' subscriptions instead of another function like I did, but eh, I also like explicit events to change states so PauseToggle was broken up from 3 purposes to 3 distinct events, multi-purpose events bug me... this did make the program longer though, but more readable and easier to follow in my opinion):  https://github.com/OvermindDL1/dontfall

Janis Voigtländer

unread,
Aug 10, 2016, 10:53:32 PM8/10/16
to elm-d...@googlegroups.com
You did change the behavior, though. Specifically, AnimationFrame.ticks is always running in your version, even when the game is paused. Similarly for some of the other subscriptions. As discussed in the generic subscription filtering thread, it's best to turn off subscriptions completely whenever possible. Of course, you could adapt your version of the subscriptions-function to bring the turning-off-of-subscriptions back. However, the result will be again more complex and less readable, I think. You will have the dealing with subscriptions distributed over different places in the code, while the original version and also the version improved by selective Keyboard functions has only one.
--

OvermindDL1

unread,
Aug 11, 2016, 10:51:11 AM8/11/16
to Elm Discuss
The ticks did change yes, I originally had it testing the state first but figured, eh, I was wanting to see what a keyboard action mapper would be (which could easily be fed into a Keyboard mapper, ignore the filters stuff).

I still find mapping the keyboard events 'inside' subscription to be very noisy.  And I still think think a single subscription for a keyboard filter would be best to prevent running and testing multiple.  Hmm, maybe something like:
```elm
  Keyboard.keyMapChar
    case d.state of
      Running ->
        [ ( 'P', KeyDown, PauseToggle )
        , ( ' ', KeyDown, JumpDown )
        , ( ' ', KeyUp,   JumpUp )
        ]
      _ ->
        [ ( 'P', KeyDown, PauseToggle )
        ]
```

I am one of those that hate inline conditionals and prefer to hoist it out, so yes I like the duplicated PauseToggle, makes it explicitly obvious that it is working in each state instead of noise like ++ and such making you have to think more.  However I think that is wonderfully readable and could easily register and unregister subscriptions as needed while making configurable key mapping much easier (since no need to convert to if/then repeating or dict lookups or so).

Or as a full example the above could be:
```elm
subscriptions : GameData -> Sub GameMsg
subscriptions =
  case d.state of
    Running ->
      Sub.batch
        [ AnimationFrame.diffs Tick
        , Keyboard.keyMapChar
          [ ( 'P', KeyDown, PauseToggle )
          , ( ' ', KeyDown, JumpDown )
          , ( ' ', KeyUp,   JumpUp )
          ]
        ]
    _ -> Keyboard.keyMapChar ( 'P', KeyDown, PauseToggle ) ]
```

Which I also think is far more readable (embedding state tests are always unreadable).

A `Keyboard.keyMapFn` could also make the first element of the tuple be a function that is passed in a keycode so it can do its own testing via returning True/False.  Or could just make it a `Keyboard.keyMap` where the first element is a subtype to become `( KeyMap.Char 'P', KeyDown, PauseToggle )` or `KeyMap.Fn` or whatever other options.  Would make it easier to expand later as well with more functionality if ever desired.

As always, lots of ways to go with various designs.

Janis Voigtländer

unread,
Aug 11, 2016, 11:58:28 AM8/11/16
to elm-d...@googlegroups.com

You throw in a lot of different ideas. I’m getting confused.

Concerning the approach with having both subscriptions and your new filters hook, I still think what I wrote in my last message (about the case if you were to make the same decisions as the original code about which subscriptions are running when, particularly concerning the animation frame ticking):

However, the result will be again more complex and less readable, I think. You will have the dealing with subscriptions distributed over different places in the code, while the original version and also the version improved by selective Keyboard functions has only one.

Your new message does not seem to try to rebut this. Instead, it proposes a completely different route? The Keyboard.keyMapChar and Keyboard.keyMapFn etc. things. This would be intended as completely replacing the current Keyboard API? As a more radical proposal than just changing the types of the current Keyboard.downs etc. functions by bringing Maybe into play?


--

OvermindDL1

unread,
Aug 11, 2016, 12:48:10 PM8/11/16
to Elm Discuss
Heh, yes I keep putting different ideas here, I am not focusing on a single idea, just working with the API's in testing a bit, figure out some new ideas, and document them here, upon which I then go out, make a mock-up of the ideas, and see how it works and document the results again.  Just a set of ideas for others to mull over as well to see if anything has merit.  And yep, the latest Keyboard.keyMap* could be a replacement of the existing API (or added to it).  From my mock testing it seems to be the easiest to use and most configurable thus far for this specific issue (since it is entirely just a simple-to-read-and-parse data structure without a host of mis-matched calls).
To unsubscribe from this group and stop receiving emails from it, send an email to elm-discuss...@googlegroups.com.

Janis Voigtländer

unread,
Aug 12, 2016, 1:09:31 AM8/12/16
to elm-d...@googlegroups.com

Okay, this keyMap* suggestion would need more scrutiny. Being a more radical departure from the current API, it is not obvious that all current Keyboard uses would still be well supported.

I will open an issue in the Keyboard repository with my more incremental API proposal.


To unsubscribe from this group and stop receiving emails from it, send an email to elm-discuss+unsubscribe@googlegroups.com.

OvermindDL1

unread,
Aug 12, 2016, 10:19:02 AM8/12/16
to Elm Discuss
On Thursday, August 11, 2016 at 11:09:31 PM UTC-6, Janis Voigtländer wrote:

Okay, this keyMap* suggestion would need more scrutiny. Being a more radical departure from the current API, it is not obvious that all current Keyboard uses would still be well supported.

They should be I'd think, could have more matchers like a `KeyMap.All` or others as well, could even make a regex matcher for example.
Reply all
Reply to author
Forward
0 new messages