Haskell 中的函数应用

Posted

技术标签:

【中文标题】Haskell 中的函数应用【英文标题】:Function application in Haskell 【发布时间】:2014-11-01 20:07:55 【问题描述】:

好吧,这已经是漫长的一天,我的大脑可能无法在 Haskell 水平上运作,但我就是无法理解“Learn You a Haskell”中的一个例子。

该部分称为 Function Application with $,并且有一个如何定义 $ 的示例:

($) :: (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 的结果。但这怎么可能呢?

从第一个代码 sn-p 可以看出,第一个参数是一个函数,我们甚至可以这样写:

*Main> ($) sqrt 4
2.0

现在($ 3) 是函数$ 的部分应用,但3 继续函数的位置!那么3 应该是一个函数还是什么?

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

【问题讨论】:

Partial Application with Infix Functions的可能重复 【参考方案1】:

我认为阻碍你的是运算符部分。这些让您可以部分应用带有任一参数的运算符,因此您可以使用运算符(+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]

【讨论】:

“你把操作符当成一个普通函数”,我认为'operators' Haskell中的普通函数。 @Mark 除了应用程序语法之外,它们在其他方面都很正常。它们默认为中缀,而非运算符函数默认为前缀。当我说普通函数时,我指的是前缀函数。【参考方案2】:

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

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

【讨论】:

【参考方案3】:

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

(<^>) :: 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 的类型因部分而异。

【讨论】:

以上是关于Haskell 中的函数应用的主要内容,如果未能解决你的问题,请参考以下文章

Haskell 中缀函数应用优先级

Haskell中函数应用运算符的使用

Haskell入门篇八:高阶函数(上)

Swift 之惰性求值

Haskell 应用习语?

函数返回与函数参数中的 Haskell 模式匹配