我很难理解 Haskell 中类型签名背后的推理。
1) 因为
->
被认为是右结合的,这是否意味着它可以像 4^(2^(3^2)) 一样理解?
2)使用简单函数的类型签名来表达我的疑问(为了解释我的理解方式,我将使用
a
,b
,c
代替Num a => a
或Int
的):
myAdd :: a -> b -> c
myAdd x y = x+y
这意味着函数接受参数
a
并返回接受 b
的函数,最后返回 c
但它可以重写为:
myAdd :: (a->(b->c))
大多数学习资料都说我们例子中的
c
是函数myAdd的结果,为什么根据括号的使用表明第一个“操作”是b->c
?如何从该类型签名推断执行操作的顺序?
3)我被赋予了一项要执行的任务
map f xs
使用
foldr
、(.)
和 (:)
导致:
map f xs = foldr ((:) . f) [] xs
我对理解上述函数的工作原理没有任何问题,但我们又来了 - 输入签名。如果我们假设名称是统一的,那么类型
a
代表所有合约中的相同类型,那么 c
和 d
似乎可以用 a
和 b
来表示。在数学中,类似的任务可能非常简单,但我如何在 Haskell 中完成它?
map :: (a -> b) -> [a] -> [b]
foldr :: (a -> c -> c) -> c -> [a] -> c
(:) :: b -> ([b] -> [b])
(.) :: (b -> d) -> (a -> b) -> a -> d
使用您的符号,在
myAdd :: a -> b -> c
myAdd x y = x+y
您正确地将类型解释为
a -> (b->c)
,但随后您继续建议 b -> c
中的计算以某种方式首先完成。
当对
myAdd 2 10
进行求值时,函数求值从左到右。
1) 首先评估
myAdd 2
。该评估的结果是将给定数字 y
发送到 2 + y
的函数。实际上,myAdd
的定义与相同
myAdd x = \y -> x+y
2) 然后将最后一个函数应用于参数
10
以产生 2 + 10 = 12
因此,
类型表达式中
->
的右结合性并不对应于函数求值中从右到左的计算顺序。事实上,函数求值是左结合的:myAdd 2 10
与(myAdd 2) 10
相同。
用术语“右关联性”来表示类型签名中默认不可见的括号是相当具有误导性的。
与数学一样,相同的符号在不同领域和研究中表示不同的含义和用法。
显式或不可见的括号并不像算术优先级那样表示结合性。
在算术中,最里面括号的计算首先评估最外面括号的计算。
而在 Haskell 的类型系统中,括号仅表示“->”符号之前的前一个类型的参数返回的函数,即接受“->”符号之后的类型参数的函数然后返回另一个函数,以此类推,直到某个函数返回最终结果。
例如: 假设我们定义了一个函数,它接受类型类“Num”中约束类型的三个参数,对所有给定的数字进行求和。
-- without explicit parentheses
sumThreeNums :: Num a => a -> a -> a -> a
sumThreeNums n1 n2 n3 = n1 + n2 + n3
-- with explicit parentheses
sumThreeNums :: Num a => a -> (a -> (a -> a))
sumThreeNums n1 n2 n3 = n1 + n2 + n3
当从 ghci 中的文件加载带有显式括号的第二个示例时,它已成功编译。
这里类型推理“a -> (a -> (a -> a))”表示函数“sumThreeNums”首先采用类型“a”的第一个参数并返回另一个函数,
即“(a -> (a -> a))”,它采用“a”类型的第二个参数并返回另一个函数,
即“(a -> a)”,它采用“a”类型的第三个参数,并返回“a”类型的最终结果。
在实践中,经常使用显式括号来表明参数是一个函数,并且我上面展示的柯里化过程默认省略括号。
就是这样,黑客快乐:)