为什么< $>和< *>以>> =?的相反顺序获取输入

时间:2014-09-24 11:12:24

标签: haskell parameters monads functor applicative

我理解<$>类型签名背后的原因,因为它只是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}}呢?

2 个答案:

答案 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版本中,效果从上到下发生。在应用版本中,效果从左到右发生。这似乎是“自然的”。