在其他答案中,我们已经看到如何手动执行统一,以及如何向ghci 提出一些有限的统一问题,当我们不需要连接我们的两种类型中的类型变量想统一。在此答案中,我将展示如何使用现有工具来回答您提出的问题,因为我理解您的意图。
诀窍是使用类型相等约束要求 GHC 统一两种类型,然后将结果公开为元组类型。类型相等约束启动了统一器;统一完成后,我们的元组类型中的类型变量将根据统一期间学到的内容进行简化。
因此,您的问题看起来像这样,例如:
> :set -XTypeFamilies
> :{
| :t undefined -- a dummy implementation we don't actually care about
| :: ((a -> b) -> c) ~ (c -> a -> b) -- the unification problem
| => (a, b, c) -- how we ask our query (what are the values of a, b, and c after unification?)
| :}
<snip -- a copy of the above text>
:: (a, b, a -> b)
由此可知,对于任何类型a和b,我们可以选择a ~ a、b ~ b和c ~ a -> b作为统一问题的解决方案。这里还有一个你可能想知道的问题:统一后,(a -> b) -> c 的简化类型是什么?您可以运行前面的查询,并手动替换 a、b 和 c,或者您可以询问 ghci:
> :t undefined :: ((a -> b) -> c) ~ (c -> a -> b) => (a -> b) -> c
undefined :: ((a -> b) -> c) ~ (c -> a -> b) => (a -> b) -> c
:: (a -> b) -> a -> b
我在这个命令中唯一改变的是“查询”部分。结果告诉我们,统一后(a -> b) -> c变成(a -> b) -> a -> b。请注意,结果类型中的a 和b 不能保证与查询中的a 和b 完全相同——尽管在GHC 中可能总是如此。
另一个值得一提的快速技巧是,您可以使用Proxy 将任意种类的类型变量转换为* 类型以在元组中使用;因此,例如:
> :t undefined :: f a ~ (b -> c) => (a, b, c, f)
<interactive>:1:42:
Expecting one more argument to ‘f’
The fourth argument of a tuple should have kind ‘*’,
but ‘f’ has kind ‘* -> *’
In an expression type signature: f a ~ (b -> c) => (a, b, c, f)
In the expression: undefined :: f a ~ (b -> c) => (a, b, c, f)
> :m + Data.Proxy
> :t undefined :: f a ~ (b -> c) => (a, b, c, Proxy f)
undefined :: f a ~ (b -> c) => (a, b, c, Proxy f)
:: (c, b, c, Proxy ((->) b))