【问题标题】:How to fix this Haskell lookup function type mismatch?如何解决这个 Haskell 查找函数类型不匹配的问题?
【发布时间】:2021-05-02 10:18:31
【问题描述】:

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

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])
   |                                                                           ^^^^^^^^^^^^^^^^

【问题讨论】:

  • 查找返回Maybe City,因为这可能会失败。
  • @WillemVanOnsem 获得预期输出的解决方案是什么?
  • 如果您输入getCityPopulation testData "New York City" 2000,预期的输出是什么? getCityPopulation testData "Boston" 2呢?
  • @n.'pronouns'm。它应该说类似Data unavailable, check inputs again

标签: function haskell lookup type-mismatch algebraic-data-types


【解决方案1】:
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 ]

【讨论】:

  • 我对 Haskell 还是很陌生,$ 有什么作用?
  • f $ x === f ( x )
【解决方案2】:

lookup :: Eq a =&gt; a -&gt; [(a, b)] -&gt; Maybe b 返回一个Maybe b,而不是匹配的bs 列表。如果在元组列表中没有找到键,则返回Nothing,否则返回Just somecity

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

现在我们有了Maybe City,我们必须决定在Nothing 的情况下要做什么,例如在这种情况下我们可以返回-1。为此,我们可以使用Maybe 类型的catamorphismmaybe :: b -&gt; (a -&gt; b) -&gt; 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) -&gt; b获取人口列表,然后查找相关人口,所以:

import Data.Maybe(maybe)

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

返回一个简单的Int 然而对于一个可能失败的计算来说并不是很“Haskellish”,通常在这种情况下,如果结果应该是一个@987654346,那么返回类型是一个Maybe Int @ 但可能会失败。我们可以使用fmap :: Functor f =&gt; (a -&gt; b) -&gt; f a -&gt; 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。

【讨论】:

    猜你喜欢
    • 2021-09-15
    • 1970-01-01
    • 2019-01-06
    • 2017-01-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-05-04
    相关资源
    最近更新 更多