9

好的,这是漫长的一天,我的大脑可能无法在 Haskell 级别上运行,但我无法理解“Learn You a Haskell”中的一个例子。

该部分被称为带有 $ 的函数应用程序,并且有如何$定义的示例:

($) :: (a -> b) -> a -> b
f $ x = f x

到目前为止,一切都清楚了。我理解本节中的所有示例,除了最后一个:

ghci> map ($ 3) [(4+), (10*), (^2), sqrt]
[7.0,30.0,9.0,1.7320508075688772]

在这里,我们($ 3)跨函数列表进行映射,并将这些函数应用到3. 但这怎么可能?

从第一个代码片段可以清楚地看出,第一个参数是一个函数,我们甚至可以这样写:

*Main> ($) sqrt 4
2.0

现在($ 3)是函数的一个部分应用$,而是3在函数的位置上!那么3应该是一个功能还是什么?

还有一个谜团:到底是(4+)什么?我知道那(+4)是函数的偏应用+,那么(4+)应该是函数的偏应用4吗?废话。什么样的技巧在这里起作用?

4

3 回答 3

14

($ 3)并且(+ 4)不是部分应用程序 - 它们是操作员部分。部分应用程序看起来像(($) 3)((+) 4)

形式的运算符部分(? x)(其中?代表任意中缀运算符)绑定运算符的操作数,即它等价于\y -> y ? x. 同样,运算符部分(x ?)绑定左操作数,因此等效于部分应用。

于 2014-09-08T14:59:12.327 回答
8

我认为让你绊倒的是运营商部分。这些使您可以部分地应用带有其中一个参数的运算符,因此您可以使用运算符(+4)(4+),其中4第二个参数是第一个参数,然后是第一个参数+。一个更清楚的例子可能是("Hello" ++)vs (++ "world"),前者附加到"Hello"字符串的前面,而后者附加"world"到字符串的末尾。

这与使用前缀形式的运算符形成对比,只是在其周围使用括号。在这种形式中,以下是等价的:

> let join = (++)
> join "Hello, " "world"
"Hello, world"
> (++) "Hello, " "world"
"Hello, world"

在前缀形式中,您将运算符视为普通函数,它按顺序接受其第一个参数,然后是第二个参数。在运算符部分中,参数位于运算符的哪一侧很重要。


所以在你的例子中,你有 的部分应用($ 3),你可以将它减少为

map ($ 3) [(4+), (10*), (^2), sqrt]
[($ 3) (4+), ($ 3) (10 *), ($ 3) (^ 2), ($ 3) sqrt]
[4 + 3, 10 * 3, 3 ^ 2, sqrt 3]
[7.0, 30.0, 9.0, 1.7320508075688772]
于 2014-09-08T14:58:42.567 回答
4

您对部分感到困惑。掌握部分概念的一个好方法是玩一个例子:

(<^>) :: Int -> Float -> Int
a <^> b = a

上面的函数是一个无用的函数,不管第二个参数是什么,它都会返回第一个参数。但它接受Int然后Float作为输入。

现在,由于部分,您可以使用他们的任何一个论点来应用:

λ> let a = (3 <^>)
λ> :t a
a :: Float -> Int
λ> let b = (<^> 3.0)
λ> :t b
b :: Int -> Int

查看 和 的类型如何因部分ab不同。

于 2014-09-08T15:04:46.437 回答