传递给 Dict.map 的函数类型错误

Type error on the function passed to Dict.map

我是函数式编程的新手,老实说大约 2 天。我正在尝试从 Dict Int Int 打印出值,但我似乎无法弄清楚如何将此 Dict Int Int 传递给函数。

我收到的错误如下。

The 1st argument to map is not what I expect:

32| div [] (Dict.map toLiDict dict) ^^^^^^^^ This toLiDict value is a:

Dict Int Int -> Html msg

But map needs the 1st argument to be:

Dict Int Int -> b

函数 toHtmlDicttoLiDict 是导致我出现此问题的原因,它们目前已在下方注释掉。我也从视图中调用它,div [] [ toHtmlDict model.uniqueValues ],我也将其注释掉了。

这是我目前正在使用的;我发布了整个代码,因为如果您需要其他任何东西,它会更容易。

这是 Ellie here 上的 link,可以是 运行。

module Main exposing (main)

import Browser
import Dict exposing (Dict)
import Html.Attributes
import Html exposing (Html, button, div, text, strong, p, li, ul)
import Html.Events exposing (onClick)

type alias Model =
    { currentNumber : Int, clicks : Int, outputList : List(String), uniqueValues : Dict Int Int }
    --{ currentNumber : Int, clicks : Int, history : String, outputList : List(String) }

initialModel : Model
initialModel =
    { currentNumber = 0, clicks = 0, outputList = [""], uniqueValues = Dict.fromList [(1,1)] } --Dict.empty should be default here...
    --{ currentNumber = 0, clicks = 0, history = "Current outputs ...", outputList = ["Current outputs ...", " "] }

-- applies a new div for each element in the list
toHtmlList : List String -> Html msg
toHtmlList strings =
  div [] (List.map toLi strings)

-- creates a div along with the text to be shown
toLi : String -> Html msg
toLi s = 
  div [] [ text s ]

-- --applies a new div for each element in the dictionary
-- toHtmlDict : Dict Int Int -> Html msg
-- toHtmlDict dict =
--   div [] (Dict.map toLiDict dict)

-- -- creates a div along with the text to be shown
-- toLiDict : Dict Int Int  -> Html msg
-- toLiDict k = 
--   div [] [ text "What here?" ]

type Msg
    = Increment
    | Decrement

update : Msg -> Model -> Model
update msg model =
    case msg of
    -- Note: when assigning the model.currentNumber and then concatenating the value, it will not be updated...
        Increment ->
            { model | currentNumber = model.currentNumber + 1, clicks = model.clicks + 1, outputList = model.outputList ++ [addToPage (oddOrEven(model.currentNumber + 1)) (model.currentNumber + 1)], uniqueValues = model.uniqueValues }
            --{ model | currentNumber = model.currentNumber + 1, clicks = model.clicks + 1, history = model.history ++ addToPage (oddOrEven(model.currentNumber + 1)) (model.currentNumber + 1), outputList = model.outputList ++ [addToPage (oddOrEven(model.currentNumber + 1)) (model.currentNumber + 1)] }

        Decrement ->
            { model | currentNumber = model.currentNumber - 1, clicks = model.clicks + 1, outputList = model.outputList ++ [addToPage (oddOrEven(model.currentNumber - 1)) (model.currentNumber - 1)]}
            --{ model | currentNumber = model.currentNumber - 1, clicks = model.clicks + 1, history = model.history ++ addToPage (oddOrEven(model.currentNumber - 1)) (model.currentNumber - 1), outputList = model.outputList ++ [addToPage (oddOrEven(model.currentNumber - 1)) (model.currentNumber - 1)]}

view : Model -> Html Msg
view model =
    Html.div []    
        [ button [ onClick Increment ] [ strong [Html.Attributes.style "color" "black"] [ text "+1" ]]     
        , button [ onClick Decrement ] [ strong [Html.Attributes.style "color" "red"] [ text "-1" ]]
        , p [] []      
        --, div [] [ text <| "The current number is: ",  strong [Html.Attributes.style "color" "red"] [ text <| String.fromInt model.currentNumber ], text " and it's ", text (oddOrEven model.currentNumber) ]
        , div [] [ text <| "The current number is: ",  strong [Html.Attributes.style "color" <| evenOddColor model.currentNumber] [ text <| String.fromInt model.currentNumber ], text " and it's ", strong [Html.Attributes.style "color" <| evenOddColor model.currentNumber ] [ text <| oddOrEven model.currentNumber ] ]          
        , div [] [ text "Total clicks: ",  strong [Html.Attributes.style "color" "red"] [ text <| String.fromInt model.clicks ]] 
        , p [] []
        , div [] [ strong [Html.Attributes.style "color" "Blue"] [ text "Unique values ..." ]]          
        , p [] []
        --, div [] [ toHtmlDict model.uniqueValues ]
        --, div [] [ text <| "The current number is: " ++ String.fromInt model.currentNumber ++ " and it's " ++  oddOrEven model.currentNumber ]        
        --, div [] [ text "Total clicks: ",  strong [Html.Attributes.style "color" "red"] [ text <| String.fromInt model.clicks ]] 
        --, p [] [] 
        , div [] [ strong [Html.Attributes.style "color" "Blue"] [ text "History ..." ]]    
        , p [] []     
        --, div [] [ text <| model.history ] - TEMPORARY        
        , div [] [ toHtmlList model.outputList ]

        ]

-- appendToList string number =
--     "Number was " ++ String.fromInt number ++ " and it was " ++ string 

addToPage string number =     
    "Number was " ++ String.fromInt number ++ " and it was " ++ string           

