Haskell 运算符与函数优先级

48

我正在尝试自己验证Haskell中的操作符和函数优先级。例如,以下代码:

list = map foo $ xs

可以重写为

list = (map foo) $ (xs)

最终会

list = map foo xs

我的问题曾经是,为什么第一个表述不能重写为

list = (map foo $) xs

因为函数优先级始终高于运算符优先级,但我认为我已经找到了答案:运算符简单地不允许作为函数的参数(当然,如果您用括号将它们括起来,则可以)。是这样吗?如果是这样,我觉得很奇怪,在RWH或Learn you a Haskell或其他我搜索过的地方中都没有提到这个机制/规则。如果您知道有规定的地方,请提供链接。

-- 编辑:感谢您的快速回答。我想我的困惑来自于认为运算符文字会以某种方式求值为可由函数作为参数消耗的内容。记得将中缀运算符机械地翻译为前缀函数对我很有帮助。将其应用于第一个公式的结果为

($) (map foo) (xs)

毫无疑问,($)是消耗函数,并且由于两种表达式是等价的,因此在第一个表达式中的$文字不能被map函数消耗。


1
请注意,您可以将 $ 分成如下部分:((map foo) $) xs。当然,这有点傻,因为它等同于 (map foo) $ xs,而 (map foo) $ xs 又等同于 map foo $ xs - Gabriel L.
(map foo $) xs = (map foo $ xs) = map foo xs = ($) (map foo) xs 可以翻译为:(map foo $)xs =(map foo $ xs)= map foo xs =($)(map foo)xs - Will Ness
5个回答

41

首先,应用程序(空格)是最高优先级的“运算符”。

其次,在Haskell中,操作符和函数之间实际上没有区别,除了操作符默认是中缀符号,而函数则不是。您可以使用反引号将函数转换为中缀符号。

2 `f` x

并使用括号将运算符转换为前缀形式:

(+) 2 3

所以,你的问题有点混淆了。

现在,特定的函数和运算符将具有已声明的优先级,你可以在GHCi中使用“:info”找到它们:

Prelude> :info ($)
($) :: (a -> b) -> a -> b   -- Defined in GHC.Base

infixr 0 $

Prelude> :info (+)

class (Eq a, Show a) => Num a where
  (+) :: a -> a -> a

infixl 6 +

展示运算符的优先级和结合性。


谢谢。阅读了您的回答后,我理解您认为我的问题有些混淆,我理解“函数优先级”的提法是错误的,函数本身并没有任何优先级,而是可以被视为应用操作符(空格)的参数。这样对吗?我还不太清楚中缀操作符如何适应这一点。我已经编辑了我的问题以反映我最好的思考方式。这个理解正确吗? - Boris
不完全正确。在Haskell中,函数和运算符是无法区分的。它们都可以由用户指定不同的优先级。 - Don Stewart
4
好的。我仍在努力理解导致list = (map foo) $ (xs)而不是list = (map foo $) xs的一般规则,我知道这是一个部分应用。可以这样说,在决定给定表达式的哪些参数属于哪些函数和运算符时,函数从左侧开始消耗所有可用的参数,直到它们遇到一个操作符。之后,根据它们的优先级和结合性,运算符按顺序使用它们的参数(这有点模糊,但我希望你能理解)。如果这似乎很明显,那我很抱歉,但对我来说从来没有真正清楚过。 - Boris

28
除了其他答案提供的信息外,还要注意不同的运算符可以具有对其他运算符的不同优先级,以及左/右或非相关的属性。 您可以在Haskell 98 Report fixity section 中找到 Prelude 运算符的这些属性。
+--------+----------------------+-----------------------+-------------------+
| 优先级 |   左关联运算符      |    非关联运算符        | 右关联运算符      |
+--------+----------------------+-----------------------+-------------------+
| 9      | !!                   |                       | .                 |
| 8      |                      |                       | ^, ^^, **         |
| 7      | *, /, `div`,         |                       |                   |
|        | `mod`, `rem`, `quot` |                       |                   |
| 6      | +, -                 |                       |                   |
| 5      |                      |                       | :, ++             |
| 4      |                      | ==, /=, <, <=, >, >=, |                   |
|        |                      | `elem`, `notElem`     |                   |
| 3      |                      |                       | &&                |
| 2      |                      |                       | ||                |
| 1      | >>, >>=              |                       |                   |
| 0      |                      |                       | $, $!, `seq`      |
+--------+----------------------+-----------------------+-------------------+

任何缺少固定声明的运算符都被认为是左关联与优先级9。

请记住,函数应用具有最高优先级(考虑表中的其他优先级与优先级10之间的区别)[1]

为什么f g . h等同于f (g.h)?这是不是意味着函数应用优先级低于或与函数组合优先级相同? - CMCDragonkai
@CMCDragonkai 我已经更新了答案,加入了函数应用优先级。f g . h 等同于 (f g) . h,因为函数应用具有比任何运算符优先级更高的优先级(包括函数组合)。 - mucaho

28

你说得对。这个规则是Haskell语言中定义的一部分,Haskell Report中有详细说明。特别是在第3节“表达式”中,函数应用的参数(fexp)必须是aexpaexp允许将操作符作为一部分放入section或者使用括号表达式,但不能独立使用操作符。

map foo $ xs中,Haskell语法意味着这被解析为两个表达式,并应用于二元运算符$。就像sepp2k指出的那样,语法(map foo $)是一个左section,具有不同的含义。

我必须承认,我从未仔细考虑过这个问题,实际上不得不查看Report才能了解操作符的行为。


12
区别在于中缀运算符被放置在它们的参数之间,因此这个
list = map foo $ xs

可以重写为前缀形式

list = ($) (map foo) xs

根据$运算符的定义,它就是

list = (map foo) xs

10

如果您用括号将运算符包围起来(即map foo ($) xs),则可以将运算符作为函数参数传递(实际上传递的是(map foo ($)) xs)。但是,如果不使用括号,则无法将其作为参数传递(或赋值给变量)。

此外,请注意语法(someValue $)(其中$可以是任何运算符)实际上意味着不同的东西:它等同于\x -> someValue $ x,即它部分地应用了左操作数的运算符(当然,在$的情况下,这是一个无操作)。同样,($ x)部分地应用了右操作数的运算符。因此,map ($ x) [f, g, h]将计算为[f x, g x, h x]


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接