如何从 - >制作IO(a-> b)功能在Haskell中的IO b

时间:2013-01-21 05:04:56

标签: function haskell tree monads

我想编写一个函数,可以在Haskell中以广度优先的方式递归列出目录。 如您所见,我需要一个可以将(a - > IO b)转换为IO(a-> b)的函数。看似简单,我无法做到。我想知道该怎么做或是否有可能。

dirElem :: FilePath -> IO [FilePath]
dirElem dirPath = do
  getDirectoryContents'' <- theConvert getDirectoryContents'
  return $ takeWhile (not.null) $ iterate (concatMap getDirectoryContents'') [dirPath] where
    getDirectoryContents' dirPath = do
      isDir <- do doesDirectoryExist dirPath
      if isDir then dirContent else return [] where
        dirContent = do
          contents <- getDirectoryContents dirPath
          return.(map (dirElem</>)).tail.tail contents
    theConvert :: (a -> IO b) -> IO (a -> b)
    theConvert = ??????????

4 个答案:

答案 0 :(得分:6)

一般情况下,您不能以纯粹的方式执行此操作,但如果您可以枚举所有参数值,则可以预先执行所有IO并返回纯函数。像

这样的东西
cacheForArgs :: [a] -> (a -> IO b) -> IO (a -> b)
cacheForArgs as f = do
    bs <- mapM f as
    let abs = zip as bs
    return $ \ a -> fromMaybe (error "argument not cached") $ lookup a abs

cacheBounded :: (Enum a, Bounded a) => (a -> IO b) -> IO (a -> b)
cacheBounded = cacheForArgs [minBound .. maxBound]

但是这个功能对你的用例并没有多大帮助。

答案 1 :(得分:5)

无法完成此操作。原因是该函数可以使用其a类型的参数来确定执行的IO操作。考虑

action :: Bool -> IO String
action True  = putStrLn "Enter something:" >> getLine
action False = exitFailure

现在,如果你以某种方式将其转换为IO (Bool -> String)并评估此操作,会发生什么?没有解决方案。我们无法决定是否应该读取字符串或退出,因为我们还不知道Bool参数(如果没有在参数上调用结果函数,我们可能永远不会知道它。)

John的答案是糟糕的想法。它只是让IO动作逃避纯粹的计算,这将使你的生活变得悲惨,你将失去Haskell的参考透明度!例如跑步:

main = unsafe action >> return ()
即使调用IO操作,

也不会执行任何操作。而且,如果我们稍微修改一下:

main = do
   f <- unsafe action
   putStrLn "The action has been called, calling its pure output function."
   putStrLn $ "The result is: " ++ f True

你会看到要求输入的action在纯计算中执行,在调用f内部。您无法保证何时(如果有的话)执行该操作!

编辑:正如其他人所指出的那样,它并不仅仅针对IO。例如,如果monad为Maybe,则无法实现(a -> Maybe b) -> Maybe (a -> b)。或者对于Either,您无法实施(a -> Either c b) -> Either c (a -> b)。关键是a -> m b我们可以根据a选择不同的效果,而m (a -> b)则必须修复效果。

答案 2 :(得分:4)

您无法以安全的方式创建此类功能。假设我们有f :: a -> IO bg = theConvert f :: IO (a -> b)。它们是两个非常不同的函数f是一个函数,它接受a类型的参数并返回结果为IO的{​​{1}}动作,其中io-action可能依赖于给出的论点。另一方面,b是一个g动作,结果是一个函数IO,io-action不能依赖于任何参数。 现在来说明这个问题让我们看看

a->b

现在它应该在运行时做什么,它肯定不会打印给定的参数,因为它没有参数。因此,与putStr不同,它只能执行一个操作,然后返回类型theConvert putStr :: IO (String -> ()) 的某个函数,该函数只有一个选项String -> ()(假设不使用const ()error)。


只是作为一个方面没有,反过来可以做到,它增加了结果动作取决于参数的概念,而实际上并没有。它可以写成

undefined

虽然它适用于任何monad,或适用形式theOtherConvert :: IO (a -> b) -> (a -> IO b) theOtherConvert m x = m >>= \f -> return $ f x

答案 3 :(得分:3)

PetrPudlák的回答非常好,但我觉得可以通过抽象IO来概括,并从ApplicativeMonad类型的角度来看待它

考虑来自ApplicativeMonad的“合并”操作的类型:

(<*>) :: Applicative m => m (a -> b) -> m a -> m b
(>>=) :: Monad m => m a -> (a -> m b) -> m b

所以你可以说你的类型a -> IO b是“monadic”而IO (a -> b)是“适用的” - 意味着你需要monadic操作来组成类似a -> IO b的类型,但仅限于IO (a -> b)

的适用操作

关于MonadApplicative之间“权力”差异的一个众所周知的直观陈述:

  • 应用计算具有固定的静态结构;将会执行哪些操作,将执行它们的顺序以及结果合并的方式是提前知道的。
  • Monadic计算没有这样一个固定的静态结构; monadic计算可以检查其中一个子动作的结果值,然后在执行时选择不同的结构。
彼得的回答是这一点的具体例证。我将重复他对action

的定义
action :: Bool -> IO String
action True  = putStrLn "Enter something:" >> getLine
action False = exitFailure

假设我们有foo :: IO Bool。然后,当我们编写foo >>= action以将action的参数绑定到foo的结果时,得到的计算不会比我的第二个项目点所描述的更少;它检查执行foo的结果,并根据其值在备选操作之间进行选择。这正是Monad允许您做的事情之一Applicative没有做到的事情。你不能将Petr的action改为IO (Bool -> String),除非你预先确定将采取哪个分支。

类似的评论适用于奥古斯都的回应。通过要求提前指定值列表,它正在做的是让您选择提前采取哪些分支,全部采用它们,然后允许您在结果之间进行选择。