我在包中找到了一堆像$||
这样的运算符parallel
:
-- Strategic function application
{-
These are very handy when writing pipeline parallelism asa sequence of
@$@, @$|@ and @$||@'s. There is no need of naming intermediate values
in this case. The separation of algorithm from strategy is achieved by
allowing strategies only as second arguments to @$|@ and @$||@.
-}
-- | Sequential function application. The argument is evaluated using
-- the given strategy before it is given to the function.
($|) :: (a -> b) -> Strategy a -> a -> b
f $| s = x -> let z = x `using` s in z `pseq` f z
-- | Parallel function application. The argument is evaluated using
-- the given strategy, in parallel with the function application.
($||) :: (a -> b) -> Strategy a -> a -> b
f $|| s = x -> let z = x `using` s in z `par` f z
但他们有 3 个论点。我没有看到使用这些运算符的示例,所以我需要想出一个好的方法来编写它们。
他们的通常风格是这样的:
f $|| strategy $ expr
?
优先级和$
所以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 $
这个运算符是一个完全正常的运算符,它似乎什么都不做:更准确地说,它将左边的函数应用到右边的参数上。它是低优先级的右关联,因此它是"懒惰的nom"($
之前的函数应用于该$
之后的所有内容)。关于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要求这些不能在值或
)
之后立即出现,但可以出现在(
或运算符之后。因此,如果你想写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
就会生成一个函数,该函数可以使用括号应用,如(a x $|| b y) (c z)
中所示,也可以使用将其左侧应用于右侧的$
运算符。