如何修复此 Haskell 查找函数类型不匹配?

How to fix this Haskell lookup function type mismatch?

如果有人可以帮助修复此错误,我们将不胜感激。代码是:

type Name = String
type Coordinates = (Int, Int)
type Pop = Int
type TotalPop = [Pop]
type City = (Name, (Coordinates, TotalPop))

testData :: [City]
testData = [("New York City", ((1,1), [5, 4, 3, 2])),
           ("Washingotn DC", ((3,3), [3, 2, 1, 1])),
           ("Los Angeles", ((2,2), [7, 7, 7, 5]))]

getCityPopulation :: [City] -> Name -> Int -> Int
getCityPopulation cs nameIn yearIn = head ([ z !! (yearIn - 1) | (x,z) <- lookup nameIn cs])

预期行为

getCityPopulation testData "New York City" 2
>>> 4

实际行为:

    • Couldn't match expected type ‘[(a0, [Int])]’
                  with actual type ‘Maybe (Coordinates, TotalPop)’
    • In the expression: lookup nameIn cs
      In a stmt of a list comprehension: (x, z) <- lookup nameIn cs
      In the first argument of ‘head’, namely
        ‘([z !! (yearIn - 1) | (x, z) <- lookup nameIn cs])’
   |
55 | getCityPopulation cs nameIn yearIn = head ([ z !! (yearIn - 1) | (x,z) <- lookup nameIn cs])
   |                                                                           ^^^^^^^^^^^^^^^^
getCityPopulation :: [City] -> Name -> Int -> Int
getCityPopulation [] nameIn yearIn = error "Can't be empty"
getCityPopulation cs nameIn yearIn = 
   head ([ z !! (yearIn - 1) | (x,z) <- maybeToList $ lookup nameIn cs])

完成任务。这使用

maybeToList :: Maybe a -> [a]   -- Defined in `Data.Maybe'

需要转换

的输出
lookup :: Eq a => a -> [(a, b)] -> Maybe b

当然该代码实际上应该是

   head ([ z !! (yearIn - 1) | (x,z) <- maybeToList $ lookup nameIn cs])
 ==
   case (lookup nameIn cs) of 
      Just (x,z) ->  z !! (yearIn - 1)
      Nothing    ->  error "couldn't find it"

这样

> getCityPopulation testData "New York City" 2
4

> getCityPopulation testData "Las Vegas" 2
*** Exception: couldn't find it

但是编写这样一个可能会失败的代码也是不对的。最好再次将结果包装在 Maybe:

getCityPopulation :: [City] -> Name -> Int -> Maybe Int
getCityPopulation [] nameIn yearIn = error "Can't be empty"
getCityPopulation cs nameIn yearIn = 
   case (lookup nameIn cs) of 
      Just (x,z) ->  Just $ z !! (yearIn - 1)
      Nothing    ->  Nothing

然后我们有

> getCityPopulation testData "New York City" 2
Just 4

> getCityPopulation testData "Las Vegas" 2
Nothing

现在,更改输出类型后,我们实际上可以返回到原始代码 -- 几乎:

getCityPopulation :: [City] -> Name -> Int -> Maybe Int
getCityPopulation [] nameIn yearIn = error "Can't be empty"
getCityPopulation cs nameIn yearIn = 
   [ z !! (yearIn - 1) | (x,z) <- lookup nameIn cs]

你问这是什么魔法?它被称为 MonadComprehensions。您可以在 GHCi 提示符下使用

打开它
> :set -XMonadComprehensions

或通过包含

{-# LANGUAGE MonadComprehensions #-}

pragma 在源文件的顶部。

尽管您的代码存在 no magic fix 可能存在的越界访问问题,但未检查就使用 !!。或者有没有?

getCityPopulation :: [City] -> Name -> Int -> Maybe Int
getCityPopulation [] nameIn yearIn = error "Can't be empty"
getCityPopulation cs nameIn yearIn = 
   [ e | (_,z) <- lookup nameIn cs, 
         e <- listToMaybe $ drop (yearIn-1) z ]

lookup :: Eq a => a -> [(a, b)] -> Maybe b return 一个 Maybe b,而不是匹配的 b 列表。如果它在元组列表中找不到键,Nothing 是 returned,否则它是 Just somecity.

getCityPopulation :: [City] -> Name -> Int -> Int
getCityPopulation cs nameIn yearIn = … (lookup nameIn cs)

现在我们有了 Maybe City,我们必须决定在 Nothing 的情况下要做什么,例如 return -1 在这种情况下.为此,我们可以使用 Maybe 类型的 catamorphismmaybe :: b -> (a -> b) -> b,因此我们可以指定在 Nothing 的情况下要做什么,然后我们传递一个函数,在 Just xx:

的情况下该怎么做
import Data.Maybe(maybe)

getCityPopulation :: [City] -> Name -> Int -> Int
getCityPopulation cs nameIn yearIn = <b>maybe (-1)</b> (…) (lookup nameIn cs)

现在是一个City,我们需要检索年份,我们可以使用snd :: (a, b) -> b获取人口列表,然后查找相关人口, 所以:

import Data.Maybe(maybe)

getCityPopulation :: [City] -> Name -> Int -> Int
getCityPopulation cs nameIn yearIn = maybe (-1) (<b>(!! yearIn) . snd</b>) (lookup nameIn cs)
然而,

返回一个简单的 Int 对于可能会失败的计算来说并不是很“Haskellish”,通常在这种情况下 return 类型是Maybe Int 如果结果应该是 Int 但可能会失败。我们可以使用 fmap :: Functor f => (a -> b) -> f a -> f b 映射包装在 Just 数据构造函数中的值,或者如果我们映射 Nothing,则使用 Nothing,那么函数看起来像:

getCityPopulation :: [City] -> Name -> Int -> <b>Maybe Int</b>
getCityPopulation cs nameIn yearIn = <b>fmap</b> ((!! yearIn) . snd) (lookup nameIn cs)

然后我们得到例如:

Prelude> getCityPopulation testData "New York" 2
Nothing
Prelude> getCityPopulation testData "New York City" 2
Just 3

(!!) 函数也不是很安全,因为 yearIn 的值可能小于 0 或大于或等于人口名单。我把它留作练习,以实现更安全的 varint。