优先级和$
所以 Haskell 有一个非常规则的语法。最高优先级是括号;然后将函数应用于其参数。函数应用程序是左关联的,或者,我更喜欢称它为贪婪的 nom:这意味着函数“吃掉”它作为参数看到的第一件事。所以,如果你这样写f g h
,就变成(f g) h
:先吃,然后返回值f
吃。通常,尤其是在定义函数时,您希望在真正需要显式括号的地方编写类似的东西,以免意外编写.g
h
f (Constructor parameter1 parameter2) = ...
((f Constructor) parameter1) parameter2
在括号和应用程序之后,我们有运算符:它们具有由“中缀指令”给出的优先级和关联性的完整层次结构。最低优先级运算符定义为:
f $ g = f g
infixr 0 $
这个操作符是一个完全正常的操作符,看起来什么都不做:更准确地说,它把左边的函数应用到右边的参数上。它是低优先级的右关联,所以它是“惰性标称”(之前的函数$
应用于之后的所有内容$
)。f . g . h $ i
关于是否比 或多或少正确存在一个有趣的语法争论f $ g $ h $ i
,它以不同的方式做同样的事情。
请记住,这$
实际上只是一个普通的运算符/函数。例如,您可以执行以下操作:
Prelude> let factorial n = product [1..n]
Prelude> map ($ 3) [(5 +), (3 *), (3 +) . factorial . (2 *)]
[8,9,723]
在这里,我们正在创建一个函数($ 3)
,该函数将函数作为参数,并将其应用于 3。我们将生成的函数映射到其他几个函数。我们也可以像zipWith ($) functions (repeat 3)
您真正想要的那样编写它,($)
作为 zipWith 将用于将两个列表压缩在一起的组合函数传递。它们是相同的东西,它们都是有趣的把戏。您甚至可能有一天想要map (flip ($))
遍历值列表,以获取函数形式的值列表。这是一个同构;你可以用 取回值id = map ($ id) . map (flip ($))
,但也许有一天这种格式对你来说会更方便。
低于此优先级的是特殊形式,如if
, let
, case
, do
, where
, 和\
。一般来说,Haskell 要求这些不能立即出现在值 or 之后)
,但可以出现在 a(
或运算符之后。所以如果你想写f \x -> 3 + 2 * x
Haskell 会抱怨,直到你把它变成以下之一:
f ((3 +) . (2 *)) -- no special forms
f (\x -> 3 + 2 * x) -- parenthesize the sub-expression
f $ \x -> 3 + 2 * x -- use $ to make the syntax "work" effortlessly.
同样,您可能会看到如下内容:
main = complicatedProcessingStep . preprocessing $ do
input <- io_input
...
用于$
避免在周围放置括号的地方,do
这样您就不必在空白处的)
某处悬挂令牌。
函数只有一个参数
Haskell 与其他语言的一大不同之处在于每个函数都只有一个参数。一开始这可能会让你感到困惑:操作符不是两个参数的函数\a b c -> ...
吗?那么,它不是有三个参数吗?
答案是否定的:\a b c -> ...
是语法糖\a -> \b -> \c -> ...
(更不用说你也可以对这些参数进行模式匹配,所以秘密地\a -> ...
是语法糖\random_token -> case random_token of a -> ...
)。每个函数都有一个参数,但有些函数返回一个函数。在 Haskell 中,我们可以做其他语言做的事情,并接受一个元组;\(a, b) -> a + b
工作正常,相当于uncurry (+)
. 我们通常不会那样做——我们通常会通过\a b -> a + b
。
您可以从任何返回函数的函数中创建运算符。结果运算符将其左侧作为第一个函数的参数,将其右侧作为第二个函数的参数。执行此操作的规范方法是使用反引号:
13 `mod` 7 == mod 13 7
但是如果类型不是多态的,或者如果你写了一个明确的类型签名或者禁用了“单态限制”,你也可以写(%%%) = mod
.
三参数运算符。
所以你对“三参数运算符”的回答是:它返回一个函数,然后可以将其应用于其他值。当你写:
a x $|| b y $ c z
由于上述规则,它解析为:
($) (($||) (a x) (b y)) (c z)
根据定义($)
变为:
($||) (a x) (b y) (c z)
只需在子表达式上使用运算符ax $|| b y
,就会生成一个函数,该函数可以使用括号应用,如 in (a x $|| b y) (c z)
,也可以使用$
将其左侧应用于右侧的运算符。