下面描述的所有实验都是用GHC 8.0.1完成的。
这个问题是对RankNTypes with type aliases confusion的跟进。这个问题归结为像这样的函数类型......
{-# LANGUAGE RankNTypes #-}
sleight1 :: a -> (Num a => [a]) -> a
sleight1 x (y:_) = x + y
...被类型检查器拒绝...
ThinAir.hs:4:13: error:
* No instance for (Num a) arising from a pattern
Possible fix:
add (Num a) to the context of
the type signature for:
sleight1 :: a -> (Num a => [a]) -> a
* In the pattern: y : _
In an equation for `sleight1': sleight1 x (y : _) = x + y
...因为排名较高的约束Num a
cannot be moved outside of the type of the second argument(如果我们改为a -> a -> (Num a => [a])
则可能)。既然如此,我们最终会尝试为已经量化的变量添加更高级别的约束,即:
sleight1 :: forall a. a -> (Num a => [a]) -> a
完成这个重演后,我们可能会尝试简化一下这个例子。让我们用不需要(+)
的东西替换Num
,并将有问题的参数的类型与结果的类型分开:
sleight2 :: a -> (Num b => b) -> a
sleight2 x y = const x y
这不像之前那样有效(除了错误信息稍有变化):
ThinAir.hs:7:24: error:
* No instance for (Num b) arising from a use of `y'
Possible fix:
add (Num b) to the context of
the type signature for:
sleight2 :: a -> (Num b => b) -> a
* In the second argument of `const', namely `y'
In the expression: const x y
In an equation for `sleight2': sleight2 x y = const x y
Failed, modules loaded: none.
然而,在这里使用const
可能是不必要的,所以我们可能会尝试自己编写实现:
sleight3 :: a -> (Num b => b) -> a
sleight3 x y = x
令人惊讶的是,这确实有效!
Prelude> :r
[1 of 1] Compiling Main ( ThinAir.hs, interpreted )
Ok, modules loaded: Main.
*Main> :t sleight3
sleight3 :: a -> (Num b => b) -> a
*Main> sleight3 1 2
1
更奇怪的是,第二个论点似乎没有实际Num
约束:
*Main> sleight3 1 "wat"
1
我不太确定如何让它变得清晰可辨。或许我们可以这样说,就像我们可以在我们永远不会评估它的时候一样玩undefined
,只要不在右手的任何地方用于统一,一个不可满足的约束就可以保持在一个类型中。侧。然而,这感觉就像一个相当弱的类比,特别是考虑到我们通常理解的非严格性是一个涉及价值观而非类型的概念。此外,这让我们无法理解世界String
如何与Num b => b
结合 - 假设这样的事情真的发生了,我完全不确定。那么,当一个约束似乎以这种方式消失时,对于正在发生的事情的准确描述是什么呢?
答案 0 :(得分:13)
哦,它变得更奇怪了:
Prelude> sleight3 1 ("wat"+"man")
1
Prelude Data.Void> sleight3 1 (37 :: Void)
1
请参阅 对该参数的实际Num
约束。只是,因为(正如chi已经评论过)b
处于协变位置,这不是您在调用sleight3
时必须提供的约束。相反,您可以选择任何类型b
,然后无论它是什么,sleight3
都会为它提供Num
个实例!
sleight3
不能为字符串提供这样的num实例,绝大多数都不适用于Void
。但它实际上 回想一下,约束多态值本质上只是字典参数的一个函数。 sleight3
只是承诺在实际使用y
之前提供这样的字典,但是它不会以任何方式使用y
,所以它很好
它与这样的函数基本相同:
defiant :: (Void -> Int) -> String
defiant f = "Haha"
同样,参数函数显然不可能产生Int
,因为没有Void
值来评估它。但这也不需要,因为f
被忽略了!
相比之下,sleight2 x y = const x y
确实有点使用y
:const
的第二个参数只是一个rank-0类型,因此编译器需要在此时解析任何所需的字典。即使const
最终也会抛出y
,它仍然“强制”足够的这个值,以明显它没有很好地输入。