What will the GameMsg type look like?
​

2016-08-10 18:59 GMT+02:00 OvermindDL1 <[email protected]>:

> > 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 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
>                 [])
>
> ```
>
> 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 GameMsgsubscriptions 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.
>
>
> On Wednesday, August 10, 2016 at 10:27:42 AM UTC-6, Janis Voigtländer
> wrote:
>>
>> 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/don
>>>>>>>>> tfall. 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.
>

-- 
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