在where子句中指定函数类型签名

时间:2018-01-23 11:02:45

标签: haskell ghc recursion-schemes catamorphism

以下函数从列表中实现了旧的过滤器函数 使用recursion-schemes库。

import Data.Functor.Foldable 

catafilter :: (a -> Bool) -> [a] -> [a] 
catafilter p = cata alg 
  where
    -- alg :: ListF a [a] -> [a]
    alg  Nil  =  []
    alg  (Cons x xs) = if (p x) then x : xs else xs

它编译并像catafilter odd [1,2,3,4]这样的简短测试成功。 但是,如果我取消注释alg的类型签名,我会收到以下错误:

src/cata.hs:8:30: error:
    • Couldn't match expected type ‘a’ with actual type ‘a1’
      ‘a1’ is a rigid type variable bound by
        the type signature for:
          alg :: forall a1. ListF a1 [a1] -> [a1]
        at src/cata.hs:6:5-29
      ‘a’ is a rigid type variable bound by
        the type signature for:
          catafilter :: forall a. (a -> Bool) -> [a] -> [a]
        at src/cata.hs:3:1-39
    • In the first argument of ‘p’, namely ‘x’
      In the expression: (p x)
      In the expression: if (p x) then x : xs else xs
    • Relevant bindings include
        xs :: [a1] (bound at src/cata.hs:8:18)
        x :: a1 (bound at src/cata.hs:8:16)
        alg :: ListF a1 [a1] -> [a1] (bound at src/cata.hs:7:5)
        p :: a -> Bool (bound at src/cata.hs:4:12)
        catafilter :: (a -> Bool) -> [a] -> [a] (bound at src/cata.hs:4:1)
  |
8 |     alg  (Cons x xs) = if (p x) then x : xs else xs
  |                              ^

SO问题的答案type-signature-in-a-where-clause 建议使用 ScopedTypeVariables 扩展名。 why-is-it-so-uncommon-to-use-type-signatures-in-where-clauses的最后一个答案中的评论 建议使用 forall 量化。

所以我补充道:

{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE RankNTypes #-}

位于我的模块顶部,并为 alg 尝试了不同类型的签名,例如: alg :: forall a. ListF a [a] -> [a]alg :: forall b. ListF b [b] -> [b]或将 forall 添加到 catalist 类型签名中。没有编译!

问题:为什么我无法为 alg 指定类型签名?

4 个答案:

答案 0 :(得分:9)

没有扩展名,原始的未注释代码

catafilter :: (a -> Bool) -> [a] -> [a] 
catafilter p = cata alg 
  where
    alg :: ListF a [a] -> [a]
    alg  Nil  =  []
    alg  (Cons x xs) = if (p x) then x : xs else xs
启用ScopedTypeVariables后,

等效于显式量化所有类型变量,如下所示:

catafilter :: forall a. (a -> Bool) -> [a] -> [a] 
catafilter p = cata alg 
  where
    alg :: forall a. ListF a [a] -> [a]
    alg  Nil  =  []
    alg  (Cons x xs) = if (p x) then x : xs else xs

这相当于(通过α转换量化变量)

catafilter :: forall a. (a -> Bool) -> [a] -> [a] 
catafilter p = cata alg 
  where
    alg :: forall b. ListF b [b] -> [b]
    alg  Nil  =  []
    alg  (Cons x xs) = if (p x) then x : xs else xs

这会触发类型错误,因为p需要a参数,但p x会传递b参数。

关键是,在启用扩展功能的情况下,以forall b. ...开头的函数承诺可以使用b的任何选项。对于alg而言,此承诺过于强大,a仅适用catafilter catafilter {。}}。

因此,解决方案如下。 a的类型可以承诺与其调用者可能选择的任何forall a.一起使用:我们可以在其中添加alg。 相反,a必须承诺仅使用相同的catafilter a,因此我们重复使用类型变量forall而不添加另一个catafilter :: forall a. (a -> Bool) -> [a] -> [a] catafilter p = cata alg where alg :: ListF a [a] -> [a] alg Nil = [] alg (Cons x xs) = if (p x) then x : xs else xs

ScopedTypeVariables

此编译是因为a看到forall在范围内,并且未在alg中添加隐式ScopedTypeVariables(因为它会在没有扩展的情况下发生)。< / p>

要点:

  • 如果没有forall ...,每个类型注释都有自己的隐式ScopedTypeVariables,量化所有变量。没有注释可以引用其他注释的变量(您可以重用相同的名称,但它不被视为相同的变量)。
  • 使用foo :: forall t. T t u ; foo = def,定义t的处理方式如下:
    • 类型变量def是普遍量化的,并在类型检查def时纳入范围:t中的类型注释可以引用u
    • 类型变量u,如果当前在范围内,则引用外部定义的u
    • 类型变量def,如果不在范围内,则是普遍量化的,但在类型检查re.example时没有带入范围(为了兼容性,这里我们遵循相同的行为而没有扩展名)

答案 1 :(得分:5)

这有效

{-# Language ScopedTypeVariables #-}

import Data.Functor.Foldable 

catafilter :: forall a. (a -> Bool) -> [a] -> [a] 
catafilter p = cata alg
  where
    alg :: ListF a [a] -> [a]
    alg  Nil  =  []
    alg  (Cons x xs) = if (p x) then x : xs else xs

如果省略forall,则这些a完全不同(即使语法上它​​们是相同的)。

由于隐式量化,您的未注释版本将被视为

catafilter :: forall a. (a -> Bool) -> [a] -> [a] 
catafilter p = cata alg
  where
    alg :: forall a1. ListF a1 [a1] -> [a1]
    alg  Nil  =  []
    alg  (Cons x xs) = if (p x) then x : xs else xs

这解释了您的错误消息:

Couldn't match expected type ‘a’ with actual type ‘a1’

谓词(p :: a -> Bool)需要a类型的参数,但它来自x :: a1 Cons x xs :: ListF a1 [a1]

根据错误消息的绑定,查看显式量化的版本是否有意义:

xs         :: [a1] 
x          :: a1
alg        :: ListF a1 [a1] -> [a1] 
p          :: a -> Bool 
catafilter :: (a -> Bool) -> [a] -> [a]

答案 2 :(得分:1)

问题在于alg依赖于外部p,因此alg的类型不能简单地是多态的。

一种简单的解决方法是通过将它们作为函数参数传入来使其独立于任何外部实体,从而使函数可以像预期的那样具有简单的多态类型:

catafilter :: (a -> Bool) -> [a] -> [a]
catafilter = cata . alg
  where
    alg :: (b -> Bool) -> ListF b [b] -> [b]
    alg p Nil  =  []
    alg p (Cons x xs)  =  if (p x) then x : xs else xs

这不需要任何语言扩展。

答案 3 :(得分:0)

这样可以避免所有与forall s

相反的直观反应
{-# LANGUAGE ScopedTypeVariables #-}

import Data.Functor.Foldable 

catafilter :: (a -> Bool) -> [a] -> [a] 
catafilter p = cata alg 
  where
    -- alg :: ListF a [a] -> [a]
    alg  (Nil :: ListF aa [aa])  =  [] :: [aa]
    alg  (Cons x xs) = if (p x) then x : xs else xs

alg Nil等式上,我实际上可以使用tyvar a:我只使用aa来表明它们是不同的绑定。由于aa出现在某个模式上,因此编译器会将其与a的签名中的catafilter统一起来。

您也可以/将类型注释放在alg Cons等式上。

我理解@Jogger关于为什么ghc对forall的位置如此挑剔的困惑;以及forall或许表示RankNTypes的紧张感。