-- determines if number is even or odd        
oddOrEven number =
 if modBy 2 number == 0 then
     "even" 
 else
    "odd"

-- call another function with param
evenOddColor number =
 if oddOrEven(number) == "even" then
    "green"
 else
    "red"

main : Program () Model Msg
main =
    Browser.sandbox
        { init = initialModel
        , view = view
        , update = update
        }

Dict.map的定义是(k -> a -> b) -> Dict k a -> Dict k b。所以它需要一个函数和一个 Dict 和 return 一个新的 Dict。该映射函数采用键和值,return 是一个新值。

在您的情况下,您正在寻找 return List (Html Msg),而不是 Dict 任何东西。因此,我不会使用 Dict.map,而是调用 Dict.values to get a List of the values, and then use List.map to transform those values into Html Msg. If you need both the key and value to generate the Html Msg, use Dict.toList 来获得 List (k, v)(即元组的 List,其中元组具有键和值)。

toHtmlDict : Dict Int Int -> Html Msg
toHtmlDict dict =
    div [] (List.map viewDictEntry (Dict.toList dict))

viewDictEntry : (Int, Int) -> Html Msg
viewDictEntry (key, value) =
    li [] [ text (String.fromInt key), text " = ", text (String.fromInt value) ]

Dict.map 是一个函数,它将使用新值转换 Dict 和 return 另一个 Dict 的值。这不是你想要的,但无论如何让我们尝试理解它的类型,因为它是一种有用的学习经验。

Dict.map

一个Dict的完整类型是Dick k v,意味着类型变量分别对应于它的键和值的类型。 Dict.map,根据 the documentation,类型为 (k -> a -> b) -> Dict k a -> Dict k b。作为它的第一个参数,它采用两个参数的函数,ka,并且 return 是一个 bmap 的第二个参数是 Dict k a,return 是 Dict k b

我们可以看到 k 在输入和 return Dict 中是相同的,这意味着它的类型将保持不变,但是类型变量为值不同,输入中的 a 和 return Dict 中的 b。该函数同样将 a 作为其输入之一,以及 k 和 return 以及 b。因此,对于输入 Dict 中的每个 key-value 对,将使用其键 'k' 和值 'a' 调用映射函数,并且预计 return一个b值。

对于您所拥有的 Dict Int Intkv 都是 Int。如果我们将它们替换为 Dict.map 的类型,我们将得到 (Int -> Int -> b) -> Dict Int Int -> Dict Int b。我们还不知道 b 是什么,因为这将由我们传递给它的函数决定,但我们至少可以看到该函数需要两个 Int 个参数。

与此同时,您给它的函数 toLiDict 具有类型 Dict Int Int -> Html msg,它接受一个不是 Int 的参数。这就是错误消息笨拙地试图传达的内容。我们可以重写 toLiDict 以符合 Dict.map 的期望,作为一个函数 Int -> Int -> Html msg,但是那会有 Dict.map return 和 Dict Int (Html msg),这不是你想要的。所以让我们放弃它。

一般来说,map 函数通常会在不改变集合类型的情况下转换集合的元素。

Dict.foldl

如果您想要将集合的元素完全转换成其他东西,并且没有更具体的东西可以使用,"fold" 通常是正确的工具。 Dict 提供 foldlfoldr,它们基本上做同样的事情,但顺序不同,foldl 遍历 "left" 和 [=53= 中的元素] 来自 "right"。如果顺序无关紧要,请使用 foldl 因为它更有效率。

Dict.foldl 的类型签名是 (k -> v -> b -> b) -> b -> Dict k v -> b。也就是说,转换函数现在采用三个参数,键 k、值 v 和一个我们称之为累加器的 b,以及 return 一个bfoldl 本身也有一个额外的参数,同样是一个 b,它将是传递给转换函数的初始 b 值。第三个参数是 Dict,return 值又是 b.

对于输入 Dict 中的每个 key-value 对,foldl 将像 map 一样,使用其键和值调用转换函数。但它也提供了一个 b 最初是传递给 foldl 本身的 b 值,但对于后续迭代将是 b 值 returned 从变换函数。以这种方式 "accumulator" 累积 return 值。

让我们重写您的代码以使用 Dict.foldl 代替:

toHtmlDict : Dict Int Int -> Html msg
toHtmlDict dict =
  div [] (Dict.foldl toLiDict [] dict)

toLiDict : Int -> Int -> List (Html msg) -> List (Html msg)
toLiDict k v acc = 
  div [] [ text "What here?" ] :: acc

此处,toHtmlDict 基本保持不变,但使用 Dict.foldl 而不是 Dict.map 并为其提供一个空列表的初始值,[]

toLiDict 看到更大的变化。它的类型已更改为 Int -> Int -> List (Html msg) -> List (Html msg),意思是它接受参数:键和值,它们都是 Int,累加器是 List (Html msg),[=138] =]值。

但实现几乎没有变化。它不是直接 returning 一个元素,而是使用 :: acc.

附加到累加器

仅此而已。正如预期的那样,折叠的结果是 Html 元素的累积列表。如果你把上面的代码放到你的代码中,它就会工作。

Dict.values 和 Dict.toList

最后,我之前提到过,如果没有更合适的专用函数,foldl 是一个不错的选择。由于您想要的最终结果是一个列表,可能是 Dict.valuesDict.toList,正如@bdukes 所建议的那样。这些不如折叠有效,因为您将遍历元素两次,一次转换为列表,然后映射它们,但这在实践中很少重要。专用函数也更具描述性,可以更好地记录您的意图,因此请尽可能使用它们。