以下函数从列表中实现了旧的过滤器函数 使用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 指定类型签名?
答案 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
的紧张感。