Transform string in key variable name - elm

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)

Related

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.

Elm: Update value in sub types

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)

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.

Updating a record in model based on another record

I have something like this:
type alias Record =
{ id : String
, nextId : String
, value : String
, result : Int
}
type alias Model = List Record
model = [
{
...
},
...
]
...
update id model =
List.map (updateRecord id) model
updateRecord id record =
if record.id == id then
{record | result = 1}
else
record
Basically what I need when I call update with some id is to update record with that id (that part I already have) but also grab the value from the record's nextId, find another record with record.id == nextId and pass the value from the first record to the second one
Basically what I am looking for written in JS:
function update(id, model) {
var nextId, value;
var newModel1 = model.map(function(record) {
if (record.id === id) {
record.result = 1;
nextId = record.nextId;
value = record.value;
return record;
} else {
return record;
}
});
var newModel2 = model.map(function(record) {
if (record.id === nextId) {
record.value = value;
return record;
} else {
return record;
}
});
return newModel2;
}
How do I achieve it?
This ought to give you what you're looking for:
type alias UpdateNext =
{ nextId : String
, value : String
}
update id model =
let
(matchedModel, nextToUpdate) = match id model
in
case nextToUpdate of
Nothing ->
model
Just next ->
updateNext next matchedModel
match : String -> Model -> (Model, Maybe UpdateNext)
match id model =
case model of
[] ->
([], Nothing)
(x::xs) ->
if x.id == id then
( { x | result = 1 } :: xs
, Just { nextId = x.nextId, value = x.value }
)
else
let (ys, updateNext) = match id xs
in (x :: ys, updateNext)
updateNext : UpdateNext -> Model -> Model
updateNext next model =
case model of
[] ->
[]
(x::xs) ->
if x.id == next.nextId then
{ x | value = next.value } :: xs
else
x :: updateNext next xs
This is going to be more efficient than List.map because it short-circuits processing when it has matched the result.