monad的表现力是否以代码重用为代价?

时间:2016-11-13 12:25:22

标签: haskell functional-programming monads dry reusability

当我比较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计算的经验很少。任何在黑暗中的光都很受欢迎!

3 个答案:

答案 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结果,然后将其提供给putStrLnGHCi> :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一样,我也无法理解你问题的前提。您似乎期望ApplicativeMonadApplicative中的一个会比其他人更好。事实并非如此。我们可以使用Functor而不是使用Monad执行更多操作,使用{{1}}执行更多操作。如果您需要额外的电源,请使用适当强大的级别。如果没有,使用功能较弱的类将导致更简单,更清晰的代码和更广泛的可用实例。

答案 2 :(得分:1)

这个问题的原因是我对仿函数,应用程序和monad类之间关系的理解太明了。我认为这只是重用纯粹的功能。

(<*>)基本上说给我一个功能的应用和一堆价值观的应用,我将根据我的规则应用它们。

(=<<)基本上说给我一个函数(a -> m b)和一个monad,然后我将(a -> m b)提供monad的值并留给(a -> m b)生成并返回包含在同一个monad中的转换值。

当然,应用代码将更简洁,更可重用,因为执行一系列操作的机制仅在(<*>)内定义。然而,应用序列也是某种技巧。所以,当你想控制序列的流动或'#34;形状&#34;在结果的结构中,你需要monad的额外功能,这会导致更详细的代码。

我认为这个问题并不是特别有用,如果人们投票支持结账,我会毫无疑问地删除它。