如何使 input[][] return 成为 Elm 中的 Just Int 而不是 String?

How to make input[][] return a Just Int instead of String in Elm?

我是 Elm 的完全初学者,我正在为类型而苦苦挣扎。我一直在与类型作斗争,但我找不到解决此问题的便捷方法。有问题的代码是这样的:

view model =
    div []
        [ h1 [] [ text "Robot" ]
        , input [ onInput SetX, value model.x ] []
        , input [ onInput SetY, value model.y ] []
        , input [ type_ "Int" , onInput SetCommands, value model.x] []
        , button [ onClick ButtonPressed] []
        , input [ readonly True ] []
        ]

X 和 Y 是在以下函数中使用的整数:

execute_orders : Int -> Int -> String -> String -> String -> { x : Int, y : Int, dir : String }
execute_orders x y commands lang dir =
    let
        moves =
            case lang of
                "English" ->
                    "RLF"

                "French" ->
                    "HVG"

                "Swedish" ->
                    "DGT"

                _ ->
                    Debug.todo "No Language Found"

        fw =
            right 1 moves

        directions =
            facing dir

        rght =
            left 1 moves

        lft =
            slice 1 2 moves

        first_move =
            left 1 (toUpper commands)

        rest =
            slice 1 (length commands) (toUpper commands)
    in
    if first_move == "" then
        { x = x, y = y, dir = fromJust (head directions) }

    else if first_move == fw then
        if dir == "N" then
            execute_orders x (y - 1) rest lang dir

        else if dir == "E" then
            execute_orders (x + 1) y rest lang dir

        else if dir == "S" then
            execute_orders x (y + 1) rest lang dir

        else
            execute_orders (x - 1) y rest lang dir

    else if first_move == lft then
        execute_orders x y rest lang (fromJust (head (turn_left directions)))

    else if first_move == rght then
        execute_orders x y rest lang (fromJust (head (turn_right directions)))

    else
        Debug.todo "branch '_' not implemented"

我需要 x 和 y 为 Int,但从 String 转换为 Maybe Integer 类型...

无法将 String 简单地“转换”为 Int,因为在那个方向上没有完全映射。例如,字符串 "banana" 没有明显的整数表示。你挣扎的不是“类型”,而是不同的事物因不同的原因而不同,并且必须做出巩固这些差异的决定。

String.toInt returns a Maybe Int 因为当给定字符串没有合理且明显的整数表示时,由您决定要做什么。您可以选择只默认为 0-1,还是显示字符串 -。这些都是针对不同场景的合理选择,但由于 String.toInt 不可能知道你处于哪种场景, 必须做出决定。

如果您只是想回退到默认整数,或者您确定任何给定的字符串都将具有有效的整数表示,您可以使用 Maybe.withDefault:

String.toInt "42" |> Maybe.withDefault 0     -- 42
String.toInt "banana" |> Maybe.withDefault 0 -- 0

或者您可以使用 case 表达式来做一些完全不同的事情:

case String.toInt s of
  Just n ->
    span [] [ text (n + 1 |> String.fromInt) ]

  Nothing ->
    button [ onClick ... ] [ text "-" ]

由于 HTML 输入事件总是处理字符串,您需要在某个时候解析您收到的字符串。我看到了三个选项。

首先,您的事件消息的有效负载可以是 String,然后您在 update 函数中解析该字符串。

type Msg
    = SetX String

type alias Model =
    { x : Int }

update msg model =
    case msg of
        SetX xString ->
            case String.toInt xString of
                Nothing ->
                    model
                Just x ->
                    { model | x = x }

上面的一个变体是将 x 作为 Maybe String 存储在你的模型上,如果你愿意的话,and/or 存储一些用于跟踪错误状态的值在发送无效值时显示消息。这是最简单和最灵活的方法。

第二个选项是将事件消息的有效负载设为 Maybe IntHtml.Events.onInput 是一个接受“标记器”函数的函数,该函数接受 String 并输出一条消息。虽然您可以只提供一个接受 String 有效负载的消息构造函数,但您也可以提供一个函数,该函数在创建 Msg 值之前进行一些处理。

type Msg
    = SetY (Maybe Int)

view model =
    input [ type_ "number", onInput (\value -> String.toInt value |> SetY) ] []

第三个选项是将事件消息的有效负载设为 Int。这将要求您使用 Html.Events.on 创建自己的 onInput 版本。 on 使用 Json.Decode.Decoder msg 而不是 onInput 使用的 (String -> msg) 函数。仅当解码器成功时才会生成消息;也就是说,产生解码器失败的事件将被忽略。这允许您处理事件处理程序中解析失败的情况。

type Msg
    = SetY Int

view model =
    input [ type_ "number", onNumericInput SetY ] []

-- based on https://github.com/elm/html/blob/97f28cb847d816a6684bca3eba21e7dbd705ec4c/src/Html/Events.elm#L115-L122
onNumericInput : (Int -> msg) -> Attribute msg
onNumericInput toMsg =
    let
        alwaysStop : a -> ( a, Bool )
        alwaysStop x =
            ( x, True )

        failIfNothing : Maybe a -> Decode.Decoder a
        failIfNothing maybe =
            case maybe of
                Nothing ->
                    Decode.fail "Parsing failed"

                Just a ->
                    Decode.succeed a

        decoder : Decode.Decoder msg
        decoder =
            Events.targetValue
                |> Decode.map String.toInt
                |> Decode.andThen failIfNothing
                |> Decode.map toMsg
    in
    Events.stopPropagationOn "input" (decoder |> Decode.map alwaysStop)

您可以在 https://ellie-app.com/h6fstFTyjXDa1

查看完整示例中的最后两个选项