问题描述
如果有人可以帮助修复此错误,将不胜感激。代码是:
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
类型的 catamorphism:maybe :: b -> (a -> b) -> b
,在这里我们可以指定在 Nothing
的情况下要做什么,然后我们通过一个函数在 Just x
与 x
的情况下做什么:
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 Int
是 Int
但可能会失败。我们可以使用 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。