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

时间:2009-06-02 16:06:37

标签: haskell syntax function-composition

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

13 个答案:

答案 0 :(得分:1153)

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

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

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 ()
  4. 您可以将show链接到putStrLn,如下所示:

    (putStrLn . show) (1 + 1)
    

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

    putStrLn . show $ 1 + 1
    

答案 1 :(得分:177)

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

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

答案 2 :(得分:121)

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

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)

希望这有帮助!

答案 3 :(得分:77)

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

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"

答案 4 :(得分:58)

短而甜蜜的版本:

  • ($)调用函数,该函数是其右手参数值的左手参数。
  • (.)组成函数,它是函数的左手参数,也就是它的右手参数。

答案 5 :(得分:29)

一个有用的应用程序,花了我一些时间从非常简短的描述at learn you a haskell中找出:自:

f $ x = f x

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

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

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

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

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

答案 6 :(得分:12)

...或者您可以使用管道来避免.$构造:

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

在您添加辅助函数之后:

(|>) x y = y x

答案 7 :(得分:11)

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

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

那是

show $ head [1, 2]

但从不:

show . head [1, 2]

答案 8 :(得分:11)

了解任何事物(任何功能)的好方法是记住一切都是功能!一般的口头禅有所帮助,但在特定情况下,如操作员,有助于记住这个小技巧:

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

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

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

答案 9 :(得分:8)

  

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

     

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

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

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

(.)是撰写函数。所以

result = (f . g) x

与构建一个函数相同,该函数将传递给g的参数的结果传递给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)

在将上述函数应用于结果之前,如果要评估所有变量,请使用($)

我们可以通过阅读每个功能的来源来看到这一点。

阅读来源

这里是(.)的{​​{3}}:

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

这里是($)的{​​{3}}:

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

当我们没有。

答案 10 :(得分:1)

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

如果您键入信息,您将看到以下内容:

accessTokenAcceptedVersion

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

因此,我们在正常功能应用程序或使用()不起作用的地方使用它。

例如,这可行:

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

但这不是:

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

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

λ> head . sort "example"

但是。期望有两个函数,由于sort和。操作的顺序,没有一个很好的捷径。

答案 11 :(得分:0)

我认为您使用print ("GetMouseButtonDown on nothing"); 而不是.的简短示例将有助于澄清事情。

$

请注意,double x = x * 2 triple x = x * 3 times6 = double . triple :i times6 times6 :: Num c => c -> c 是一个根据函数组合创建的函数。

答案 12 :(得分:0)

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