[英]How to make a IO (a->b) function from a -> IO b in Haskell
我想編寫一個函數,可以在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 = ??????????
一般情況下,您不能以純粹的方式執行此操作,但如果您可以枚舉所有參數值,則可以預先執行所有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]
但是這個功能對你的用例並沒有多大幫助。
這是不可能做到的。 原因是該函數可以使用其類型a
參數來確定執行的IO
操作。 考慮
action :: Bool -> IO String
action True = putStrLn "Enter something:" >> getLine
action False = exitFailure
現在如果你將它以某種方式轉換為IO (Bool -> String)
並評估此動作,會發生什么? 沒有解決方案。 我們無法決定是否應該讀取字符串或退出,因為我們還不知道Bool
參數(如果沒有在參數上調用結果函數,我們可能永遠不會知道它)。
約翰的回答是個壞主意。 它只是讓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 cb) -> Either c (a -> b)
。 關鍵在於a -> mb
我們可以根據a
選擇不同的效果,而在m (a -> b)
,效果必須固定。
您無法以安全的方式創建此類功能。 假設我們有f :: a -> IO b
和g = theConvert f :: IO (a -> b)
。 它們是兩個非常不同的函數f
是一個函數,它接受類型為a
的參數並返回帶有結果b
的IO
動作,其中io-action可能取決於給定的參數。 另一方面, g
是一個IO
動作,結果是函數a->b
,io-action不能依賴於任何參數。 現在來說明這個問題讓我們看看
theConvert putStr :: IO (String -> ())
現在它應該在運行時做什么,它肯定不會打印給定的參數,因為它沒有參數。 因此,與putStr不同,它只能執行一個操作,然后返回一些String -> ()
類型的函數,它只有一個選項const ()
(假設沒有使用error
或undefined
)。
只是作為一個方面沒有,反過來可以做到,它增加了結果動作取決於參數的概念,而實際上沒有。 它可以寫成
theOtherConvert :: IO (a -> b) -> (a -> IO b)
theOtherConvert m x = m >>= \f -> return $ f x
雖然它適用於任何monad,或以適用的形式使用其他theOtherConvert mx = m <*> pure x
。
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)
是“applicative” - 意味着你需要monadic操作來組成看起來像a -> IO b
,但只是應用IO (a -> 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)
除非您同時預先確定將采用哪個分支。
類似的評論適用於奧古斯都的回應。 通過要求提前指定值列表,它正在做的是讓您選擇提前采取哪些分支,全部采用,然后允許您在結果之間進行選擇。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.