当我比较Applicative和Monad类型的二进制操作
时(<*>) :: Applicative f => f (a -> b) -> f a -> f b
(=<<) :: Monad m => (a -> m b) -> m a -> m b
两个不同之处显而易见:
ap
期望一个正常的纯函数,而bind
期望一个monadic动作,它必须返回一个相同类型的monad ap
行动顺序由应用程序确定,而bind
行动可以确定控制流程所以monad给了我额外的表现力。但是,由于它们不再接受正常的纯函数,这种表达性似乎是以代码重用为代价的。
我的结论可能有点幼稚甚至错误,因为我对Haskell和monadic计算的经验很少。任何在黑暗中的光都很受欢迎!
答案 0 :(得分:8)
如果你有纯函数g :: a -> b
,你可以使它成为Applicative
版本
pure g :: Applicative f => f (a -> b)
或Monad
ish by
pure . g :: Applicative f => a -> f b
因此,您不会在任何意义上丢失任何代码重用。
答案 1 :(得分:2)
如果您可以重复使用代码来实现您真正想要的功能,那么代码重用只是一个优势。
GHCi> putStrLn =<< getLine
Sulfur
Sulfur
GHCi>
在此处,(=<<)
选择String
上下文IO
生成的getLine
结果,然后将其提供给putStrLn
,GHCi> :t getLine
getLine :: IO String
GHCi> :t putStrLn
putStrLn :: String -> IO ()
GHCi> :t putStrLn =<< getLine
putStrLn =<< getLine :: IO ()
然后打印出结果。< / p>
fmap
现在,类型为(<$>)
/ GHCi> :t (<$>)
(<$>) :: Functor f => (a -> b) -> f a -> f b
...
b
... IO ()
完全有可能成为putStrLn
,因此没有什么可以阻止我们使用GHCi> putStrLn <$> getLine
Sulfur
GHCi>
。然而...
GHCi> :t putStrLn <$> getLine
putStrLn <$> getLine :: IO (IO ())
......什么都不打印。
IO (IO ())
执行IO ()
操作不会执行内部Monad
操作。要做到这一点,我们需要(<$>)
的额外力量,可以将(=<<)
替换为join
,或者等效地使用IO (IO ())
上的GHCi> :t join
join :: Monad m => m (m a) -> m a
GHCi> join (putStrLn <$> getLine)
Sulfur
Sulfur
GHCi>
值:
Functor
像chi一样,我也无法理解你问题的前提。您似乎期望Applicative
,Monad
和Applicative
中的一个会比其他人更好。事实并非如此。我们可以使用Functor
而不是使用Monad
执行更多操作,使用{{1}}执行更多操作。如果您需要额外的电源,请使用适当强大的级别。如果没有,使用功能较弱的类将导致更简单,更清晰的代码和更广泛的可用实例。
答案 2 :(得分:1)
这个问题的原因是我对仿函数,应用程序和monad类之间关系的理解太明了。我认为这只是重用纯粹的功能。
(<*>)
基本上说给我一个功能的应用和一堆价值观的应用,我将根据我的规则应用它们。
(=<<)
基本上说给我一个函数(a -> m b)
和一个monad,然后我将(a -> m b)
提供monad的值并留给(a -> m b)
生成并返回包含在同一个monad中的转换值。
当然,应用代码将更简洁,更可重用,因为执行一系列操作的机制仅在(<*>)
内定义。然而,应用序列也是某种技巧。所以,当你想控制序列的流动或'#34;形状&#34;在结果的结构中,你需要monad的额外功能,这会导致更详细的代码。
我认为这个问题并不是特别有用,如果人们投票支持结账,我会毫无疑问地删除它。