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


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 }



On Sun, Aug 14, 2016 at 11:28 AM, OvermindDL1 <overmind...@gmail.com> 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 <joey.e...@gmail.com>
>> 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 <overm...@gmail.com> 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 elm-discuss...@googlegroups.com.
>>>> 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 elm-discuss+unsubscr...@googlegroups.com.
> 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 elm-discuss+unsubscr...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Reply via email to