我正在使用Haskell reflection包,我遇到了一个我不完全理解的类型错误。首先,我尝试编写以下函数,很高兴地进行了类型检查:
{-# LANGUAGE TypeApplications #-}
reifyGood :: (forall s. Reifies s a => Proxy a) -> ()
reifyGood p = reify undefined (\(_ :: Proxy t) -> p @t `seq` ())
有趣的是,这个略有不同的程序不类型检查:
reifyBad :: (forall s. Reifies s s => Proxy s) -> ()
reifyBad p = reify undefined (\(_ :: Proxy t) -> p @t `seq` ())
• Could not deduce (Reifies s s) arising from a use of ‘p’
from the context: Reifies s a0
bound by a type expected by the context:
Reifies s a0 => Proxy s -> ()
• In the first argument of ‘seq’, namely ‘p @t’
In the expression: p @t `seq` ()
In the second argument of ‘reify’, namely
‘(\ (_ :: Proxy t) -> p @t `seq` ())’
表达式是相同的,但请注意类型签名之间的区别:
reifyGood :: (forall s. Reifies s a => Proxy a) -> ()
reifyBad :: (forall s. Reifies s s => Proxy s) -> ()
我觉得这很好奇。乍一看,这是无效的,因为在第二个例子中,skolem s
将逃避其范围。然而,事实并非如此 - 错误消息从未提及skolem逃脱,与此略有不同的程序形成鲜明对比:
reifyBad' :: (forall s. Reifies s s => Proxy s) -> ()
reifyBad' p = reify undefined (\(_ :: Proxy t) -> p @t) `seq` ()
• Couldn't match expected type ‘t0’ with actual type ‘Proxy s’
because type variable ‘s’ would escape its scope
This (rigid, skolem) type variable is bound by
a type expected by the context:
Reifies s a0 => Proxy s -> t0
• In the expression: p @t
In the second argument of ‘reify’, namely
‘(\ (_ :: Proxy t) -> p @t)’
In the first argument of ‘seq’, namely
‘reify undefined (\ (_ :: Proxy t) -> p @t)’
所以也许其他东西在这里发挥作用。
检查reify
的类型,有点不清楚:
reify :: forall a r. a -> (forall s. Reifies s a => Proxy s -> r) -> r
a
和s
的范围明显不同,因此GHC不允许它们统一似乎是有道理的。但是,似乎关于Reifies
的函数依赖引入的局部等式约束会引起某种奇怪的行为。有趣的是,这对功能是强大的:
foo :: forall a r. Proxy a -> (forall s. (s ~ a) => Proxy s -> r) -> r
foo _ f = f Proxy
bar :: (forall a. Proxy a) -> ()
bar p = let p' = p in foo p' (\(_ :: Proxy s) -> (p' :: Proxy s) `seq` ())
...但是删除foo
类型签名中的等式约束会使它们无法进行类型检查,从而产生一个skolem转义错误:
• Couldn't match type ‘a0’ with ‘s’
because type variable ‘s’ would escape its scope
This (rigid, skolem) type variable is bound by
a type expected by the context:
Proxy s -> ()
Expected type: Proxy s
Actual type: Proxy a0
• In the first argument of ‘seq’, namely ‘(p' :: Proxy s)’
In the expression: (p' :: Proxy s) `seq` ()
In the second argument of ‘foo’, namely
‘(\ (_ :: Proxy s) -> (p' :: Proxy s) `seq` ())’
此时,我很困惑。我有几个(高度相关的)问题。
为什么reifyBad
首先未能进行类型检查?
更具体地说,为什么会产生缺少实例错误?
此外,这种行为是期望的还是定义明确的,还是仅仅是类型检测器的一个奇怪的边缘情况恰好产生了这个特定的结果?
答案 0 :(得分:6)
reify :: forall a r. a -> (forall s. Reifies s a => Proxy s -> r) -> r
skolem要求基本上表明上述类型中的r
不能依赖于在第二个参数中量化的s
。否则,实际上它将“逃避”其范围,因为reify
返回r
。
在
reifyBad :: (forall s. Reifies s s => Proxy s) -> ()
reifyBad p = reify undefined (\(_ :: Proxy t) -> p @t `seq` ())
我们看到reify
的第二个参数是\(_ :: Proxy t) -> p @t `seq` ()
,因此类型r
将是该函数的返回类型,即()
。由于r ~ ()
不依赖s
,因此此处没有任何转义问题。
但是,p @t
根据p
的类型需要Reifies t t
。
由于reify
会选择t ~ s
,因此约束与Reifies s s
相同。
相反,reify
仅提供Reifies s a
,其中a
是undefined
的类型。
这里的细微之处在于,虽然undefined
可以生成任何类型a
,但类型检查器无法统一s
和a
。这是因为与reify
具有相同类型的函数有权仅接收固定(刚性)类型a
的一个值,然后根据需要选择尽可能多的类型s
。将所有此类s
与单个a
统一起来是错误的,有效地限制s
选择reify
。
相反,在变体中
reifyBad' :: (forall s. Reifies s s => Proxy s) -> ()
reifyBad' p = reify undefined (\(_ :: Proxy t) -> p @t) `seq` ()
此处r
被推断为\(_ :: Proxy t) -> p @t
的返回类型,即Proxy t
,其中t ~ s
。由于r ~ Proxy s
确实取决于s
,因此我们会触发一个skolem错误。