我对Haskell和函数式编程一般都是新手,我无法弄清楚为什么我一直得到“约束中的非类型变量参数:Num [a](使用FlexibleContexts来允许这个)”错误
我有这个函数用于按键在字典中查找值:
lookup1 "b" [("a", "aa"), ("b", "bb"), ("c", "cc")]
它与char类型工作正常,例如,lookup1 2 [(1, 11), (2, 22), (3, 33)]
返回“bb”。但是当我尝试使用数字类型时,例如* Non type-variable argument in the constraint: Num [a]
(Use FlexibleContexts to permit this)
* When checking the inferred type
it :: forall a. Num [a] => [a]
我得到了这个:
ghci
奇怪的是,如果我在 lookup_duplicates x [] = []
lookup_duplicates x (h:t) = if x == fst h then (snd h):(lookup_duplicates x t) else lookup_duplicates x t
lookup1 x (h:t) = let (a:b) = lookup_duplicates x (h:t) in a
中定义相同的函数,它可以同时使用字符和数字。
由于我还需要从字典中返回所有值的函数,其中字典可以包含重复的键,我尝试了这种解决方法:
lookup_duplicates
从 find_all_values [] (h:t) = []
find_all_values (g:a) (h:t) = let found = (lookup1 g (h:t)) in (if found == [] then find_all_values a (h:t) else found:(find_all_values a (h:t)))
获取第一个值并且它确实正常工作。但现在我有了这个函数,可以在字典中查找给定键的所有值:
* Non type-variable argument in the constraint: Num [a1]
(Use FlexibleContexts to permit this)
* When checking the inferred type
it :: forall a1. (Num [a1], Eq a1) => [[a1]]
对于char类型也很好用,但是数字类型我得到了这个:
DateFormatSymbols
有人可以指导我走向正确的方向吗?
答案 0 :(得分:3)
看看
lookup1 x [] = []
lookup1 x (h:t) = if x == fst h then snd h else lookup1 x t
它的类型是什么?第一个等式表示我们采用两个参数,其中第一个是未知类型,第二个是未知类型的列表,我们将返回另一个未知类型的列表:
lookup1 :: _a -> [_b] -> [_c] -- Not fully solved
在第二个等式中,当你在第二个参数的头部使用fst
和snd
时,你强制第二个参数成为对的列表
lookup1 :: _a -> [(_b, _d)] -> [_c]
由于您的功能可以评估为snd h
,因此我们知道_d
必须为[_c]
。
lookup1 :: _a -> [(_b, [_c])] -> [_c]
并且由于您在第一个参数和(==)
之间检查fst h
,我们知道_a ~ _b
和Eq _a
lookup1 :: Eq _a => _a -> [(_a, [_c])] -> [_c]
这是我们拥有的所有信息,因此我们说我们已经完全解决了这种类型并用普遍量化的类型替换了所有剩余的变量:
lookup1 :: Eq a => a -> [(a, [b])] -> [b]
如果你在GHCi中问:type lookup1
,它会告诉你同样的事情。这是错误的,因为你只能用它来查找事物的列表,当你应该能够查找你想要的任何东西时!具体来说,此功能将查找目标列表,或者,如果找不到,则评估为[]
。另外,你说它适用于Char
,并举例
lookup1 "b" [("a", "aa"), ("b", "bb"), ("c", "cc")]
但那不是Char
!这是[Char]
(又名String
)! String
是""
中包含的零个或多个字符; Char
只是''
中包含的一个字符。
另外,如果你试过
lookup1 'b' [('a', "a")]
你得到""
,并且这不是一个明智的输出,因为你不知道是否找到"b"
并恰好指向{ {1}},或者它根本就没有,我们只是默认。
当你尝试
时""
每个数字文字都以类型lookup1 2 [(1, 11), (2, 22), (3, 33)]
开头(Haskell数字文字是多态的)。 Num _a => _a
的类型强制每对的第二个元素也是某个列表,因此lookup1
,11
和22
采用类型{{1} },如果没有33
语言扩展名,则不允许这样做(无论如何都是废话)。
正确的类型是
Num [a] => [a]
现在,FlexibleContexts
(驼峰是惯例)应该具有类型
lookup1 :: Eq a => a -> [(a, b)] -> Maybe b
lookup1 _ [] = _ -- Exercise for reader
lookup1 x (e:es) = _
这对你写的内容是正确的,但现在lookupDuplicates
是坏的:
lookupDuplicates :: Eq a => a -> [(a, b)] -> [b]
如果你给它一个空列表怎么办?您将尝试将其与lookup1
匹配,这将失败。更糟糕的是,你只需将列表拆开再重新放回去。它应该只是
lookup1 x (h:t) = let (a:b) = lookupDuplicates x (h:t) in a
然而,这仍然是不可取的。如果你在任何地方找不到你的条目,你就会抛出一个运行时错误(除非你在h:t
monad中,否则你无法捕获它)。你想使用另一个monad,可能是lookup1 x es = head $ lookupDuplicates x es
-- Read: First result of looking up x in es
:
IO
Maybe
库提供了在lookup1 :: Eq a => a -> [(a, b)] -> Maybe b
lookup1 x es = case lookupDuplicates x es of
[] -> _ -- Exercise for reader
(e:_) -> _
中工作的列表函数的版本,而不是部分函数,因此,如果您安装了该函数,则可以说
listsafe
现在,转到Maybe
import qualified Data.List.Safe as LS
lookup1 x es = LS.head $ lookupDuplicates x es
你可以更清楚地写出来:
findAllValues
如果您使用
findAllValues [] (h:t) = []
findAllValues (g:a) (h:t) = let found = (lookup1 g (h:t))
in if found == []
then findAllValues a (h:t)
else found:(findAllValues a (h:t))
上面的版本,那么最常见的import Data.Maybe
findAllValues :: Eq a => [a] -> [(a, b)] -> [b]
findAllValues keys dict = catMaybes $ flip lookup1 dict <$> keys
-- Go over the list of keys with (flip lookup1 dict), then remove the Maybes
-- (flip lookup1 dict) is a function that takes a key and looks it up in dict
-- (<$>) is the infix version of fmap which is the general version of map
-- catMaybes :: [Maybe a] -> [a]
-- Nothings stop existing and the Just wrappings evaporate
类型实际上是import qualified Data.List.Safe as LS
lookup1 x es = LS.head $ lookupDuplicates x es
,这意味着lookup
简化为:
(MonadThrow m, Eq a) => a -> [(a, b)] -> m b
因为findAllValues
。 (失败的findAllValues keys dict = concat $ flip lookup1 dict <$> keys
&#34;神奇地&#34;在某些情况下返回MonadThrow []
,在其他情况下返回lookup1
,在其他情况下返回Nothing
,所有这些都取决于类型。)< / p>
最后注意事项:始终首先考虑您的类型签名并将其写下来(至少对于顶级定义)。它确实可以帮助您掌握应该做的事情。
答案 1 :(得分:1)
您的第一个问题是,当找不到某个项目时,您无法从lookup1
返回正确的值。您不能只返回一个空列表,因为在其他情况下您不必返回列表(这取决于元组中找到的值的类型)。相反,您需要返回一些Maybe
类型的值:
lookup1 :: Eq a => a -> [(a, b)] -> Maybe b
lookup1 x [] = Nothing
lookup1 x ((k,v):t) | x == k = Just v
| otherwise = lookup1 x t
或者,您可以保证返回列表,其中包含零个值或一个值。但这并不理想,因为类型并不能保证只返回空值或单值。
lookup1 :: Eq a => a -> [(a, b)] -> [b]
lookup1 x [] = []
lookup1 x ((k,v):t) | x == k = [v]
| otherwise = lookup1 x t
您的代码适用于lookup1 "b" [("a", "aa"), ("b", "bb"), ("c", "cc")]
的原因是您使用的是String
个值,String
只是[Char]
的别名; []
是该实例中的有效基本案例。