Elm: Update value in sub types - elm

I have this type
type alias Cat =
{ head : Head
}
type alias Head =
{ eyes : Int
, nose : Int
, mouth : Mouth
}
type alias Mouth =
{ tongue : Int
, prey : Prey
}
type alias Prey =
{ left : Int
, right : Int
}
Inserting values
cat : Cat
cat =
{ head =
{ eyes = 2
, nose = 1
, mouth =
{ tongue = 1
, prey =
{ left = 1
, right = 1
}
}
}
}
I'm usually doing this to change the right side of the prey
let
newCat =
(Cat
(Head oldValue.eyes oldValue.nose
(Mouth
(Prey oldValue.left 1)
)
)
)
in
({model | cat = newCat }, Cmd.none)
And I hate hitting that, but I can not figure out how to do it the right way. When I try to do it right I can not return 'type cat'.
How to change the right prey in the right way?

You're already doing it the right way.
When you're dealing with nested records, you have to build them up, just like you're already doing.
Now, what you can do is abstract newCat and turn it into a function of type Int -> Cat -> Cat. Then, you'd be able to do ({model | cat = updateRightPrey 1 model.cat }, Cmd.none)
But honestly, you may want to reconsider your data model. Does Head, Mouth, and Prey actually provide any value? What would you loose (and what would you have to gain) if you flatten your model like this:
type alias Cat =
{ eyes : Eyes
, nose : Nose
, tongue : Tongue
, left : Prey
, right : Prey
}
type alias Eyes = Int
type alias Nose = Int
type alias Tongue = Int
type alias Prey = Int
Even if you change all those Int type alias to types...
type Eyes = Eyes Int
... it would be a lot simpler than dealing with nested records:
let
cat = model.cat
in
({model | cat = { cat | right = 1 }, Cmd.none)
or...
let
cat = model.cat
in
({model | cat = { cat | right = Prey 1 }, Cmd.none)

Related

how to link variables so when i modify one of them both are modified? or just the top level one

I have 3 variables A,b and c in a class called ONE
var a = 0
var b = 0
var c = 0
that class(class ONE) is abstract and is used by 3 classes(TWO, THREE, FOUR). the variable selected changes by using a when statement.
var chosenVariable:Long
chosenVariable= when (sign){
1-> a
2-> b
3-> c
else -> a
}
note that sign is what the user inputs to decide which variable is chosen.
my question is. when i modify chosenVariable I want it to change whatever variable it was set a, b or c.
is this possible? I thought it might be called instancing, but I can't seem to come up with anything searching that way. I think i need to do it somehow with a setter and getter?
You can use KMutableProperty0<> class to hold reference to primitive variables:
var chosenVariable: KMutableProperty0<Long> = when (sign) {
1-> ::a
2-> ::b
3-> ::c
else -> ::a
}
// set value
chosenVariable.set(7)
It is currently available only for global variables. Here is some useful info.
If you mean chosenVariable is the sign, either:
there can be another function to set/get chosen variables (a, b, c), such as:
var chosenVariable : Long = 0 // init value
fun set ( value : Int ) {
when ( chosenVariable ) {
// 1 -> a = value
2 -> b = value
3 -> c = value
else -> a = value
}
}
fun get ( ) {
return when ( chosenVariable ) {
// 1 -> a
2 -> b
3 -> c
else -> a
}
}
or maybe use MutableMap instead:
val variables = mutableMapOf <Long /* sign */, Int /* value */> (
1 to 0, // a
2 to 0, // b
3 to 0, // c
)
// still with getter/setter like before, but with map
fun get ( ) = variables [ chosenVariable ]
fun set ( value : Int ) {
variables [ chosenVariable ] = value
}

Transform string in key variable name

There is the possibility of converting a string to a key, eg:
type alias Model =
{ sun : Int -- Init with 0
, moon : Int -- Init with 0
}
What I want to achieve:
let
userSelect = "sun";
in
({ model | userSelect = 1 }, Cmd.none) -- ugly to be easy to understand
After model.sun should 1
You won't be able to do exactly what you want as access to records does not work that way. In your case i would recommend a Dictionary
type alias Model =
{ planets : Dict String Int
}
planets =
Dict.fromList
[ ("sun", 0)
, ("moon": 0)
]
model = { planets = planets }
and then
let
userSelect = "sun";
in
({ model | planets = Dict.insert userSelect 1 model.planets }, Cmd.none)

Remove item of record without knowing all item

I have this simulation:
init : (Model, Cmd Msg)
init = ({ dog = List Dog }, Cmd.none)
type alias Dog =
{ name : String
, age : Int
, price : Float
, extra = List Extra
}
type alias Extra =
{ allergies : List String
, wishes : List String
}
[{ name = "Hot"
, age = 1
, price = 300.5
, extra = [{...}]
},
{ name = "Dog"
, age = 3
, price = 150.0
, extra = [{...}]
}]
And I want to remove only 'extras' of Dog, in determined part of the code:
[{ name = "Hot"
, age = 1
, price = 300.5
},
{ name = "Dog"
, age = 3
, price = 150.0
}]
I can do this by mapping the entire list and generating a new one by removing 'extra' occurrence:
removeExtraOfDogs dogList =
(dogList |> List.map (\dog ->
{ name = dog.name
, age = dog.age
, price = dog.price
}
))
but I want to make it dynamic to just pass the extra to remove, without having to know what variables there are in the type and recreate it
Elm used to have this feature but it was removed a while ago. But, based on your use case described in a comment, I don't think you need this feature. You can instead use extensible record feature of Elm to allow passing different records into a function as long as they contain a fixed set of fields.
For example, let's say you have two types with name and age fields and having an extra incompatible field:
type alias Foo = { name : String, age : Int, extra : List String }
type alias Bar = { name : String, age : Int, extra : Int }
You can define a function that takes a record with a name field of type String and age of type Int and any extra fields:
encode : { r | name : String, age : Int } -> String
encode record = record.name ++ "," ++ toString record.age
You'll can now pass both Foo and Bar to this function because they both satisfy the requirements of the type signature.

How to upate a record that has another record within it?

I have a record :
type alias Point = {x : Int, y : Int}
I have another record like this :
type alias SuperPoint = {p : Point, z : Int}
p = Point 5 10
sp = SuperPoint p 15
Now if I need to update SuperPoint.z I can do this :
{sp | z = 20}
How do I update SuperPoint.Point?
sp2 =
let
p2 = { p | x = 42 }
in
{ sp | p = p2 }
Right now you have four ways to update:
Two of them you can find below in code
third is using Monocle.
fourth: Re-structure your model with Dictionaries and handle the update in a proper generic way
A the end there is also a code which doesn't work, but could be useful, if that worked that way
Example in elm-0.18
import Html exposing (..)
model =
{ left = { x = 1 }
}
updatedModel =
let
left =
model.left
newLeft =
{ left | x = 10 }
in
{ model | left = newLeft }
updateLeftX x ({ left } as model) =
{ model | left = { left | x = x } }
updatedModel2 =
updateLeftX 11 model
main =
div []
[ div [] [ text <| toString model ]
, div [] [ text <| toString updatedModel ]
, div [] [ text <| toString updatedModel2 ]
]
Examples from https://groups.google.com/forum/#!topic/elm-discuss/CH77QbLmSTk
-- nested record updates are not allowed
-- https://github.com/elm-lang/error-message-catalog/issues/159
updatedModel3 =
{ model | left = { model.left | x = 12 } }
-- The .field shorthand does not for the update syntax
-- https://lexi-lambda.github.io/blog/2015/11/06/functionally-updating-record-types-in-elm/
updateInt : Int -> (data -> Int) -> data -> data
updateInt val accessor data =
{ data | accessor = val }
updatedModel4 =
{ model | left = updateInt 13 .x model.left }

Setting values in aliased records

I'm just starting out with Elm, so be nice:)
In the "Random" example of the official Elm Guide the model seems to be initialized with the value 1 like this:
type alias Model =
{ dieFace : Int
}
init : (Model, Cmd Msg)
init =
(Model 1, Cmd.none)
My understanding of this is that the code
Model 1
sets the value of the dieFace attribute in the record. Is this correct and if so: what is this strange syntax for setting the attribute of a record? I would have expected something along the lines of
{ model | dieFace = 1 }
Model is a type alias for a record which has a single int value called dieFace.
There are a few ways to create a value of that type:
Model 1 -- treats Model as a function with a single parameter
{ dieFace = 1 } -- creates a record that happens to coincide with the Model alias
The strange syntax you see in { model | dieFace = 1 } is short-hand for creating a new value based off an existing record value but changing one or more fields. It perhaps doesn't make much sense when your record type has a single field so let's create an arbitrary example:
type alias ColoredDie = { dieFace: Int, color: String }
You can play around in the Elm REPL and perhaps this will help it make sense:
> blue3 = ColoredDie 3 "blue"
{ dieFace = 3, color = "blue" } : Repl.ColoredDie
> red3 = { blue3 | color = "red" }
{ dieFace = 3, color = "red" } : { dieFace : Int, color : String }
> red4 = { red3 | dieFace = 4 }
{ dieFace = 4, color = "red" } : { color : String, dieFace : number }
> green6 = { red4 | color = "green", dieFace = 6 }
{ dieFace = 6, color = "green" } : { color : String, dieFace : number }
You can read up on Elm's record syntax here.