我想编写一个函数,可以在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 = ??????????
答案 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 b
和g = 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
来概括,并从Applicative
和Monad
类型的角度来看待它
考虑来自Applicative
和Monad
的“合并”操作的类型:
(<*>) :: 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)
关于Monad
和Applicative
之间“权力”差异的一个众所周知的直观陈述:
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)
,除非你预先确定将采取哪个分支。
类似的评论适用于奥古斯都的回应。通过要求提前指定值列表,它正在做的是让您选择提前采取哪些分支,全部采用它们,然后允许您在结果之间进行选择。