Haskell 中的函数应用

9
好的,今天是漫长的一天,我的大脑可能无法像Haskell一样运转,但我就是无法理解“学习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的部分应用吗?荒谬。这里使用了什么样的技巧?

可能是[使用中缀函数进行部分应用]的重复问题(https://dev59.com/4mkw5IYBdhLWcg3wHWzn) - Lambdageek
3个回答

14

($ 3)(+ 4)不是部分应用程序-它们是运算符截面。 部分应用程序看起来像(($)3)((+)4)

形式为(?x)的运算符截面(其中代表任意中缀运算符)绑定运算符的右操作数,即等同于\y -> y?x。 同样,运算符截面(x?)绑定左操作数,因此相当于部分应用程序。


9
我认为你被“运算符部分应用”所困扰。这使您可以使用其任一参数部分地应用运算符,因此您可以拥有运算符(+4)(4+),其中4分别是+的第二个和第一个参数。更清晰的例子可能是("Hello" ++)(++ "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]

我认为在Haskell中,“运算符”本身就是普通函数,你可以像对待普通函数一样对待它们。 - Mark Karpov
1
@Mark,除了应用语法之外,它们在所有方面都是正常的。它们默认为中缀,而非运算符函数默认为前缀。当我说普通函数时,我的意思是前缀函数。 - bheklilr

4
你可能对“sections”这个概念感到困惑。一个理解“sections”的好方法是通过实际操作示例来学习:
(<^>) :: Int -> Float -> Int
a <^> b = a

上面的函数是一个无用的函数,无论第二个参数是什么,它都会返回第一个参数。但它接受 IntFloat 作为输入。

现在,由于可以使用部分应用程序来应用它们的任何一个参数:

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

看看由于区段的不同,ab的类型是如何不同的。


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