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 <[email protected]> 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].
> 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.