之间有什么区别。(点)和$(美元符号)?


Answers:


1226

$操作是为了避免括号。之后出现的所有内容将优先于之前出现的所有内容。

例如,假设您有一行显示:

putStrLn (show (1 + 1))

如果要删除这些括号,则以下任何一行都将执行相同的操作:

putStrLn (show $ 1 + 1)
putStrLn $ show (1 + 1)
putStrLn $ show $ 1 + 1

.运算符的主要目的不是避免括号,而是链接函数。它使您可以将右侧显示的输出与左侧显示的输入绑定在一起。通常,这也会导致括号较少,但工作方式有所不同。

回到同一个例子:

putStrLn (show (1 + 1))
  1. (1 + 1)没有输入,因此不能与.运算符一起使用。
  2. show可以取Int并返回String
  3. putStrLn可以取String并返回IO ()

你可以链showputStrLn这样的:

(putStrLn . show) (1 + 1)

如果您喜欢的括号太多,请与$运算符删除它们:

putStrLn . show $ 1 + 1

54
实际上,由于+也是一个函数,因此不能将其加上前缀也要像`putStrLn一样进行组合。显示 。(+)1 1`并不是说它更清晰,但是我的意思是……你可以吧?
CodexArcanum 2010年

4
@CodexArcanum在此示例中,类似的内容putStrLn . show . (+1) $ 1将是等效的。您是正确的,因为大多数(全部?)中缀运算符都是函数。
迈克尔·斯蒂尔

79
我不知道为什么没人会提到map ($3)。我的意思是,我通常$也尽量避免使用括号,但这并不是他们所需要的全部。
立方

43
map ($3)是type的函数Num a => [(a->b)] -> [b]。它使用一个带数字的函数列表,将3应用于所有函数并收集结果。
立方2014年

21
与其他运算符一起使用$时,请务必小心。“ x + f(y + z)”与“ x + f $ y + z”不同,因为后者实际上表示“(x + f)(y + z)”(即x和f之和为视为功能)。
保罗·约翰逊

186

它们具有不同的类型和不同的定义:

infixr 9 .
(.) :: (b -> c) -> (a -> b) -> (a -> c)
(f . g) x = f (g x)

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

($)旨在替换常规功能应用程序,但使用不同的优先级来避免使用括号。(.)用于将两个函数组成一个新函数。

在某些情况下,它们是可互换的,但这通常是不正确的。它们所在的典型示例是:

f $ g $ h $ x

==>

f . g . h $ x

换句话说,在$s 链中,除最后一个外的所有s都可以替换为.


1
如果x是一个函数怎么办?然后可以.用作最后一个吗?
richizy

3
@richizy,如果您实际上是x在这种情况下申请,那么可以-但是“最终”申请将适用于而不是x。如果您不申请x,那么x与值一样。
GS-向Monica致谢,

123

另请注意,这($)专用于功能类型的身份功能。身份函数如下所示:

id :: a -> a
id x = x

虽然($)看起来像这样:

($) :: (a -> b) -> (a -> b)
($) = id

请注意,我有意在类型签名中添加了额外的括号。

的用途($),通常可以通过添加括号(除非操作者在一个部分中使用)来消除。例如:f $ g x变成f (g x)

的使用(.)通常较难替换;他们通常需要lambda或引入显式函数参数。例如:

f = g . h

变成

f x = (g . h) x

变成

f x = g (h x)

希望这可以帮助!


“请注意,我故意在类型签名中添加了额外的括号。” 我很困惑...你为什么这样做?
Mateen Ulhaq,

3
@MateenUlhaq($)的类型是(a-> b)-> a-> b,与(a-> b)->(a-> b)相同,但是这里额外的括号会添加一些明晰。
鲁迪

2
哦,我想。我本来以为它是两个参数的函数...但是由于存在柯里化,它完全等效于返回函数的函数。
Mateen Ulhaq '18

78

($) 允许将函数链接在一起,而无需添加括号来控制评估顺序:

Prelude> head (tail "asdf")
's'

Prelude> head $ tail "asdf"
's'

compose运算符(.)无需指定参数即可创建一个新函数:

Prelude> let second x = head $ tail x
Prelude> second "asdf"
's'

Prelude> let second = head . tail
Prelude> second "asdf"
's'

上面的示例可以说是说明性的,但实际上并没有显示使用合成的便利性。这是另一个比喻:

Prelude> let third x = head $ tail $ tail x
Prelude> map third ["asdf", "qwer", "1234"]
"de3"

如果只使用一次三次,则可以避免使用lambda命名:

Prelude> map (\x -> head $ tail $ tail x) ["asdf", "qwer", "1234"]
"de3"

最后,组合使我们避免使用lambda:

Prelude> map (head . tail . tail) ["asdf", "qwer", "1234"]
"de3"

3
如果stackoverflow具有组合功能,则我希望将上述两个说明与该答案中的示例结合起来的答案。
Chris.Q 2014年

59

简短而甜美的版本:

  • ($) 调用函数,该函数是其左手参数,值是其右手参数。
  • (.) 将函数(其左手参数)组合在函数(其右手参数)上。

29

一个有用的应用程序让我花了一些时间从简短的说明中了解到您学习haskell

f $ x = f x

