2013-01-21 84 views
2

我想編寫一個函數,它可以在Haskell中以寬度優先的方式遞歸列出目錄。 正如你所看到的,我需要一個可以將(a - > IO b)轉換爲IO(a-> b)的函數。看起來很簡單,我無法做到。我想知道該怎麼做或者是否有可能。如何在Haskell中使用 - > 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 = ?????????? 
+2

我一般你不能那樣做。但我認爲你不需要這裏。我想你可以在這裏使用'getDirectoryContents'' – Satvik

+0

是的,但沒有(a - > IO b) - > IO(a - > b)我必須編寫iterate,concatMap等Monadic版本。也許有一個簡單的方法來編寫(a - > IO b) - > IO(a - > b),這將帶來很多好處。 – TorosFanny

+0

@TorosFanny這是要走的路。你的一些combinators需要是monadic。 – sdcvvc

回答

5

這是無法完成的。原因是該函數可以使用其類型爲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 c b) -> Either c (a -> b)。關鍵在於,對於a -> m b,我們可以根據a選擇不同的效果,而在m (a -> b)中,效果必須是固定的。

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] 

但是這個函數在你的用例中並沒有多大的幫助。

4

您無法以安全的方式創建此類功能。假設我們有f :: a -> IO bg = theConvert f :: IO (a -> b)。它們是兩個非常不同的函數f是一個函數,它接受類型爲a的參數並返回IO動作,結果爲b,其中io動作可能取決於給定的參數。另一方面,g是一個IO動作,結果是一個函數a->b,io動作不能依賴於任何參數。 現在來說明這個問題讓LOOKAT

theConvert putStr :: IO (String ->()) 

現在什麼都要當它的運行,也一定無法打印給定的參數,因爲它沒有任何論據它做。因此,與putStr不同,它只能執行一個操作,然後返回String ->()類型的函數,該函數只有一個選項​​(假設不使用errorundefined)。


正如一個側面沒有,周圍的其他方法可以做到的,它增加所產生的動作取決於參數,而實際上並沒有概念。它可以雖然它適用於任何單子,或者在應用形式theOtherConvert m x = m <*> pure x寫成

theOtherConvert :: IO (a -> b) -> (a -> IO b) 
theOtherConvert m x = m >>= \f -> return $ f x 

3

切赫Pudlá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是「一元」,而IO (a -> b)是「應用性」,意思是,你需要一元操作撰寫看起來像a -> IO b類型,但只有應用性操作爲IO (a -> b)

Monad和之間的「權力」的區別的著名直觀的聲明:

  • 適用性計算具有固定的靜態結構;提前知道將執行什麼操作,執行什麼命令以及如何組合結果。
  • 一元計算沒有這樣一個固定的靜態結構;一次性計算可以檢查來自其一個子動作的結果值,然後在執行時間在不同結構之間進行選擇。

彼得的回答就是這一點的具體說明。我會重複的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)

類似的言論適用於奧古斯特的迴應。通過要求提前指定一個值列表,它所做的是讓你選擇提前分支的分支,把它們全部拿走,然後讓你在它們的結果之間進行選擇。