简短的回答是不,没有标准的方法。稍长一点的答案是你可以自己写一个>&> 组合器:
(>&>) :: Monad m => (a -> m b) -> (a -> m c) -> a -> m c
(f >&> g) x = f x >> g x
正如您从类型中看到的那样,这完全符合您的要求。使用这个,你有emptyDirectory >&> copyStubFileTo :: FilePath -> IO (),所以
mapM_ (emptyDirectory >&> copyStubFileTo) ["1", "2"]
不幸的是,Hoogle 没有为类型签名提供任何信息,因此我认为可以安全地假设它不存在。
现在,这不是我对>&> 的原始实现,因为我最初是根据类似的非单子组合器(&) :: (a -> b) -> (a -> c) -> a -> (b,c) 来看待它的,我发现我自己重新实现了一些规律性。如果你非单子地处理这个然后泛化,你最终会得到我认为是有用的组合器的集合(我一直在重新发明),这些组合器似乎不存在任何标准(即使我觉得至少其中一些)应该)。如果您想要一些更通用的组合器,它们就在这里;这些似乎都不存在于 Hoogle 上。 (选择适当的优先级留给感兴趣的读者作为练习。)
首先,您需要(&) :: (a -> b) -> (a -> c) -> a -> (b,c),这是您想要的非单子版本。你不能合并b 和c,因为它们是任意类型,所以我们返回一个元组。这种类型只有一个合理的函数:(f & g) x = (f x, g x)。如果我们通过pointfree 提供这个实现,我们会得到更好的东西:
(&) :: Monad m => m a -> m b -> m (a,b)
(&) = liftM2 (,)
这适用于函数,因为(r ->) 是一个单子(阅读器单子); (&) 抓住了做两件事并收集两个结果的概念,对于 (r ->),“做某事”是评估一个函数。
但是,有了这个,您将拥有emptyDirectory & copyStubFileTo :: FilePath -> (IO (), IO ())。哎呀。因此,我们想要将 monad 从元组中取出,因此我们需要一个函数 tupleM :: Monad m => (m a, m b) -> m (a,b)。自己写这个,它使用上面的&函数:
tupleM :: Monad m => (m a, m b) -> m (a,b)
tupleM = uncurry (&)
如果您查看类型,这实际上是有道理的,尽管它可能需要一些阅读(对我来说确实如此)。
现在,我们可以为一元函数定义一个版本的(&):
(<&>) :: Monad m => (a -> m b) -> (a -> m c) -> a -> m (b, c)
f <&> g = tupleM . (f & g)
我们现在有了emptyDirectory <&> copyStubFileTo :: FilePath -> IO ((),()),这是一个改进。但是我们真的不需要那个元组(尽管我们可能会进行更有趣的操作)。相反,我们想要(>&>) :: Monad m => (a -> m b) -> (a -> m c) -> a -> m c(>> 的类似物),这是我们开始定义的(事实上,上面已经定义了)。因为无论如何我都在定义各种组合子,所以我将通过 <.> :: Functor f => (b -> c) -> (a -> f b) -> a -> f c(来自 Control.Monad 的 <=< 的功能类似物)来定义它。
(<.>) :: Functor f => (b -> c) -> (a -> f b) -> a -> f c
(f <.> g) x = f <$> g x
(>&>) :: (Monad m, Functor m) => (a -> m b) -> (a -> m c) -> a -> m c
f >&> g = snd <.> (f <&> g)
(如果您不喜欢Functor m 约束,请将<$> 替换为`liftM`。)最有趣的是,我们得到了一个完全不同的>&> 实现。第一个实现侧重于>&> 的“做两件事”方面;第二个重点是“评估两个功能”方面。这第二个实际上是我想到的第一个实现;我没有尝试编写第一个实现,因为我假设它会很丑。这可能是一个教训:-)