我理解<$>
类型签名背后的原因,因为它只是fmap
的中缀版本,但将它与>>=
的类型签名相比较,它的意义却大打折扣对我来说。
让我们首先确定我的意思。
(>>=) :: Monad m => m a -> (a -> m b) -> m b
(<*>) :: Applicative f => f (a -> b) -> f a -> f b
(<$>) :: Functor f => (a -> b) -> f a -> f b
查看类型签名,我们可以看到>>=
左边是一个值,右边是一个函数,如果考虑它的链接属性,这很有意义:foo >>= bar >>= baz
< / p>
这让我想知道,为什么<*>
和<$>
也不这样做?你不能写foo <*> bar <*> baz
,因为它要求foo <*> bar
的输出是一个函数,而不是一个值。
我知道存在<**>
和=<<
,它们都会翻转参数的顺序,允许我执行以下操作:
Just 4 <**> pure (+3) <**> pure (*2) >>= (\x -> Just (x-3))
本来可以精美地减少到:
Just 4 <$$> (+3) <$$> (*2) >>= (\x -> Just (x-3))
如果<$$>
已存在,或者<$>
和<*>
的参数顺序已被撤消。
让我想知道为什么存在这种差异的另一件事是,它让新手更难以习惯和/或记住它是否是功能,或者首先是值,而不必查看它。< / p>
那么,为什么<*>
和<$>
fn op val
>>=
,val op fn
{{1}}呢?
答案 0 :(得分:22)
不要让monad妨碍这里。想想应用程序。
比较
(<$>) :: Functor f => (a -> b) -> f a -> f b
和
($) :: (a -> b) -> a -> b
您可以看到常规应用程序与仿函数下的应用程序之间存在联系。
Trivia:有proposals使用括号来重载空格(应用程序),以便我们可以写:
(| f a1 .. an |)
而不是
pure f <*> a1 <*> .. <*> an
答案 1 :(得分:18)
“为什么按此顺序采取参数”的答案基本上是“因为”。定义这些功能的人认为这是最好的方法。 (在每种情况下,它可能不是同一个人。)但是,我会提供一些例子:
假设我们有一种解析monad。假设我们已经定义了
data Foobar = Foobar X Y Z
parseFoo :: Parser X
parseBar :: Parser Y
parseBaz :: Parser Z
然后我们可以写
parseFoobar :: Parser Foobar
parseFoobar = do
foo <- parseFoo
bar <- parseBar
baz <- parseBaz
return (Foobar foo bar baz)
或者,明确地,
parseFoobar =
parseFoo >>= \ foo ->
parseBar >>= \ bar ->
parseBaz >>= \ baz ->
return (Foobar foo bar baz)
现在让我们写一下这个应用风格:
parseFoobar = return Foobar <*> parseFoo <*> parseBar <*> parseBaz
或者,
parseFoobar = Foobar <$> parseFoo <*> parseBar <*> parseBaz
如果我们假设<**> = flip <*>
存在(且具有正确的关联性),那么我们有
parseFoobar = parseBaz <**> parseBar <**> parseFoo <**> return Foobar
这看起来很奇怪。最后的函数和反向的参数?你为什么要这样写呢? (请注意,任何效果也的顺序相反。)
在monadic版本中,效果从上到下发生。在应用版本中,效果从左到右发生。这似乎是“自然的”。