在Haskell中使用相同的输入加入两个IO操作

时间:2010-12-29 04:50:03

标签: haskell

我有两个功能:

emptyDirectory, copyStubFileTo :: FilePath -> IO ()

我想按照以下方式组合它们:

forM_ ["1", "2"] $\n -> do
  emptyDirectory n
  copyStubFileTo n

Haskell内置了其他标准方法来简化这种组合吗? 我的意思是加入两个IO动作并给它们相同的输入。

5 个答案:

答案 0 :(得分:11)

liftA2 (>>) emptyDirectory copyStubFileTo

答案 1 :(得分:7)

简短的回答是,不,没有标准的方法。稍微长一点的答案是你可以自己写一个>&>组合器:

(>&>) :: 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没有为类型签名提供任何内容,所以我认为假设它不存在是安全的。


现在,这不是我>&>的原始实现,因为我最初用类似的非monadic组合(&) :: (a -> b) -> (a -> c) -> a -> (b,c)来看待它,我发现自己重新实现了一些规律性。如果你接近这个非monadically然后概括,你最终得到我认为是有用的组合器(我一直在重新发明)的集合,似乎不存在任何标准(尽管我觉得至少有一些它们应该)。关于你想要一些更通用的组合器的可能性,它们在这里;这些似乎都不存在于Hoogle上。 (选择适当的优先级留给感兴趣的读者练习。)

首先,您需要(&) :: (a -> b) -> (a -> c) -> a -> (b,c),这是您想要的非monadic版本。你不能合并bc,因为它们是任意类型,所以我们返回一个元组。这种类型只有一个合理的功能:(f & g) x = (f x, g x)。如果我们通过pointfree提供此实施,我们会得到更好的结果:

(&) :: Monad m => m a -> m b -> m (a,b)
(&) = liftM2 (,)

这适用于函数,因为(r ->)是monad(读者monad); (&)捕获了做两件事并收集两个结果的概念,对于(r ->),“做某事”正在评估一个函数。

但是,有了这个,你就有了emptyDirectory & copyStubFileTo :: FilePath -> (IO (), IO ())。 OOG。因此,我们想要将monad从元组中提取出来,因此我们需要一个函数tupleM :: Monad m => (m a, m b) -> m (a,b)。自己写这个,它使用上面的&函数:

tupleM :: Monad m => (m a, m b) -> m (a,b)
tupleM = uncurry (&)

如果你看一下这些类型,这实际上是有道理的,虽然它可能需要一些读取(它对我来说)。

现在,我们可以为monadic函数定义(&)的版本:

(<&>) :: 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`。)最有趣的是,我们已经达到{完全不同的{ {1}}。第一个实现侧重于>&>的“做两件事”;第二个侧重于“评估两个功能”方面。第二个实际上是我想到的第一个实现;我没有尝试编写第一个实现,因为我假设它会很难看。可能有一个教训: - )

答案 2 :(得分:6)

免责声明:我认为您的原始代码非常易读,重复变量n两次没有错。

也就是说,如果你想获得幻想,((->) a)形成一个Applicative实例,那么你可以这样做:

import Control.Applicative

forM_ ["1", "2"] $ (>>) <$> emptyDirectory <*> copyStubFileTo

答案 3 :(得分:4)

您可以尝试mapM_

之类的内容
mapM_ ($input) [list of functions]

所以在你的情况下:

mapM_ forM_ [emptyDirectory, copyStubFileTo] . flip ($) $ ["1", "2"] 

未测试。但应该是类似的。

答案 4 :(得分:0)

我仍然不确定我是完全理解这一点,但在这里。 (基本上,我从FUZxxl那里偷了这个想法,但是简化并解释了它)

mapM_ (forM_ [list of inputs]) [list of functions]

例如

mapM_ (forM_ ["foo", "bar"]) [putStrLn, putStrLn . reverse]

可生产

foo
bar
oof
rab

这可以概括

xs >.> fs = mapM_ (forM_ xs) fs

所以这会产生相同的输出

main = ["foo","bar"] >.> [putStrLn, putStrLn . reverse]

另外,>.>是一个有趣的脸。

基本上,mapM_接受一个函数和一个列表,并将该函数应用于列表的每个成员。因此,我们创建了具有(forM_ ["foo", "bar"])类型的函数(Monad m) => (String -> m b) -> m ()。它希望输入一个函数。幸运的是,第二个清单的每个成员都是一个功能!因此,第二个列表中的每个函数都将被馈送到此高阶函数,从而产生所需的效果。