如何在Monad中不使用do表示法合并结果?
解决方案
当然存在这类合并方式,而且不止一种,分两种场景来说:
1. 针对任意二元合并函数(比如你的(++))
利用Applicative/Monad的特性,就能把二元函数作用在两个Monadic值上,无需do表示法:
写法一:用liftA2
来自Control.Applicative(Monad是Applicative的子类,所有Monad都适用),类型签名为:
liftA2 :: Applicative f => (a -> b -> c) -> f a -> f b -> f c
你的代码可以简化成一行:
foobar = liftA2 (++) getFoo getBar
写法二:用<$>和<*>组合
这是Applicative风格的标准写法,和liftA2等价,更直观:
foobar = (++) <$> getFoo <*> getBar
<$>把普通函数(++)提升为作用在Monadic值上的函数,<*>再传入第二个Monadic值,最终得到合并后的结果。
2. 当结果类型是Monoid实例时
如果foo和bar的类型属于Monoid(比如String、List、Map等,(++)就是String/List的mappend),可以直接用Monoid的合并运算符<>(来自Data.Semigroup,Haskell 8.0及以上可用)或者mappend(来自Data.Monoid),结合Applicative写法:
import Data.Semigroup ((<>)) foobar = (<>) <$> getFoo <*> getBar
或者用liftA2:
foobar = liftA2 (<>) getFoo getBar
为什么Hoogle搜不到你要的类型?
标准库中没有单独定义Monad m, Monoid a => m a -> m a -> m a这个类型的函数,因为它可以通过liftA2 <>直接组合出来,不需要单独封装。如果你常用,可以自己定义一个运算符:
import Control.Applicative (liftA2) import Data.Semigroup ((<>)) (<>>) :: (Monad m, Monoid a) => m a -> m a -> m a (<>>) = liftA2 (<>)
之后就能直接用foobar = getFoo <>> getBar了。
内容的提问来源于stack exchange,提问作者Tomas Aschan
相关产品推荐
相关产品推荐

