点 (.)
和美元符号 ($)
有什么区别?
据我了解,它们都是不需要使用括号的语法糖。
$
运算符用于避免括号。在它之后出现的任何东西都将优先于之前出现的任何东西。
例如,假设您有一行内容为:
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 可以接受一个字符串并返回一个 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
是一个函数怎么办?然后你可以使用 .
作为最后一个吗?
x
,那么是的 - 但是“最终”应用程序将应用于 x
以外的其他东西。如果您不应用 x
,那么它与 x
是一个值没有什么不同。
另请注意,($)
是专用于函数类型的标识函数。身份函数如下所示:
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'
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"
简短而甜蜜的版本:
($) 在作为其右手参数的值上调用作为其左手参数的函数。
(.) 将作为其左侧参数的函数组合在作为其右侧参数的函数上。
一个有用的应用程序,我花了一些时间从非常简短的描述中弄清楚 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") ["hello", "goodbye"]
map (\f -> f 3) [(4 +), (3 *)]
显然,后一种变体对大多数人来说更具可读性。
$3
。如果启用了 Template Haskell,这将被解析为拼接,而 $ 3
始终表示您所说的。一般来说,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)
如果在将前面的函数应用于结果之前要评估所有变量,请使用 ($)
。
我们可以通过阅读每个函数的源代码来看到这一点。
阅读源代码
这是 (.)
的 source:
-- | 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)
这是 ($)
的 source:
-- | 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
甚至可能像 {5 },在 F# 风格的语法中是这样的:let third = List.head << List.tail << List.tail
我的规则很简单(我也是初学者):
不使用 。如果你想传递参数(调用函数),并且
如果还没有参数,则不要使用 $(组成一个函数)
那是
show $ head [1, 2]
但从不:
show . head [1, 2]
了解任何事物(任何函数)的好方法是记住一切都是函数!一般的口头禅会有所帮助,但在像运算符这样的特定情况下,记住这个小技巧会有所帮助:
:t (.)
(.) :: (b -> c) -> (a -> b) -> a -> c
和
:t ($)
($) :: (a -> b) -> a -> b
只要记住要自由地使用 :t
,并将您的运算符包装在 ()
中!
所有其他答案都很好。但是关于 ghc 如何处理 $ 有一个重要的可用性细节,即 ghc 类型检查器允许具有更高等级/量化类型的 instatiarion。例如,如果您查看 $ id
的类型,您会发现它会采用一个函数,其参数本身就是一个多态函数。类似的小事情没有与等效的翻转操作符相同的灵活性。 (这实际上让我想知道 $! 是否应该得到同样的待遇)
$ 最重要的部分是它具有最低的运算符优先级。
如果你输入信息,你会看到:
λ> :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 和 .
我认为在哪里使用 .
而不是 $
的简短示例将有助于澄清事情。
double x = x * 2
triple x = x * 3
times6 = double . triple
:i times6
times6 :: Num c => c -> c
请注意,times6
是从函数组合创建的函数。
不定期副业成功案例分享
putStrLn . show . (+1) $ 1
这样的东西是等价的。您是正确的,因为大多数(全部?)中缀运算符都是函数。map ($3)
这样的用途。我的意思是,我也主要使用$
来避免括号,但这并不是它们的全部用途。map ($3)
是Num a => [(a->b)] -> [b]
类型的函数。它需要一个带数字的函数列表,将 3 应用于所有函数并收集结果。