770

What is the difference between the dot (.) and the dollar sign ($)?

As I understand it, they are both syntactic sugar for not needing to use parentheses.

4

13 回答 13

1314

$运算符用于避免括号。在它之后出现的任何东西都将优先于之前出现的任何东西。

例如,假设您有一行内容为:

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可以取 aInt并返回 a String
  3. putStrLn可以取 aString并返回 a IO ()

你可以showputStrLn这样链接:

(putStrLn . show) (1 + 1)

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

putStrLn . show $ 1 + 1
于 2009-08-17T22:01:59.793 回答
204

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

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 链中,除了最后一个之外,所有的都可以替换为.

于 2009-06-02T16:14:22.443 回答
134

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

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)

希望这可以帮助!

于 2009-06-02T16:32:36.497 回答
83

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

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"
于 2010-01-07T01:43:45.647 回答
61

简短而甜蜜的版本:

  • ($)调用作为其右手参数的值的函数,该函数是其右手参数。
  • (.)将作为其左侧参数的函数组合到作为其右侧参数的函数上。
于 2009-06-10T20:11:07.537 回答
34

一个有用的应用程序,我花了一些时间从Learn You a Haskell的简短描述中弄清楚:因为

f $ x = f x

并且在包含中缀运算符的表达式的右侧加上括号将其转换为前缀函数,可以写($ 3) (4 +)类似于(++ ", world") "hello".

为什么会有人这样做?例如,对于函数列表。两个都:

map (++ ", world") ["hello", "goodbye"]
map ($ 3) [(4 +), (3 *)]

map (\x -> x ++ ", world") ["hello", "goodbye"]
map (\f -> f 3) [(4 +), (3 *)]

显然,后一种变体对大多数人来说更具可读性。

于 2010-01-31T12:12:29.260 回答
28

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

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

它们不是不需要使用括号的语法糖 - 它们是函数, - 中缀,因此我们可以称它们为运算符。

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

(.)是撰写功能。所以

result = (f . g) x

与构建一个将其参数的结果传递给gon的函数相同f

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

结论

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

当您提供所有参数以进行全面评估时,请使用应用程序。

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

f $ g x

当我们有x(或者更确切地说g是 的论点),并且做:

f . g

当我们不这样做时。

于 2017-11-09T23:12:45.800 回答
12

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

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

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

(|>) x y = y x
于 2012-12-04T10:24:49.533 回答
12

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

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

那是

show $ head [1, 2]

但从不:

show . head [1, 2]
于 2013-08-20T15:53:44.970 回答
11

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

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

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

只要记住要:t自由使用,并将您的运算符包装在()!

于 2015-04-15T06:10:17.183 回答
1

$ 最重要的部分是它具有最低的运算符优先级。

如果你输入信息,你会看到:

λ> :info ($)
($) :: (a -> b) -> a -> b
    -- Defined in ‘GHC.Base’
infixr 0 $

这告诉我们它是具有最低可能优先级的右结合性中缀运算符。普通函数应用程序是左关联的并且具有最高优先级 (10)。所以 $ 是相反的东西。

因此,我们在普通函数应用程序或 using () 不起作用的地方使用它。

因此,例如,这有效:

λ> head . sort $ "example"
λ> e

但这不会:

λ> head . sort "example"

因为 。优先级低于 sort 并且 (sort "example") 的类型是 [Char]

λ> :type (sort "example")
(sort "example") :: [Char]

但 。需要两个函数,并且由于 sort 和 .

于 2020-06-08T18:05:53.070 回答
1

所有其他答案都很好。但是关于 ghc 如何处理 $ 有一个重要的可用性细节,即 ghc 类型检查器允许具有更高等级/量化类型的 instatiarion。如果你看一下 $ idexample 的类型,你会发现它会采用一个函数,其参数本身就是一个多态函数。类似的小事情没有与等效的翻转操作符相同的灵活性。(这实际上让我想知道 $! 是否应该得到同样的待遇)

于 2018-08-09T02:59:36.960 回答
0

我认为一个简短的例子说明你会在哪里使用.而不是在哪里$有助于澄清事情。

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

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

请注意,times6这是从函数组合创建的函数。

于 2016-10-15T23:26:42.517 回答