我正在学习SYB和n级类型,并遇到了一个令人困惑的情况,即单态限制。
我编写了一个函数来查找与谓词匹配的最浅条目。我想使用Alternative
来接受类似于谓词的函数,而不是归约函数,然后自己将其转换为通用函数。我决定在let
块中省略类型注释,以了解单态减少将如何影响此实现中的类型:
shallowest :: (Alternative f, Typeable b) => (b -> f a) -> GenericQ (f a)
shallowest p z =
let op = (empty `mkQ` p) in
op z <|> foldl (<|>) empty (gmapQ op z)
这会产生一个错误,表明let
绑定中的歧义会阻止类型检查器解决约束Data a1
。
Error: • Couldn't match type ‘d’ with ‘a1’
‘d’ is a rigid type variable bound by
a type expected by the context:
forall d. Data d => d -> m a
‘a1’ is a rigid type variable bound by
the type signature for:
shallowest :: (b -> m a) -> GenericQ (m a)
(其他类似head (gmapQ op z)
的主体会引起“ let
绑定的含糊不清的明确错误,这沿“由于使用'mkQ'而无法推断(可键入a0)”的思路进行;我也还没有弄清楚为什么上面的表格不行。
当我们在let
的{{1}}块中添加注释时(需要ScopedTypeVariables),类型错误就会消失。
但是,我感到困惑的是,似乎可以推断出op :: GenericQ (f a)
上的Data
约束 :跟随类型在返回类型时进行检查:
op
有什么区别?两种情况都要求shallowest p = let { op = (empty `mkQ` p) } in op
为op
;我看到的唯一区别是,第一个位于参数位置,第二个位于返回位置。
答案 0 :(得分:2)
在第二个摘录中,op
实际上不是多态的。
shallowest p = let { op = (empty `mkQ` p) } in op
这是一个微妙的区别:op
实际上是单态的,但是在开放的上下文中。使用通常的键入判断符号,op
右侧的in
的键入如下所示:
types values
↓ ↓
x, a, f, ...; op :: x -> f a, ... |- op :: x -> f a
↑
monotype (no "forall")
In English: "op has type (x -> f a) in the context consisting of type variables (x, a, f, ...) and values (op :: x -> f a, ...)"
shallowest
通过在顶层进行的 generalization 步骤变为多态。如果在具有类型变量x, a, f, ...
的上下文中,shallowest
的主体具有类型x -> f a
,那么我们可以“关闭上下文”并将类型变量移到{{1 }}。类型派生看起来像这样:
shallowest :: forall x a f. x -> f a
(类型类和统一算法使事情变得更加复杂,但这超出了答案的范围。)
使用多态进行类型检查的主要问题是确定何时应该进行泛化。由于缺少主体类型和不确定性,因此没有通用的解决方案。因此,类型检查器的实现必须做出一些选择。
在Haskell中,泛化发生在以下位置(列表可能并不详尽),这是很自然的选择:
函数定义,即 x, a, f |- (let op = ... in op) :: x -> f a
⸻⸻⸻⸻⸻⸻⸻⸻⸻⸻⸻⸻⸻ (generalization)
|- (let op = .... in op) :: forall x a f. x -> f a
和具有至少一个显式参数的顶级绑定(这里是单态限制);
高级函数的多态参数:如果您有函数let
,则在进行f :: (forall a. w a) -> r
的类型检查时,f x
将概括a
;
,当然,当由显式注释x
指示时。
答案 1 :(得分:0)
初步说明:鉴于此处提供的证据,我认为您正在使用:
type GenericQ r = forall a . Data a => a -> r
from syb和gmapQ :: Data a => (forall d. Data d => d -> u) -> a -> [u]
from Data.Data
。如果我误会了,请告诉我。另外,后面的任何forall
都将明确写入。
在这里,不仅有吸引人的目光。 As Li-yao Xia suggests,这是涉及op
类型的一般化问题。关于您对shallowest
的第一个定义,有三个相关事实:
在归纳之前,op
的推断类型为Data d => d -> f a
。给定Data d
约束,单态约束的规则1(请参阅subsection 4.5.5 of the Report)意味着这种类型的d
无法推广。
在shallowest
的正文中,op
出现在两个位置。第一个是op z
,其中z :: a1
受shallowest
签名的约束和约束。结果是op
的出现并不需要对参数类型进行泛化:就其而言,op
的类型可以为forall f a. a1 -> f a
,在类型变量a1
中是单态的(我从subsection 4.5.4 of the Report获得了此术语)。
不过,其他情况是gmapQ op z
。 gmapQ
具有等级2类型,需要多态参数。既然如此,这种情况需要概括op
的参数类型,如夏丽瑶回答的结尾所述。
#1和#3是矛盾的要求,因此会出现类型错误,可以通过禁用单态性限制或要求op
对带有签名的参数类型为多态来避免。由于#2中描述了op
的另一次出现,该情况被报告为涉及这两次出现的不匹配。
下面是一个更简单的扩展示例,它可能有助于了解正在发生的情况。 (如果要将以下代码段放入GHCi中,除了-XRankNTypes
之外,还应该设置-XMonomorphismRestriction
和-XNoExtendedDefaultRules
才能看到相同的结果。)
这是一个具有等级2类型的函数,它将扮演gmapQ
的角色:
glub :: (forall x. Show x => x -> String) -> String
glub f = f 7
现在让我们尝试一种类似于shallowest
...
foo1 :: forall a. Show a => a -> String
foo1 x = bar x ++ glub bar
where
bar = show
...并且有您的错误:
<interactive>:506:23: error:
• Couldn't match type ‘x’ with ‘a’
‘x’ is a rigid type variable bound by
a type expected by the context:
forall x. Show x => x -> String
at <interactive>:506:18-25
‘a’ is a rigid type variable bound by
the type signature for:
foo1 :: forall a. Show a => a -> String
at <interactive>:505:1-38
Expected type: x -> String
Actual type: a -> String
• In the first argument of ‘glub’, namely ‘bar’
In the second argument of ‘(++)’, namely ‘glub bar’
In the expression: bar x ++ glub bar
• Relevant bindings include
bar :: a -> String (bound at <interactive>:508:3)
x :: a (bound at <interactive>:506:5)
foo1 :: a -> String (bound at <interactive>:506:1)
在bar
的签名应添加通配符的地方添加一个附加错误,该错误提示性更高:
foo2 :: forall a. Show a => a -> String
foo2 x = bar x ++ glub bar
where
bar :: _
bar = show
• Found type wildcard ‘_’ standing for ‘a -> String’
Where: ‘a’ is a rigid type variable bound by
the type signature for:
foo2 :: forall a. Show a => a -> String
at <interactive>:511:1-38
To use the inferred type, enable PartialTypeSignatures
• In the type signature: bar :: _
In an equation for ‘foo2’:
foo2 x
= bar x ++ glub bar
where
bar :: _
bar = show
• Relevant bindings include
x :: a (bound at <interactive>:512:5)
foo2 :: a -> String (bound at <interactive>:512:1)
请注意,如何将通配符“代表a -> String
声明为与a
受foo2
的类型签名约束的独立事实。我相信这对应于我在上面的第2点中提到的类型变量和多态之间的单态区别。
赋予bar
多态类型签名使其可以工作:
foo3 :: forall a. Show a => a -> String
foo3 x = bar x ++ glub bar
where
bar :: forall b. Show b => b -> String
bar = show
使bar的定义也很有意义,通过将其设为a "function binding" rather than a "simple pattern binding"来逃避单态性限制:
foo4 :: forall a. Show a => a -> String
foo4 x = bar x ++ glub bar
where
bar x = show x
为完整起见,值得注意的是,对类型的无约束意味着没有单态性约束:
foo5 :: forall a. Show a => a -> String
foo5 x = bar x ++ glub bar
where
bar = const "bar"
一种相关情况涉及两次使用bar
,但没有等级2功能:
foo6 x y = bar x ++ bar y
where
bar = show
GHC会为foo6
推断哪种类型?
GHCi> :t foo6
foo6 :: Show a => a -> a -> [Char]
参数具有相同的类型,否则将需要bar
的泛化,这需要类型签名(或点满度等):
foo7 x y = bar x ++ bar y
where
bar :: forall a. Show a => a -> String
bar = show
GHCi> :t foo7
foo7 :: (Show a1, Show a2) => a1 -> a2 -> [Char]
由于我还没有提到它,所以这与您的第二个shallowest
类似:
foo8 :: forall a. Show a => a -> String
foo8 x = bar x
where
bar = show
值得强调的是,bar
实际上并未在此处得到概括:它在类型变量a
中是单态的。我们仍然可以通过弄乱foo7
而不是bar
来打破这个例子:
foo9 = bar
where
bar :: _
bar = show
在这种情况下,bar
没有被概括,foo
也没有被概括(现在是无点的,没有签名)。这意味着永远不会解析单态类型变量。根据单态性限制的规则2,它变成了模糊的类型变量:
<interactive>:718:14: error:
• Found type wildcard ‘_’ standing for ‘a0 -> String’
Where: ‘a0’ is an ambiguous type variable
To use the inferred type, enable PartialTypeSignatures
• In the type signature: bar :: _
In an equation for ‘foo9’:
foo9
= bar
where
bar :: _
bar = show
• Relevant bindings include
foo9 :: a0 -> String (bound at <interactive>:716:5)
<interactive>:719:13: error:
• Ambiguous type variable ‘a0’ arising from a use of ‘show’
prevents the constraint ‘(Show a0)’ from being solved.
Relevant bindings include
bar :: a0 -> String (bound at <interactive>:719:7)
foo9 :: a0 -> String (bound at <interactive>:716:5)
Probable fix: use a type annotation to specify what ‘a0’ should be.
These potential instances exist:
instance Show a => Show (ZipList a)
-- Defined in ‘Control.Applicative’
instance Show Constr -- Defined in ‘Data.Data’
instance Show ConstrRep -- Defined in ‘Data.Data’
...plus 64 others
...plus 250 instances involving out-of-scope types
(use -fprint-potential-instances to see them all)
• In the expression: show
In an equation for ‘bar’: bar = show
In an equation for ‘foo9’:
foo9
= bar
where
bar :: _
bar = show
在bar
的定义中向foo9
添加类型签名无济于事–只是更改了报告错误的起点。将bar
更改为没有约束的内容确实可以消除错误,因为它可以归纳bar
和foo
。