On Monday, August 15, 2016 at 12:25:49 AM UTC-6, Aaron VonderHaar wrote:
>
> Can you give more details about an example of where doing nested record
> updates is useful? When I've run into nested records in real projects,
> usually it's a better choice to remove the nested update.
>
I tend to prefer nested records when they follow the view callpath. That
way I just have to copy, in most of my cases, their Location descriptor and
the specific view state. I then mutate the view states in the model. I
prefer to have the model match the view so things that are subparts of
something else make sense, else my main model would by 200+ keys large.
On Monday, August 15, 2016 at 12:25:49 AM UTC-6, Aaron VonderHaar wrote:
> Here are two ways to do that:
>
> 1) flatten the record
>
> This is appropriate when a single module is dealing with a bunch of things
> that don't actually need to be nested:
>
> Instead of this:
> type alias Course =
> { teacher : { id : Int, name : String } }
>
> Try this:
> type alias Course =
> { teacherId : Int
> , teacherName : String
> }
>
This would make my main model on the order **huge**! Even right now with
it following the view paths the model has 24 keys in the root model, if
flattened it would be well over 200. This is not a small project.
On Monday, August 15, 2016 at 12:25:49 AM UTC-6, Aaron VonderHaar wrote:
> 2) make the nested thing into a module
>
> This is appropriate when it makes sense to have functions that manipulate
> the nested thing.
>
> Instead of this:
> moveSpaceshipEast ship =
> let
> oldPosition = ship.position
> newPosition = { oldPosition | x = oldPosition.x + 1 }
> in
> { ship | position = newPosition }
>
> Try this:
> moveSpaceshipEast ship =
> { ship | position = Position.addX 1 ship.position }
>
> module Position
> addX dx position =
> { position | x = position.x + dx }
>
Not a game sadly, but I already do use a lot of modules to handle the
various components and so forth, and that is indeed what I do for most of
the sub-records, however they have a lot of cross-information
(notifications incoming have to be updated in the proper room record,
etc... etc...). This handles most of the load but everything is still
*very* verbose for a functional language.
On Monday, August 15, 2016 at 12:25:49 AM UTC-6, Aaron VonderHaar wrote:
> On Sun, Aug 14, 2016 at 11:28 AM, OvermindDL1 <[email protected]
> <javascript:>> wrote:
>
>>
>> Lists/Dicts/Records do have different interfaces, however if Elm had
>> HKT's it would not need to, you could have a generic 'map' function that
>> works over all of them, a generic fold, etc... and so forth (See Haskell).
>>
>> However note the List Access is what would define the required structure
>> of the thing that would be passed in, that is resolvable in the type system
>> quite easily (at least with HKT's, unsure otherwise).
>>
>> However, ignoring other types and just doing it for records you could do
>> it with the current Elm type system 'if' we get some kind of way to set a
>> record value via an argument, I.E. we could create all the proposed
>> getters/setters 'now' on just records if we get a function that acts
>> something like:
>> ```elm
>> Record.set : RecordID record id -> id_type -> { record | id : id_type }
>> Record.set id val rec = <something perhaps like `{ rec | id=val }`>
>> ```
>> That allows type-safe enforcement of a key ID.
>> Where you could use it like:
>> ```elm
>> model = { something=0 }
>> newModel = Record.set 42 model
>> newModel == { something=42 }
>> ```
>> Basically Elm needs something like Ocaml's Field(s) functions:
>> ```ocaml
>> Field.t # Type declaration for following inputs/outputs
>> Field.name # Get the name of a record field
>> Field.get # Get the value of a record field
>> Field.fset # Functionally set the value of a record field, returning the
>> new record with the new field value
>> ```
>> Which can be used like (remember Ocaml's type system is exceedingly
>> strict with no HKT support, it's type system is not as advanced as, say,
>> Haskell's):
>> ```ocaml
>> module Model = struct
>> type t =
>> { something : int
>> };;
>> end
>> module Model :
>> sig
>> type t =
>> { something : int;
>> }
>> end
>>
>> let model = { Model.
>> something = 42;
>> };;
>>
>> let name = Field.name Model.Fields.something model;; # Returns a string
>> of "something"
>>
>> let val = Field.get Model.Fields.something model;; # Returns an int of 42
>>
>> let newModel = Field.fset Model.Fields.something 3 model # Returns the
>> structure of { something = 3 }
>> ```
>>
>> So yes it is very possible in the normal Elm type system as Ocaml's is
>> similarly restricted. As I recall Ocaml is using the functional idiom
>> called Lenses in its field work to do that (lot of packaging access up in
>> functions, but does require a record to have more data, like adding an
>> implicit RecordName.Fields thing or so, or make better syntax for it).
>>
>> With proper HKT's, like with Haskell, then fully type-safe we could have
>> very powerful, generic, and simple syntax such as this:
>> ```haskell
>> -- Given these simplified types:
>> data Model = Model { something :: SomeSubData }
>>
>> data SomeSubData = SomeSubData { moreThing :: Integer }
>>
>> -- And given this value
>> where
>> model = Model (SomeSubData 42)
>>
>>
>> -- You could do the way that Elm does now (although elm has the existing
>> record inside the {} and haskell has it outside, so this is how haskell
>> does it):
>> newModel = model {
>> something = (something model) {
>> moreThing = moreThing (something model) + 1
>> }
>> }
>>
>>
>> -- The above set newModel as { something = { moreThing = 43 } } in Elmish
>> parlance, and is very similar to how it is done in Elm itself, however
>> Haskell has HKT's, which allow for a variety of other methods such as:
>>
>>
>> -- references+lenses (not possible to do anything even remotely like this
>> in Elm right now due to not being able to operate over types, but it is the
>> most succinct):
>> $(mkLabels [ 'Model, 'SomeSubData ]) -- Only need to do this once
>>
>> newModel = modify (+1) (moreThing . something) model
>>
>>
>> -- SYB (A generic library that handles tasks like this with is, it comes
>> bundled with Haskell
>> incMoreThing v s@(SomeSubData moreThing) = s { moreThing = moreThing + v
>> } -- Per change you want to make anywhere in the record path
>>
>> newModel = everywhere (mkT (incMoreThing 1))
>>
>>
>> -- Semantic Editor Combinators (low-level version of SYB, no libraries
>> needed, and you could make these 'now' in Elm, and I have to an extent, but
>> it is very easy in haskell to define it from scratch, as doing here, still
>> very wordy as it requires one function made per key):
>> type SemEdit a = a -> a
>> type Lifter p q = SemEdit p -> SemEdit q
>>
>> onSomething :: Lifter SomeSubData Model
>> onSomething f (Model something) = Model (f something)
>>
>> onMoreThing :: Lifter Integer SomeSubData
>> onMoreThing f (SomeSubData moreThing) = SomeSubData
>>
>> newModel = (onSomething . onMoreThing) (+1) model
>> ```
>>
>> So yes, it is very possible to perform such things while remaining
>> strongly typed. OCaml does it with an interface similar to my first
>> email. Haskell can do it with ease in an almost crazy amount of ways and
>> would be the better ways by far, however most of its ways require HKT's
>> and/or code that works on types. My original email proposal for just the
>> records is something that could be added to Elm with relative ease now
>> compared to enhancing the entire type system with HKT's.
>>
>> More responses in-line. :-)
>>
>>
>> On Sunday, August 14, 2016 at 10:54:15 AM UTC-6, Joey Eremondi wrote:
>>>
>>> Also, Lists, Dicts, and Records have different interfaces *because they
>>> are fundamentally different*
>>>
>>
>> Yes, that was an example and it would be most useful for nested records.
>> I was showing that example as a 'complete' method that could work over
>> everything as the API.
>>
>>
>> On Sunday, August 14, 2016 at 10:54:15 AM UTC-6, Joey Eremondi wrote:
>>
>>> If you are accessing the nth element of a List so often that you need
>>> special syntax to do so, you are doing something wrong, because list's are
>>> intended for iteration. You probably want an array.
>>>
>>
>> Normally you'd never ever do that, usually you would use Access.all for
>> lists, which does indeed iterate over all of them (even nested records
>> instead nested records instead a list that is inside a record would be
>> trivial via `[ :something, Access.all, :anotherkey, :yetanotherkey ]`),
>> just showing it for completion.
>>
>>
>> On Sunday, August 14, 2016 at 10:54:15 AM UTC-6, Joey Eremondi wrote:
>>
>>> Likewise, if you ever have a time where, at compile-time, you're trying
>>> to access a field in a record that might not exist, you're doing something
>>> terribly wrong, since that's not what records are for.
>>>
>>
>> Such code in typed languages would indeed create a compile-time error.
>> Things like variables that are only known at run-time are not allowed in
>> the keylist, only constants or sub-types that restrict the value of keys
>> allowed to the given record are allowed and is type-safe.
>>
>>
>> On Sunday, August 14, 2016 at 10:54:15 AM UTC-6, Joey Eremondi wrote:
>>
>>> I can see why these things look nice coming from Elixr, but Elm is not
>>> Elixr. We choose types, and there is a degree of awkwardness that comes
>>> from this, and that is somewhat inherent to the system. These things are
>>> easy when you have no types, like Elixr, and they are hard when you have
>>> types, in Elm. There is tons of research going on into how to deal with
>>> these things in a typed setting, but that's how you get the hundreds of GHC
>>> extensions like Haskell has, most of which break type inference and require
>>> you to annotate things yourself.
>>>
>>
>> This is not purely an Elixir thing, I just used it as is was a more
>> simple example compared to the complete and type-safe and OCaml code, which
>> I gave a hint of above (with the `Field(s)` stuff).
>>
>>
>> On Sunday, August 14, 2016 at 10:54:15 AM UTC-6, Joey Eremondi wrote:
>>
>>> On Sun, Aug 14, 2016 at 9:47 AM, Joey Eremondi <[email protected]>
>>> wrote:
>>>
>>>> @OvermindDL1: I'm still grokking what you've proposed, but it's
>>>> problematic for a few reasons. I'm 99% sure that it can't be done in a way
>>>> that is type safe.
>>>>
>>>
>> It can, because OCaml does. If we get a better type system sometime in
>> Elm then we could use even the better patterns that we do use in Haskell.
>>
>>
>> On Sunday, August 14, 2016 at 10:54:15 AM UTC-6, Joey Eremondi wrote:
>>
>>> What you're doing doesn't need Higher-Kinded types, it's just not type
>>>> safe, at least not as how it's presented here.
>>>>
>>>> One is that you've got type variables in your Access type that it is
>>>> not parameterized over. So you need to do something like this:
>>>>
>>>> type Access recordKeyType dictKeyType
>>>> = All
>>>> | At Int
>>>> | Elem Int
>>>> | Key recordKeyType {- Whatever recordKeyType might be as an
>>>> indicator for a key on a record -}
>>>> | DictKey dictKeyType
>>>> | Fn (EnumerableType -> EnumerableType) {- This is why I think HKT's
>>>> might be needed, or special caseing in the compiler -}
>>>>
>>>> or, with Higher-Ranked Types
>>>>
>>>> type Access
>>>> = All
>>>> | At Int
>>>> | Elem Int
>>>> | Key (forall recordKeyType . recordKeyType) {- Whatever
>>>> recordKeyType might be as an indicator for a key on a record -}
>>>> | DictKey (forall dictKeyType . dictKeyType)
>>>> | Fn (EnumerableType -> EnumerableType) {- This is why I think HKT's
>>>> might be needed, or special caseing in the compiler -}
>>>>
>>>> but, I'm pretty sure neither of these would do what you want. In the
>>>> first case, you'd need to know ahead of time what kind of record or Dict
>>>> you were dealing with, or you'd need to write something that worked with
>>>> any record/dict type, which is probably too generic to get anything done.
>>>>
>>>
>> It was a quick write-up, not meant as a final API as I am asking about
>> ideas of the style, not of my specific implementation. ;-)
>>
>>
>> On Sunday, August 14, 2016 at 10:54:15 AM UTC-6, Joey Eremondi wrote:
>>
>>> In the second case, you'd have to give a value that is *truly* generic,
>>>> that matches literally any type you throw at it. There is no such value.
>>>> This is also problematic, since it causes all sorts of awful with type
>>>> inference and would require significantly changing Elm's type system.
>>>>
>>>
>> No, rather the types should be compared recursively through value types
>> specified in the list, it definitely would *not* work as presented, nor was
>> it meant to be, just given as a possible example of usage.
>>
>>
>> On Sunday, August 14, 2016 at 10:54:15 AM UTC-6, Joey Eremondi wrote:
>>
>>> Also, I don't think you're quite understanding how the type variables
>>>> work. Let's look at the signature here:
>>>>
>>>> {-| Sets a value(s) at the given path -}
>>>> putIn
>>>> : List Access
>>>> -> newValue
>>>> -> EnumerableType
>>>> -> EnumerableType
>>>>
>>>> Every type variable in Elm has an implicit "forall" at the beginning of
>>>> the signature. So this is really:
>>>>
>>>> {-| Sets a value(s) at the given path -}
>>>> putIn
>>>> : forall newValue . List Access
>>>> -> newValue
>>>> -> EnumerableType
>>>> -> EnumerableType
>>>>
>>>> so what you're saying is, for *literally any type* T, if you give me a
>>>> List of Access and a value of type T and an EnumerableType, I can give you
>>>> an updated EnumerableType. So all of the following would be allowed:
>>>>
>>>> putIn [At 3] "Hello" {field = False}
>>>> putIn [Key foo] "Hello" {foo = True}
>>>> putIn [Key foo] "Hello" {bar ="Goodbye"}
>>>>
>>>> All of these should generate type errors, but they wouldn't with the
>>>> given types you have.
>>>>
>>>
>> Correct, but as stated, it is not an implementation example, just an idea
>> for a way of editing records, not a defined API, such a thing could be made
>> later if so wished, instead of a list of accesses you could have curried
>> functions (that would even work as-is in elm right now, but would require a
>> *lot* of boilerplate that elm should be able to generate itself as it knows
>> the record types).
>>
>>
>> On Sunday, August 14, 2016 at 10:54:15 AM UTC-6, Joey Eremondi wrote:
>>
>>> On Sun, Aug 14, 2016 at 8:40 AM, OvermindDL1 <[email protected]> wrote:
>>>>
>>>>> Expressions are not currently allowed to be updated like that, so
>>>>> would that return a model with the something.more updated on it (seems
>>>>> surprising) or return model.something with more updated on it (not
>>>>> surprising, and would allow arbitrary expressions there).
>>>>>
>>>>>
>>>>>
>>>>> On Saturday, August 13, 2016 at 11:51:29 PM UTC-6, Robin Heggelund
>>>>> Hansen wrote:
>>>>>>
>>>>>> All I really want is:
>>>>>>
>>>>>> ```elm
>>>>>> { model.something | more = 42 }
>>>>>> ```
>>>>>>
>>>>>> søndag 14. august 2016 02.49.07 UTC+2 skrev OvermindDL1 følgende:
>>>>>>>
>>>>>>> Just a passing idea to perhaps help give ideas for better methods:
>>>>>>>
>>>>>>>
>>>>>>> Updating a nested record is a bit convoluted as something like:
>>>>>>> ```elm
>>>>>>> let
>>>>>>> something = model.something
>>>>>>> in
>>>>>>> { model | something = { something | more = 42 } }
>>>>>>> ```
>>>>>>> Excepting the let/in part because Elm does not support an expression
>>>>>>> as the first argument (`model` and `something` in these cases) for
>>>>>>> I-have-no-clue-reason, and another language I work often in is Elixir,
>>>>>>> its
>>>>>>> syntax for the above would be similar:
>>>>>>> ```elixir
>>>>>>> %{ model | something: %{ model.something | more: 42 } }
>>>>>>> ```
>>>>>>>
>>>>>>> However, that is painful, so Elixir has a couple of helper functions
>>>>>>> that simplify that kind of work, let me demonstrate, this does the same
>>>>>>> as
>>>>>>> the above:
>>>>>>> ```elixir
>>>>>>> put_in models, [:something, :more], 42
>>>>>>> ```
>>>>>>> And you can go arbitrarily deep and it returns a new model with the
>>>>>>> path altered to the given value as necessary. Elixir also has lispy
>>>>>>> macros
>>>>>>> so you can also use the above function via:
>>>>>>> ```elixir
>>>>>>> put_in models.something.more, 42
>>>>>>> ```
>>>>>>> Basically using 'read' syntax to specify the path, but it gets
>>>>>>> expanded to the above at compile-time. It also supports not only
>>>>>>> records
>>>>>>> but also maps (dicts in elm), lists (also lists in elm) and anything
>>>>>>> else
>>>>>>> that follows the Access protocol (a set of functions of certain types
>>>>>>> to do
>>>>>>> basic functions), but those are the default.
>>>>>>>
>>>>>>> It has extra features like this, say `model.something` is a `List
>>>>>>> Int` in elm parlance:
>>>>>>> ```elixir
>>>>>>> put_in model, [:something, Access.all], 42
>>>>>>> ```
>>>>>>> This will set any and all values in the list at model.something to
>>>>>>> 42, not terribly useful, however it has a lot more functions as well,
>>>>>>> such
>>>>>>> as (I want to use more 'elmy' syntax, so I will now use things like
>>>>>>> `.something` instead of `:something` and no commas between arguments,
>>>>>>> only
>>>>>>> in tuples and lists and such):
>>>>>>> ```elixir
>>>>>>> -- Where model = { something : Dict String (List Int) }
>>>>>>> ( oldValue, newModel ) = get_and_update_in model [ .something,
>>>>>>> "joe", Access.at(0) ] (\oldValue -> let oldValue = Maybe.withDefault 0
>>>>>>> in (
>>>>>>> oldValue, Just (oldValue+1) ))
>>>>>>> ```
>>>>>>> This will update a value in and let you return a value (anything you
>>>>>>> wish) within a tuple. This one will access `Dict.get "joe"
>>>>>>> model.something` and get the returned list, accessing the first element
>>>>>>> (`at` for lists, `elem` for a tuple index starting at 0 as well), and
>>>>>>> the
>>>>>>> called passed in function returns a tuple where the first element is
>>>>>>> the
>>>>>>> first element of the returned tuple and the second element is what the
>>>>>>> thing at the path will be updated to, so this case will return the
>>>>>>> `oldValue+1` if it existed, if it did not then it returns 1 due to the
>>>>>>> `withDefault 0`.
>>>>>>>
>>>>>>> More functions it adds are:
>>>>>>> ```elixir
>>>>>>> -- Where model = { something : Dict String (List Int) }
>>>>>>> value = get_in model [ .something, "joe", Access.at(2) ] -- Returns
>>>>>>> the value at the path
>>>>>>>
>>>>>>> values = get_in model [ .something, Access.all, Access.at(2) ] --
>>>>>>> Returns all of the values 2nd values in the lists in all the values of
>>>>>>> the
>>>>>>> dictionary as a list if they exist, else they are skipped
>>>>>>>
>>>>>>> pop_in model [ .something, Access.all, Access.at(2) ] -- Removes the
>>>>>>> element in the list at position 2 in all the dictionary values if it
>>>>>>> exists, if it does not exist then it skips it
>>>>>>>
>>>>>>> update_in model [ .something, Access.all, Access.at(2) ] (\oldValue
>>>>>>> -> Just (( oldValue |> Maybe.withDefault 0 ) + 4)) -- Updates a
>>>>>>> value(s)
>>>>>>> in-place
>>>>>>> ```
>>>>>>> Along with macro's for the read-format pathing, which is not needed
>>>>>>> here.
>>>>>>>
>>>>>>> The keylist (the `[ .something, Access.all, Access.at(2) ]` in the
>>>>>>> last example) can also take functions, whatever they return (empty
>>>>>>> list,
>>>>>>> single-element list, multiple-element list, etc...) will be what is
>>>>>>> used
>>>>>>> and what is set back.
>>>>>>>
>>>>>>>
>>>>>>> *Thus*, what would be thought of Elm adding in functions like these
>>>>>>> (HKT's might be needed, not thought through the implementation yet,
>>>>>>> only
>>>>>>> the API):
>>>>>>> ```
>>>>>>> type Access
>>>>>>> = All
>>>>>>> | At Int
>>>>>>> | Elem Int
>>>>>>> | Key recordKeyType {- Whatever recordKeyType might be as an
>>>>>>> indicator for a key on a record -}
>>>>>>> | DictKey dictKeyType
>>>>>>> | Fn (EnumerableType -> EnumerableType) {- This is why I think
>>>>>>> HKT's might be needed, or special caseing in the compiler -}
>>>>>>>
>>>>>>> -- You'd need some kind of EnumerableType as well, no doubt opaque
>>>>>>> or something, or need HKT's, probably need HKT's in general, Elm really
>>>>>>> badly needs HKT's...
>>>>>>>
>>>>>>> {-| Get a value calculated from the old value and set a new value
>>>>>>> simultaneously -}
>>>>>>> getAndUpdateIn
>>>>>>> : List Access
>>>>>>> -> (Maybe valueType -> ( retValue, Maybe valueType ))
>>>>>>> -> EnumerableType
>>>>>>> -> ( List retValue, EnumerableType )
>>>>>>>
>>>>>>>
>>>>>>> {-| Gets a value from an access path -}
>>>>>>> getIn
>>>>>>> : List Access
>>>>>>> -> EnumerableType
>>>>>>> -> List retValue
>>>>>>>
>>>>>>>
>>>>>>> {-| Removes a value from a given path if possible, returning it if
>>>>>>> it exists -}
>>>>>>> popIn
>>>>>>> : List Access
>>>>>>> -> EnumerableType
>>>>>>> -> ( List retValue, EnumerableType )
>>>>>>>
>>>>>>>
>>>>>>> {-| Sets a value(s) at the given path -}
>>>>>>> putIn
>>>>>>> : List Access
>>>>>>> -> newValue
>>>>>>> -> EnumerableType
>>>>>>> -> EnumerableType
>>>>>>>
>>>>>>>
>>>>>>> {-| Updates a value in the path and returns the new modified object
>>>>>>> -}
>>>>>>> updateIn
>>>>>>> : List Access
>>>>>>> -> (Maybe oldValue -> Maybe newValue )
>>>>>>> -> EnumerableType
>>>>>>> -> EnumerableType
>>>>>>> ```
>>>>>>>
>>>>>>> These could then be used like:
>>>>>>> ```
>>>>>>> -- Where model = { something : Dict String (List Int) }
>>>>>>> -- With values of: model = { something = Dict.fromList [("joe", [1,
>>>>>>> 2, 3]), ("beth", [10, 11, 12, 13])] }
>>>>>>>
>>>>>>>
>>>>>>> ( oldValue, newModel ) = model |> getAndUpdateIn [ Key .something,
>>>>>>> DictKey "joe", All] (\v -> ( v, v |> Maybe.withDefault 0 |> (+) 1 ))
>>>>>>> -- Will return `oldValue == 1`
>>>>>>> -- And returns `newModel == { something = Dict.fromList [("joe", [2,
>>>>>>> 2, 3]), ("beth", [10, 11, 12, 13])] }`
>>>>>>>
>>>>>>>
>>>>>>> newModel = model |> putIn [ Key .something, All ] [ 42 ]
>>>>>>> -- Will return `newModel == { something = Dict.fromList [("joe",
>>>>>>> [42]), ("beth", [42])] }````
>>>>>>>
>>>>>>>
>>>>>>> newModel = model |> updateIn [ Key .something, Fn (\dict -> dict |>
>>>>>>> Dict.filter (\k v -> (String.length k) >= 4 ) ) ] (\v -> v)
>>>>>>> -- Will return `newModel == { something = Dict.fromList [("beth",
>>>>>>> [10, 11, 12, 13])] }`
>>>>>>> -- This is because we used a Fn to return the set of things we want
>>>>>>> to operate over and as such will only assign those back, allowing us to
>>>>>>> filter out things with ease.
>>>>>>> ```
>>>>>>>
>>>>>>> This style makes doing very complex embedded enumerable updating
>>>>>>> with ease. However as the above is proposed it would likely require
>>>>>>> Higher
>>>>>>> Kind Types in Elm, which does not have those yet, thus for now just
>>>>>>> implementing the above for just records would be sufficient for a good
>>>>>>> portion if not the overwhelming majority of program, and that could be
>>>>>>> done
>>>>>>> without HKT's.
>>>>>>>
>>>>>> --
>>>>> 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] <javascript:>.
>> 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.