并用括号括起包含infix运算符的表达式的右侧,可将其转换为前缀函数,可以($ 3) (4+)类似于编写(++", world") "hello"

为什么有人会这样做?例如,用于功能列表。都:

map (++", world") ["hello","goodbye"]`

和:

map ($ 3) [(4+),(3*)]

map (\x -> x ++ ", world") ...或短map (\f -> f 3) ...。显然,后一种变体对大多数人来说更易读。


14
顺便说一句,我建议不要在$3没有空格的情况下使用。如果启用了模板Haskell,则将其解析为拼接,而$ 3始终表示您所说的内容。通常,Haskell似乎倾向于通过坚持某些运算符在其周围留有空间来“窃取”某些语法。
GS-向Monica致谢,2010年

1
我花了一段时间才能找出如何在括号中工作:en.wikibooks.org/wiki/Haskell/...
Casebash

18

Haskell :(.点)和$(美元符号)之间的区别

(.)和美元符号有($)什么区别?据我了解,它们都是不需要使用括号的语法糖。

他们是不是不需要使用括号语法糖-他们是功能- infixed,因此,我们可以称他们为运营商。

撰写,(.)以及何时使用它。

(.)是撰写功能。所以

result = (f . g) x

是相同的构建通过参数传递到的结果的功能gf

h = \x -> f (g x)
result = h x

使用(.)时,你没有可用的传递给你希望组成函数的参数。

正确的关联申请,($)以及何时使用

($)是具有低绑定优先级的右联想应用函数。因此,它仅首先计算右边的事物。从而,

result = f $ g x

在程序上与此相同(这很重要,因为Haskell的计算是惰性的,它将首先开始计算f):

h = f
g_x = g x
result = h g_x

或更简而言之:

result = f (g x)

使用($)时,你有所有的变量在应用之前函数结果之前评估。

通过阅读每个函数的源代码,我们可以看到这一点。

阅读资料

这里的来源(.)

-- | Function composition.
{-# INLINE (.) #-}
-- Make sure it has TWO args only on the left, so that it inlines
-- when applied to two functions, even if there is no final argument
(.)    :: (b -> c) -> (a -> b) -> a -> c
(.) f g = \x -> f (g x)

而这里的来源($)

-- | Application operator.  This operator is redundant, since ordinary
-- application @(f x)@ means the same as @(f '$' x)@. However, '$' has
-- low, right-associative binding precedence, so it sometimes allows
-- parentheses to be omitted; for example:
--
-- >     f $ g $ h x  =  f (g (h x))
--
-- It is also useful in higher-order situations, such as @'map' ('$' 0) xs@,
-- or @'Data.List.zipWith' ('$') fs xs@.
{-# INLINE ($) #-}
($)                     :: (a -> b) -> a -> b
f $ x                   =  f x

结论

当您不需要立即评估该功能时,请使用组合。也许您想将合成产生的功能传递给另一个功能。

提供所有参数以进行全面评估时,请使用application。

因此,对于我们的示例,在语义上更可取

f $ g x

当我们有x(或者更确切地说g是的参数)并执行以下操作时:

f . g

当我们不这样做时。


12

...或者您可以通过使用流水线来避免.$结构:

third xs = xs |> tail |> tail |> head

那是在您添加了辅助函数之后:

(|>) x y = y x

2
是的,|>是F#管道运算符。
user1721780

6
这里要注意的一件事是,Haskell的$运算符实际上<|比F#更像F#|>,通常在haskell中,您应该像这样编写上述函数:third xs = head $ tail $ tail $ xs甚至可能像third = head . tail . tail,在F#风格的语法中,它应该是这样的:let third = List.head << List.tail << List.tail
电动咖啡

1
为什么要添加一个辅助函数使Haskell看起来像F#?-1
vikingsteve

9
被翻动的$是已经上市,它被称为& hackage.haskell.org/package/base-4.8.0.0/docs/...
拍拍

11

了解任何事物(任何功能)的一种好方法是记住一切都是一个功能!一般的口头禅可以帮助您,但是在像运算符这样的特定情况下,记住这一小技巧将有帮助:

:t (.)
(.) :: (b -> c) -> (a -> b) -> a -> c

:t ($)
($) :: (a -> b) -> a -> b

只要记住要:t放心使用,然后将运算符括起来即可()


11

我的规则很简单(我也是初学者):

  • .如果要传递参数(调用函数),请不要使用,并且
  • $如果没有参数,请不要使用(组成一个函数)

那是

show $ head [1, 2]

但永远不会:

show . head [1, 2]

3
启发式良好,但可以使用更多示例
Zoey Hewll '17

0

我认为一个简短的示例说明您将在哪里使用.而不是在哪里使用$

double x = x * 2
triple x = x * 3
times6 = double . triple

:i times6
times6 :: Num c => c -> c

请注意,这times6是一个根据功能组合创建的功能。


0

所有其他答案都很好。但是,有一个重要的可用性细节涉及ghc如何处理$,ghc类型检查器允许具有较高等级/量化类型的状态。如果您查看$ id例如的类型, 您会发现它将使用一个函数,其参数本身就是一个多态函数。像这样的小事情在使用等效的翻转运算符时不会具有相同的灵活性。(这实际上使我怀疑$!是否应得到相同的待遇)

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.