.(点)和$(美元符号)有什么区别?

(.)和美元符号($)有什么区别?

据我所知,它们都是不需要使用括号的语法糖。

199587 次浏览

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

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

($)旨在替换普通函数应用程序,但优先级不同以帮助避免括号。(.)用于将两个函数组合在一起以创建新函数。

在某些情况下,它们是可以互换的,但一般情况下并非如此。它们存在的典型例子是:

f $ g $ h $ x

==>

f . g . h $ x

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

另请注意,($)专门用于函数类型的单位函数。单位函数如下所示:

id :: a -> aid 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)

希望这有帮助!

简短而甜蜜的版本:

  • ($)在作为其右侧参数的值上调用作为其左侧参数的函数。
  • (.)将作为其左侧参数的函数组合到作为其右侧参数的函数上。

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

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

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 ()

您可以像这样将show链接到putStrLn

(putStrLn . show) (1 + 1)

如果你喜欢太多的括号,用$运算符去掉它们:

putStrLn . show $ 1 + 1

($)允许函数链接在一起而不添加括号来控制求值顺序:

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

组合运算符(.)创建了一个新函数,但没有指定参数:

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

上面的例子可以说是说明性的,但并没有真正展示使用组合的便利。这是另一个类比:

Prelude> let third x = head $ tail $ tail xPrelude> 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"

一个很有用的应用程序,我花了一些时间从非常简短的描述学习你一个Haskell中弄清楚:因为

f $ x = f x

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

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

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

短于

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

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

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

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

这是在您在helper函数中添加之后:

(|>) x y = y x

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

  • 如果要传递参数(调用函数),请不要使用.,并且
  • 如果还没有参数,请不要使用$(编写函数)

那是

show $ head [1, 2]

但从来没有:

show . head [1, 2]

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

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

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

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

我认为使用.而不是$的简短示例将有助于澄清事情。

double x = x * 2triple x = x * 3times6 = double . triple
:i times6times6 :: Num c => c -> c

请注意,times6是一个由函数组合创建的函数。

Haskell:.(点)和$(美元符号)之间的差异

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

它们是<强>不语法糖,不需要使用括号-它们是函数,-固定,因此我们可以称它们为运算符。

组合,(.),以及何时使用它。

(.)是组成函数。所以

result = (f . g) x

与构建一个函数相同,该函数将传递给g的参数的结果传递给f

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

当您没有可用于传递给您希望组合的函数的参数时,请使用(.)

右关联应用,($),以及何时使用它

($)是一个具有低绑定优先级的右关联应用函数。所以它只是先计算它右边的东西。因此,

result = f $ g x

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

h = fg_x = g xresult = 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 -> bf $ x                   =  f x

结论

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

当您为完整评估提供所有参数时,请使用应用程序。

对于我们的例子,在语义上更可取的做法是

f $ g x

当我们有x(或者更确切地说,g的参数)时,并执行:

f . g

当我们不。

所有其他的答案都很好。但是关于ghc如何处理$有一个重要的可用性细节,ghc类型检查器允许具有更高等级/量化类型的instatiarion。例如,如果你看一下$ id的类型,你会发现它会接受一个参数本身就是多态函数的函数。这样的小事情在使用等效的重置运算符时没有得到同样的灵活性。(这实际上让我怀疑$!是否值得同样的处理)

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

如果你输入info,你会看到:

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

这告诉我们它是一个具有右关联性的中缀运算符,具有尽可能低的优先级。正常函数应用程序是左关联的,具有最高优先级(10)。所以$是相反的东西。

因此,我们在正常函数应用程序或使用()不起作用的情况下使用它。

例如,这是有效的:

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

但这并不是:

λ> head . sort "example"

因为。的优先级低于排序,并且(排序“示例”)的类型是[Char]

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

但是。需要两个函数,并且由于排序和的操作顺序,没有一个很好的简短方法来做到这一点。