我有两个功能:
emptyDirectory, copyStubFileTo :: FilePath -> IO ()
我想按照以下方式组合它们:
forM_ ["1", "2"] $\n -> do
emptyDirectory n
copyStubFileTo n
Haskell内置了其他标准方法来简化这种组合吗? 我的意思是加入两个IO动作并给它们相同的输入。
答案 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版本。你不能合并b
和c
,因为它们是任意类型,所以我们返回一个元组。这种类型只有一个合理的功能:(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 ()
。它希望输入一个函数。幸运的是,第二个清单的每个成员都是一个功能!因此,第二个列表中的每个函数都将被馈送到此高阶函数,从而产生所需的效果。