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

问题描述

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

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,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 返回 Maybe b,而不是匹配的 b 列表。如果在元组列表中没有找到键,则返回 Nothing,否则返回 Just somecity

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

既然我们有了 Maybe City,我们必须决定在 Nothing 的情况下该怎么做,例如,在这种情况下我们可以返回 -1。为此,我们可以使用 Maybe 类型的 catamorphismmaybe :: b -> (a -> b) -> b,在这里我们可以指定在 Nothing 的情况下要做什么,然后我们通过一个函数在 Just xx 的情况下做什么:

import Data.Maybe(maybe)

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

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

import Data.Maybe(maybe)

getCityPopulation :: [City] -> Name -> Int -> Int
getCityPopulation cs nameIn yearIn = maybe (-1) ((!! yearIn) . snd) (lookup nameIn cs)

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

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

然后我们得到例如:

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

(!!) 函数也不是很安全,因为 yearIn 的值可能小于 0 或大于或等于列表的长度的人口。我将其留作练习以实现更安全的 varint。

相关问答

错误1:Request method ‘DELETE‘ not supported 错误还原:...
错误1:启动docker镜像时报错:Error response from daemon:...
错误1:private field ‘xxx‘ is never assigned 按Alt...
报错如下,通过源不能下载,最后警告pip需升级版本 Requirem...