1
votes

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?

1
I would say the type error is clear enough, but I can't say how to fix it because I don't understand what you're trying to do. I would guess you just want to leave your model unchanged in the Nothing case? I certainly don't see much else you can do - but as I said I don't really know what you're aiming for. - Robin Zigmond

1 Answers

3
votes

It seems to me that the model is wrong, for two reason:

  1. If you have a value that is common across all cases, it's usually more convenient to move it out and up a level.

  2. 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