点号(.)和美元符号($)有什么区别?
据我所知,它们都是语法糖,用于不需要使用括号。
点号(.)和美元符号($)有什么区别?
据我所知,它们都是语法糖,用于不需要使用括号。
$
操作符用于避免括号。它之后的任何内容都会比之前的内容优先执行。
例如,假设你有一行代码:
putStrLn (show (1 + 1))
如果你想要去掉这些括号,下面任何一行代码都能实现同样的效果:
putStrLn (show $ 1 + 1)
putStrLn $ show (1 + 1)
putStrLn $ show $ 1 + 1
.
运算符的主要目的不是为了避免使用括号,而是为了链式调用函数。它允许你将右侧表达式的输出与左侧表达式的输入相连接。这通常也会导致更少的括号,但是工作方式不同。回到同一个例子:
putStrLn (show (1 + 1))
(1 + 1)
没有输入,因此不能与 .
运算符一起使用。show
可以接受一个 Int
并返回一个 String
。putStrLn
可以接受一个 String
并返回一个 IO ()
。您可以像这样将 show
链接到 putStrLn
:
(putStrLn . show) (1 + 1)
如果您对那么多括号感到不满意,可以使用$
运算符来摆脱它们:
putStrLn . show $ 1 + 1
它们有不同的类型和不同的定义:
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
$
中,除了最后一个以外的所有字符都可以被替换为 .
。x
是一个函数,那么你能把 .
作为最后一个吗? - richizy还要注意 ($)
是专门针对函数类型的恒等函数(identity function)。恒等函数长这样:
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)
($)
允许函数在不添加括号控制求值顺序的情况下链接在一起:
Prelude> head (tail "asdf")
's'
Prelude> head $ tail "asdf"
's'
组合运算符(.)
创建一个新的函数,而不指定参数:
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"
如果我们仅使用third一次,我们可以通过使用lambda避免对其命名:
Prelude> map (\x -> head $ tail $ tail x) ["asdf", "qwer", "1234"]
"de3"
最后,组合让我们避免使用lambda:
Prelude> map (head . tail . tail) ["asdf", "qwer", "1234"]
"de3"
简短而精炼的版本:
($)
调用其左侧参数为函数,右侧参数为值的函数。(.)
将其左侧参数为函数和右侧参数为函数组合起来。在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 *)]
$3
这样没有空格的写法。如果启用了 Template Haskell,这将被解释为语法插入,而 $ 3
始终表示你所说的意思。总的来说,在 Haskell 中有一种趋势是通过坚持某些运算符周围必须有空格来“窃取”语法位。 - GS - Apologise to Monica.
)和美元符号($
)之间的区别点号(.
)和美元符号($
)的区别是什么?据我所知,它们都是语法糖,用于不需要使用括号。
它们不是用于不需要使用括号的语法糖-它们是函数,中缀的,因此我们可以称它们为运算符。
(.)
以及何时使用它(.)
是组合函数。
result = (f . g) x
构建一个函数,该函数将其参数传递给g
的结果传递到f
中,与构建将其参数传递给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
当我们不这样做时。
...或者您可以通过使用管道技术避免使用 .
和 $
结构:
third xs = xs |> tail |> tail |> head
在添加了辅助函数之后:
(|>) x y = y x
$
运算符实际上更像 F# 的 <|
而不是 |>
。通常在 Haskell 中,你会像这样编写上面的函数:third xs = head $ tail $ tail $ xs
或者甚至像这样:third = head . tail . tail
,在 F# 风格的语法中,它会变成这样:let third = List.head << List.tail << List.tail
。 - Electric Coffee$
已经存在,它被称为 &
。具体信息请查看此链接:https://hackage.haskell.org/package/base-4.8.0.0/docs/Data-Function.html#v:-38-。 - pat我的规则很简单(我也是新手):
。
$
就是这样。
show $ head [1, 2]
但永远不要:
show . head [1, 2]
学习任何事情(任何函数)的好方法是记住一切都是函数!这个普遍的口号有所帮助,但在特定情况下,如操作符,记住这个小技巧会更加有用:
:t (.)
(.) :: (b -> c) -> (a -> b) -> a -> c
并且
:t ($)
($) :: (a -> b) -> a -> b
记得要广泛使用:t
,并将运算符包裹在()
中!
putStrLn . show . (+) 1 1
?虽然这并没有更加清晰,但我的意思是......你可以这样做,对吗? - CodexArcanumputStrLn . show . (+1) $ 1
зҡ„иЎЁиҫҫејҸжҳҜзӯүд»·зҡ„гҖӮдҪ иҜҙеҫ—еҜ№пјҢеӨ§еӨҡж•°пјҲжүҖжңүпјҹпјүдёӯзјҖиҝҗз®—з¬ҰйғҪжҳҜеҮҪж•°гҖӮ - Michael Steelemap ($3)
这样的用法。我的意思是,我大多数时候使用$
是为了避免使用括号,但这并不意味着它们只有这个作用。 - Cubicmap ($3)
是一个类型为Num a => [(a->b)] -> [b]
的函数。它接受一个函数列表,这些函数都需要一个数字作为参数,并将数字 3 应用到它们所有的函数上,然后收集结果。 - Cubic