函数类型签名中的右结合性

问题描述 投票:0回答:2

我很难理解 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
haskell types functional-programming currying type-signature
2个回答
8
投票

使用您的符号,在

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
相同。


0
投票

用术语“右关联性”来表示类型签名中默认不可见的括号是相当具有误导性的。

与数学一样,相同的符号在不同领域和研究中表示不同的含义和用法。

显式或不可见的括号并不像算术优先级那样表示结合性。

在算术中,最里面括号的计算首先评估最外面括号的计算。

而在 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”类型的最终结果。

在实践中,经常使用显式括号来表明参数是一个函数,并且我上面展示的柯里化过程默认省略括号。

就是这样,黑客快乐:)

© www.soinside.com 2019 - 2024. All rights reserved.