为什么以下的类型检查?

时间:2016-01-27 20:16:46

标签: haskell

为什么以下的类型检查?

cancel x y = x
distribute f g x = f x (g x)
identity = distribute cancel cancel

显然cancel :: a -> b -> adistribute :: (a -> b -> c) -> (a -> b) -> a -> cidentity :: a -> a。现在,distribute cancel :: (a -> b) -> a -> a,但我不明白为什么cancela -> b匹配。

有人能解释一下吗?

2 个答案:

答案 0 :(得分:6)

让所有类型变量区别开来:

identity = distribute cancel1 cancel2
  where distribute :: (a -> b -> c) -> (a -> b) -> a -> c
        cancel1 :: x -> y -> x
        cancel2 :: r -> s -> r

所以,简单地排列我们需要统一的类型来证明分发呼叫检查:

distribute :: (a -> b -> c) -> (a -> b)     -> a -> c
cancel1    ::  x -> y -> x
cancel2    ::                   r -> s -> r

cancel1很明显;我们有:

a ~ x
b ~ y
c ~ x

(〜符号基本上是我们如何在Haskell中编写类型的相等;如果你打开一些扩展,你可以在实际代码中使用它)

让我们替换

中的那些人
distribute :: (x -> y -> x) -> (x -> y)     -> x -> x
cancel1    ::  x -> y -> x
cancel2    ::                   r -> s -> r

对于下一位,我们需要记住箭头是二元运算符。它只需要两个参数:参数类型和结果类型。如果我们有一个带有两个箭头的函数类型,其中一个必须 inside 其他参数类型或结果类型。在类似r -> s -> r的情况下,我们会使用->的正确关联来省略使其显而易见的括号:它真的是r -> (s -> r) 1

那么:

distribute :: (x -> y -> x) -> (x ->  y      ) -> x -> x
cancel1    ::  x -> y -> x
cancel2    ::                   r -> (s -> r)

所以现在我们可以马上宣读:

x ~ r
y ~ s -> r

更多替代:

distribute :: (r -> (s -> r) -> r) -> (r -> (s -> r)) -> r -> r
cancel1    ::  r -> (s -> r) -> r
cancel2    ::                          r -> (s -> r)

所以cancel1忽略的是类型s -> r的函数,这也是cancel2返回的函数。记住f x (g x) distribute的实现,这是有道理的。 cancel1和cancel2都必须用同样的东西调用;然后,cancel1接收调用cancel2作为其第二个参数的结果,它会立即忽略,因此取消第二个参数的类型并不重要,因为它从未真正调用另一个参数(任何接受r作为其第一个参数的函数都可以在这里工作)。这是编写一个什么都不做的函数的精心设计的方法:身份。

1 如果您无法记住->是关联还是左关联,您可能听说过所有Haskell函数只接受一个参数而我们通常会"假&#34 ;通过使用返回其他函数的函数来实现多参数函数。这就是这里发生的事情,以及功能箭头为什么与右边相关联。

答案 1 :(得分:4)

cancel的类型为a -> b -> a,但与a -> (b -> a)相同,因此它是一个输入ab -> a输出的函数类型。

a -> b匹配任何函数类型;在这种情况下,a匹配ab匹配b -> a

distribute cancel (\a b c -> a)同样检查。 Haskell函数是curry,因此总是只有一个输入类型和一个返回类型,但返回类型也可以是一个函数。