Conditionally change record in update - elm

I would like to have some logic worked upon the Msg and, depending on the result, update the view in a different ways.
I'm flipping some cards, and I want to test two of the selected ones. Then, accept them as a pair or discard and try again.
update : Msg -> Model -> Model
update msg model =
case msg of
ClickedCard data ->
{ model
| activeCard = data.id
, (if List.lenght selectedCards < 2 then
selectedCards = data.id :: model.selectedCards
else if (List.take 1 model.selectedCards) == (List.drop 1 model.selectedCards) then
completedPairs = ( List.take 1 model.selectedCards , List.drop 1 model.selectedCards ):: model.completedPairs
else
selectedCards = List.drop 2 model.selectedCards)
}
_ ->
model
But, seems like I can't insert the logic there. Where should I put it, instead?
-- PROBLEM IN RECORD ------------------------------------------ src/Flipping.elm
I am partway through parsing a record, but I got stuck here:
126| { model
127| | activeCard = data.id
128| , (if List.lenght selectedCards < 2 then
^
I was expecting to see another record field defined next, so I am looking for a
name like userName or plantHeight.

The record update syntax doesn't work like that.
You can do the following.
update : Msg -> Model -> Model
update msg model =
case msg of
ClickedCard data ->
let
newModel = { model | activeCard = data.id }
in
if List.length selectedCards < 2 then
{newModel | selectedCards = data.id :: model.selectedCards}
else if (List.take 1 model.selectedCards) == (List.drop 1 model.selectedCards) then
{newModel | completedPairs = ( List.take 1 model.selectedCards , List.drop 1 model.selectedCards ):: model.completedPairs}
else
{newModel | selectedCards = List.drop 2 model.selectedCards)}
_ ->
model

Related

Elm: Access value of custom type

I have a custom type in Elm to handle error branching. Basically, I have an input, which gives Strings and I need to convert it to Ints.
type Seconds
= Error Int
| Valid Int
type alias Model =
{ timeBetweenExercises : Seconds
, roundsSequenceOne : Seconds
, roundsSequenceTwo : Seconds
, roundsSequenceThree : Seconds
, repititionsPerExercise : Seconds
, secondsPerExercise : Seconds
, totalTrainingDuration : Seconds
}
init : Model
init =
{ timeBetweenExercises = Valid 3
, roundsSequenceOne = Valid 3
, roundsSequenceTwo = Valid 3
, roundsSequenceThree = Valid 3
, repetitionsPerExercise = Valid 6
, secondsPerExercise = Valid 6
, totalTrainingDuration = Valid 6
}
I got the idea for the custom type from Evan's "Life of a file" talk. I want to remember the number when there is an error (e.g. a user entered a string instead of a number). Here is the attempt at my update function:
update : Msg -> Model -> Model
update msg model =
case msg of
TimeBetweenExercisesChanged newTime ->
case String.toInt newTime of
Nothing ->
{ model | timeBetweenExercises = Error model.timeBetweenExercises }
Just time ->
{ model | timeBetweenExercises = Valid time }
My problem is, that the compiler yells at me because because model.timeBetweenExercises is of type Seconds. Is there a way I can only get the Int value of the custom type?
It seems to me that the model is wrong, for two reason:
If you have a value that is common across all cases, it's usually more convenient to move it out and up a level.
Your update function suggests that the Error state doesn't actually describe the value it holds, as you're just throwing away newTime if invalid and using the old time for the Error case instead.
Based on that, I'd suggest the following model instead:
type alias TimeInput =
{ seconds: Int
, state: TimeInputState
}
type TimeInputState = Error | Valid
type alias Model =
{ timeBetweenExercises : TimeInput
...
}
and to change your update function to this:
update : Msg -> Model -> Model
update msg model =
case msg of
TimeBetweenExercisesChanged newTime ->
case String.toInt newTime of
Nothing ->
{ model
| timeBetweenExercises =
{ seconds = model.timeBetweenExercises.seconds
, state = Error
}
}
Just time ->
{ model
| timeBetweenExercises =
{ seconds = time
, state = Valid
}
}
Otherwise, you could always just make a function to extract the Int no matter the case:
getSeconds : Seconds -> Int
getSeconds time =
case time of
Error seconds -> seconds
Valid seconds -> seconds

Get constructor by value option in Elm (0.19.1)

I have some types:
type alias Type1 = { name : String }
type alias Type2 = { name : String, age : Int }
type S
= S1 Type1
| S2 Type2
So, when I want to build my info I can do the following:
a = S1 { name = "Name1" }
b = S2 { name = "Name2", age = 100 }
Now, I would like to get its constructor (S1 or S2) based on the info passed as parameter to the constructorByData function. Examples:
constructorByData a -- It should be returned `S1`
constructorByData b -- It should be returned `S2`
This is my aproach:
constructorByData : S -> (a -> S)
constructorByData s =
case s of
S1 _ -> S1
S2 _ -> S2
Please, note that the above code does not work. I get the following error:
-- TYPE MISMATCH ---------------------------------------------------------- REPL
Something is off with the 2nd branch of this `case` expression:
14| S2 _ -> S2
^^
This `S2` value is a:
Type2 -> S
But the type annotation on `constructorByData` says it should be:
a -> S
-- TYPE MISMATCH ---------------------------------------------------------- REPL
Something is off with the 1st branch of this `case` expression:
13| S1 _ -> S1
^^
This `S1` value is a:
Type1 -> S
But the type annotation on `constructorByData` says it should be:
a -> S
The main reason I want to do this is because I want to do the following by generalizing the message:
type Field
= Name
| Age
type Msg
= UpdateFieldString S Field String
| UpdateFieldInt S Field Int
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
case msg of
UpdateFieldString s field value -> updateFieldString model s field value
UpdateFieldInt s field value -> updateFieldInt model s field value
And is for this I need the constructor in updateField{String, Int} functions

Elm update infinite loop

New to Elm, so I may be missing something obvious.
I'm working on an Elm application that uses annaghi/dnd-list. I'm encountering an infinite loop of calls to update. This happens when clicking on one element, then another one. Here's the code:
config : DnDList.Config Player
config =
{ beforeUpdate = \_ _ list -> list
, movement = DnDList.Free
, listen = DnDList.OnDrag
, operation = DnDList.Swap
}
system : DnDList.System Player Msg
system =
DnDList.create config DndMsg
type alias Model =
{ navKey : Nav.Key
, room : WebData Room
, dnd : DnDList.Model
, startError : Maybe String
}
type Msg
= RoomReceived (WebData Room)
| DndMsg DnDList.Msg
...
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
case msg of
RoomReceived room ->
( { model | room = room }, Cmd.none )
DndMsg message ->
let
room = model.room
in
case room of
RemoteData.Success actualRoom ->
let
( dnd, players ) =
system.update message model.dnd actualRoom.players
updatedRoom = RemoteData.map
(\roomData ->
{ roomData | players = players }
) room
in
( { model | dnd = dnd, room = updatedRoom }
, system.commands model.dnd
)
_ ->
( model, Cmd.none )
When I change the line system.commands model.dnd to Cmd.none, then there is no infinite looping call to the update function, but also nothing happens. The message that keeps getting called in the dnd-list library is GotDropElement (Ok dropElement)
Again, new to Elm, so this may be a poorly formed question, but any help is appreciated.
Thanks!
Figured it out. Had to add a subscription to listen to mouse events
currentSubs : Model -> Sub Msg
currentSubs model =
case model.page of
GameRoomPage pageModel ->
GameRoom.subscriptions pageModel
|> Sub.map GameRoomMsg
_ ->
always Sub.none model
...
main : Program () Model Msg
main =
Browser.application
{ view = view
, init = init
, update = update
, subscriptions = currentSubs
, onUrlRequest = LinkClicked
, onUrlChange = UrlChanged
}
Try following the flow from system.commands. Probably it is eventually sending again the message DndMsg and this is what is causing the issue.
It is usually considered not a good practice to send messages from commands.
In case you cannot solve the issue, having a working example of the problematic code in Ellie (https://ellie-app.com/new) would help.

How can I pass around state without copying an entire structure per message?

I have a textbox that's extremely slow when displaying characters.
I once observed a Stackoverflow exception related to it.
I believe that the performance issue is associated to preparing the portal value:
Sources.InputAccessId _ ->
( { model | portal = portal }, sourceCmd )
Each time a character is keyed-in, I copy and modify a new portal record which consumes more memory.
The code can be found below.
Main:
onSourcesUpdated : Sources.Msg -> Model -> ( Model, Cmd Msg )
onSourcesUpdated subMsg model =
let
pendingPortal =
model.portal
provider =
pendingPortal.provider
profile =
provider.profile
source =
pendingPortal.newSource
( sources, subCmd ) =
Sources.update subMsg
{ profileId = profile.id
, platforms = model.platforms
, source = { source | profileId = profile.id }
, sources = profile.sources
}
sourceCmd =
Cmd.map SourcesUpdated subCmd
pendingProvider =
{ provider | profile = { profile | sources = sources.sources } }
portal =
{ pendingPortal | newSource = sources.source, provider = pendingProvider }
in
case subMsg of
Sources.InputAccessId _ ->
( { model | portal = portal }, sourceCmd )
...
Sources.elm:
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
let
source =
model.source
in
case msg of
InputAccessId v ->
( { model | source = { source | accessId = v } }, Cmd.none )
...
How can I pass around state without copying entire structures each time an event occurs?
You might want to debounce these events with libraries like this one: elm-debounce
The consequence of debouncing though is that some events will be discarded so you only get one that sums them all when the user is done typing/interacting with the page (more precisely, after a – configurable - timeout).

creating sequences in ML

datatype 'a seq = Nil | Cons of 'a * (unit -> 'a seq);
fun head(Cons(x,_)) = x | head Nil = raise EmptySeq;
fun tail(Cons(_,xf)) = xf() | tail Nil = raise EmptySeq;
datatype direction = Back | Forward;
datatype 'a bseq = bNil
| bCons of 'a * (direction -> 'a bseq);
fun bHead(bCons(x,_)) = x | bHead bNil = raise EmptySeq;
fun bForward(bCons(_,xf)) = xf(Forward) | bForward bNil = raise EmptySeq;
fun bBack(bCons(_,xf)) = xf(Back) | bBack bNil = raise EmptySeq;
so after all those definitions. here's what I'm trying to do. I need to take 2 sequences and make them into 1 sequence that I can move Farward and Back on.
for example if 1 sequence is 0123... and 2 is -1-2-3-4..., I get -4-3-2-10123.
my current location must always be the first element of the sequence going "up".
this is what I've tried to do:
(*************************************************************)
local
fun append_aux (Nil, yq) = yq
|append_aux (Cons(x,xf), yq) = Cons(x,fn() => append_aux(xf(),yq))
fun append(t,yq) = append_aux(Cons(t,fn() => Nil),yq)
in
fun seq2bseq (Cons(x,xrev)) (Cons(y,ynorm)) = bCons(y,fn Farward => seq2bseq (append(y,Cons(x,xrev))) (ynorm())
|Back => seq2bseq (xrev()) (append(x,Cons(y,ynorm))))
|seq2bseq (_) (_) = bNil
end;
but I get an error "match redundant" for the "Back" match and I can't figure out why.