I have a custom type in Elm to handle error branching. Basically, I have an input, which gives String
s and I need to convert it to Int
s.
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