关于haskell:. (dot) and $两者有什么区别

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

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


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

例如,假设您有一行代码:

1
putStrLn (show (1 + 1))

如果您想去掉这些括号,下面的任何一行也会做同样的事情:

1
2
3
putStrLn (show $ 1 + 1)
putStrLn $ show (1 + 1)
putStrLn $ show $ 1 + 1

.运算符的主要目的不是避免括号,而是链函数。它允许您将右侧显示的任何内容的输出与左侧显示的任何内容的输入绑定起来。这通常也会导致括号更少,但工作方式不同。

回到同样的例子:

1
putStrLn (show (1 + 1))
  • (1 + 1)没有输入,因此不能与.运算符一起使用。
  • show可以取一个Int返回一个String
  • putStrLn可以取一个String返回一个IO ()
  • 您可以这样将show链接到putStrLn

    1
    (putStrLn . show) (1 + 1)

    如果括号太多了,请使用$运算符将其去掉:

    1
    putStrLn . show $ 1 + 1


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

    1
    2
    3
    4
    5
    6
    7
    infixr 9 .
    (.) :: (b -> c) -> (a -> b) -> (a -> c)
    (f . g) x = f (g x)

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

    ($)用于替换普通函数应用程序,但优先级不同,有助于避免括号。(.)是将两个函数组合在一起形成一个新的函数。

    在某些情况下,它们是可互换的,但一般来说这是不正确的。它们的典型示例是:

    1
    f $ g $ h $ x

    =>

    1
    f . g . h $ x

    换言之,在$的链中,除最后一个外,其余的都可以用.代替。


    还要注意,($)是专门针对函数类型的标识函数。Identity函数如下所示:

    1
    2
    id :: a -> a
    id x = x

    虽然($)看起来是这样的:

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

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

    使用($)通常可以通过添加括号来消除(除非在节中使用了运算符)。例如:f $ g x变为f (g x)

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

    1
    f = g . h

    变成

    1
    f x = (g . h) x

    变成

    1
    f x = g (h x)

    希望这有帮助!


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

    1
    2
    3
    4
    5
    Prelude> head (tail"asdf")
    's'

    Prelude> head $ tail"asdf"
    's'

    compose运算符(.)在不指定参数的情况下创建一个新函数:

    1
    2
    3
    4
    5
    6
    7
    Prelude> let second x = head $ tail x
    Prelude> second"asdf"
    's'

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

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

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

    如果我们只使用第三次,我们可以避免使用lambda来命名它:

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

    最后,合成让我们避免lambda:

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


    简短而甜蜜的版本:

    • ($)调用函数,该函数是它在值上的左侧参数,而该值是它的右侧参数。
    • (.)组成的函数是它的左参数,而函数是它的右参数。

    有一个应用程序很有用,我花了一些时间从"学习哈斯克尔"的简短描述中找到答案:因为:

    1
    f $ x = f x

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

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

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

    还有:

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

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


    …或者您可以通过使用管道来避免.$结构:

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

    在添加了helper函数之后:

    1
    (|>) x y = y x


    了解任何事物(任何函数)的一个好方法是记住,所有事物都是一个函数!一般的咒语是有帮助的,但在某些特殊情况下,如操作员,记住这一小诀窍是有帮助的:

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

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

    记住要充分使用:t,并用()包装您的操作员!


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

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

    那就是

    1
    show $ head [1, 2]

    但绝不会:

    1
    show . head [1, 2]


    Haskell: difference between . (dot) and $ (dollar sign)

    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.

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

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

    (.)是复合函数。所以

    1
    result = (f . g) x

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

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

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

    右联想应用,($),何时使用

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

    1
    result = f $ g x

    与此相同,程序上(由于Haskell的评估不及时,因此它将首先开始评估f):

    1
    2
    3
    h = f
    g_x = g x
    result = h g_x

    或者更简明扼要地说:

    1
    result = f (g x)

    在将前面的函数应用于结果之前,当您有所有要评估的变量时,使用($)

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

    读源

    这是(.)的来源:

    1
    2
    3
    4
    5
    6
    -- | 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)

    这是($)的来源:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    -- | 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

    结论

    当您不需要立即评估函数时,可以使用composition。也许你想把合成的函数传递给另一个函数。

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

    所以对于我们的例子来说,在语义上最好这样做

    1
    f $ g x

    当我们有x的论点(或者更确切地说,是g的论点)时,我们应该:

    1
    f . g

    当我们不知道的时候。


    其他的答案都很好。但是有一个重要的可用性细节是关于ghc如何处理$的,ghc类型检查器允许Instatiarion具有更高级别/量化的类型。例如,如果你看$ id的类型,你会发现它将采用一个自变量为多态函数的函数。像这样的小事情,对于一个等价的烦扰操作符,并没有同样的灵活性。(这真的让我怀疑是不是美元!是否需要同样的待遇)


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

    1
    2
    3
    4
    5
    6
    double x = x * 2
    triple x = x * 3
    times6 = double . triple

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

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