为什么以下的类型检查?
cancel x y = x
distribute f g x = f x (g x)
identity = distribute cancel cancel
显然cancel :: a -> b -> a
,distribute :: (a -> b -> c) -> (a -> b) -> a -> c
和identity :: a -> a
。现在,distribute cancel :: (a -> b) -> a -> a
,但我不明白为什么cancel
与a -> b
匹配。
有人能解释一下吗?
答案 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)
相同,因此它是一个输入a
和b -> a
输出的函数类型。
a -> b
匹配任何函数类型;在这种情况下,a
匹配a
,b
匹配b -> a
。
distribute cancel (\a b c -> a)
同样检查。 Haskell函数是curry,因此总是只有一个输入类型和一个返回类型,但返回类型也可以是一个函数。