统一时Haskell类型合成器推断的类型是什么
类型c -> a -> b
和(a -> b) -> c
?
有人可以解释我如何解决它?
谢谢!
答案 0 :(得分:14)
这似乎是某种运动/家庭作业,所以我不会破坏一切,但首先给你一些提示:
c -> a -> b
实际上是c -> (a -> b)
c -> (a -> b)
(a -> b) -> c
,即:
c
与a -> b
(第一部分)a -> b
与c
(第二部分)现在可以做什么(试图摆脱c
;))?
PS:我假设你希望那些类型a
,b
,...是相同的
答案 1 :(得分:8)
在其他答案中,我们已经了解了如何手动执行统一,以及当我们不需要在两者中连接类型变量时如何询问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))
答案 2 :(得分:6)
你可以问ghci
:t [undefined :: c -> a -> b, undefined :: (a -> b) -> c]
需要统一类型以确定列表元素的类型。我们可以通过这种方式统一任意数量的类型;甚至0,试试吧!
c -> a -> b
左侧的类型变量与a -> b -> c
右侧的类型变量不同。 GHC将重命名类型变量以使它们保持不同,但它会尝试保留原始名称。它通过在类型变量名称的末尾添加数字来实现此目的。此查询的答案包括一些类型变量a
,a1
,b
,b1
,c
和c1
。如果您不希望类型变量不同,则可以忽略添加的数字来读取答案。
如果你确实希望类型变量是不同的,那么告诉ghc正在做什么可能有点棘手,因为你不知道哪些类型变量重命名为什么。在实际编码中,当尝试理解类型错误时,这可能是一个问题。在这两种情况下都有一个简单的解决方案:自己重命名具有不同名称的类型变量,以便ghc不需要重命名它们。
:t [undefined :: c1 -> a1 -> b1, undefined :: (a2 -> b2) -> c2]
我们已经完成了vanilla Haskell可以做的事情,但是你可以让编译器更频繁地使用type equality constraints as described in Daniel Wagner's answer来回答问题。下一节仅描述为什么forall
作用域类型不是一般解决方案。
在阅读本节之前,您应该考虑是否可以统一,适用于所有c
,c -> a -> b
和(a -> b) -> c
。
对于经验丰富的haskeller,看起来你可以通过在forall
范围内引入ScopedTypeVariables扩展名来保持类型变量不同。我不知道在ghci中执行此操作的简单方法,但是以下带有hole †的snipet要求编译器统一a -> b
和a -> b
。
{-# LANGUAGE ScopedTypeVariables #-}
example1 :: forall a b. ()
example1 = (undefined :: _) [undefined :: a -> b, undefined :: a -> b]
输出似乎告诉我们列表是a -> b
。
Found hole `_' with type: [a -> b] -> ()
如果我们尝试将此问题用于示例问题,则无效。
example2 :: forall a b c. ()
example2 = (undefined :: _) [undefined :: c -> a -> b, undefined :: (a -> b) -> c]
编译器礼貌地告诉我们为什么†
Couldn't match type `c' with `a -> b'
对于所有类型c
,c
都是函数,这是不正确的。一些不是函数的示例类型包括Int
,Bool
和IO a
。
†在询问洞中的类型是什么时,我使用(undefined :: _)
代替_
。如果您只是使用_
ghc,请不要键入检查所有表达式。编译器可能会让您相信当一个洞实际上是不可能的时候可以填充一个洞。在example2
的输出中,还有以下非常误导的行
Found hole `_' with type: [c -> a -> b] -> ()