试图理解为什么这个函数在 Haskell 中使用 foldr 不起作用

Trying to understanding why this function using foldr in Haskell isnt working

所以我是 Haskell 的新手,正在使用 WikiBooks 学习它。在高阶函数章节中,使用了以下示例。

echoes = foldr (\ x xs -> (replicate x x) ++ xs) []

所以我试了 运行 它,但它给我一个错误如下:

 * Ambiguous type variable `t0' arising from a use of `foldr'
  prevents the constraint `(Foldable t0)' from being solved.
  Relevant bindings include
    echoes :: t0 Int -> [Int] (bound at HavingFun.hs:107:1)
  Probable fix: use a type annotation to specify what `t0' should be.
  These potential instances exist:
    instance Foldable (Either a) -- Defined in `Data.Foldable'
    instance Foldable Maybe -- Defined in `Data.Foldable'
    instance Foldable ((,) a) -- Defined in `Data.Foldable'
    ...plus one other
    ...plus 29 instances involving out-of-scope types
    (use -fprint-potential-instances to see them all)
* In the expression: foldr (\ x xs -> (replicate x x) ++ xs) []
  In an equation for `echoes':
      echoes = foldr (\ x xs -> (replicate x x) ++ xs) []

然后如果我按下面这样写,就可以了。

echoes lis = foldr (\ x xs -> (replicate x x) ++ xs) [] lis

我对此感到困惑,我认为这在某种程度上与函数的自由定义有关? 请澄清这里的问题是什么。 我学习的 link - https://en.wikibooks.org/wiki/Haskell/Lists_III

tl;博士

只是总是写显式类型签名,这样你就可以安全地避免遇到类似的奇怪问题。


这个 曾经有效但现在无效的原因 foldr 以前有签名

foldr :: (a -> b -> b) -> b -> [a] -> b

这是 WikiBooks 的假设,但在较新的 GHC 中它实际上具有更严格的通用签名

foldr :: Foldable t => (a -> b -> b) -> b -> t a -> b

旧版本的一个特例,只需选择t ~ []。他们改变它的原因是您还可以折叠其他容器,例如数组或映射。事实上,在你的代码中

echoes = foldr (\ x xs -> (replicate x x) ++ xs) []

也没有什么要求输入容器是一个列表,所以它实际上可以与签名完美配合

echoes :: Foldable t => t Int -> [Int]

...其中,[Int] -> [Int] 是一个特例,因此该函数可以用作

> echoes [1,2,3]
[1,2,2,3,3,3]

但也作为

> echoes $ Data.Map.fromList [('a',2), ('c',5), ('b',1)]
[2,2,1,5,5,5,5,5]

或者您可以为函数提供 list-specific 签名

echoes' :: [Int] -> [Int]
echoes' = foldr (\x xs -> (replicate x x) ++ xs) []

这在 [1,2,3] 上同样有效,但不能接受 Map

现在的问题是,为什么 GHC 不自己推断这些签名中的任何一个?好吧,如果它必须选择一个,它应该是更通用的 Foldable 版本,因为人们可能需要将它与其他容器一起使用并且不想继续重复 Foldable t => 量词。然而,这与另一个 Haskell 规则相矛盾,即 monomorphism restriction. Because your echoes implementation doesn't explicitly accept any parameters (it only does that point-freely), it is a constant applicative form,并且独立的 CAF 应该具有单态类型,除非明确指定为多态。因此,您 运行 的错误消息是:GHC 确实希望它是单态的,但它没有限制 what 具体 Foldable 容器选择的信息。

有四种解决方法:

  • 正如您所注意到的,通过在范围内显式引入参数,echoes 不再是 CAF,因此 GHC 推断出多态类型:

    echoes'' l = foldr (\x xs -> (replicate x x) ++ xs) [] l
    
    > :t echoes''<br>echoes'' :: Foldable t => t Int -> [Int]
    
  • 通过禁用单态性限制,GHC 将不再关心它是否是 CAF 并且只给它更通用的类型而不管:

    {-# LANGUAGE NoMonomorphismRestriction #-}
    echoes''' = foldr (\x xs -> (replicate x x) ++ xs) []
    
    > :t echoes'''<br>echoes''' :: Foldable t => t Int -> [Int]
    
  • 不鼓励如果你开启-XExtendedDefaultingRules扩展,GHC会自动选择[] 作为 CAF 的具体单态容器:

    {-# LANGUAGE ExtendedDefaultRules #-}
    echoes'''' = foldr (\x xs -> (replicate x x) ++ xs) []
    
    > :t echoes''''<br>echoes'''' :: [Int] -> [Int]
    

    GHCi 默认启用 -XExtendedDefaultingRules,因此如果您在 GHCi 提示符中声明该函数,也会发生这种情况。

  • 强烈推荐 如果您明确指定签名,您和 GHC 都确切地知道其意图并相应地表现,不需要任何特殊的 GHC 扩展。

    echoes :: Foldable t => t Int -> [Int]
    echoes = foldr (\x xs -> (replicate x x) ++ xs) []
    
    echoes' :: [Int] -> [Int]
    echoes' = foldr (\x xs -> (replicate x x) ++ xs) []
    
    > :t echoes<br>echoes :: Foldable t => t Int -> [Int]<br>> :t echoes'<br>echoes' :: [Int] -> [Int]