簡體   English   中英

如何在Haskell中從 - > IO b創建IO(a-> b)函數

[英]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 bg = theConvert f :: IO (a -> b) 它們是兩個非常不同的函數f是一個函數,它接受類型為a的參數並返回帶有結果bIO動作,其中io-action可能取決於給定的參數。 另一方面, g是一個IO動作,結果是函數a->b ,io-action不能依賴於任何參數。 現在來說明這個問題讓我們看看

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

現在它應該在運行時做什么,它肯定不會打印給定的參數,因為它沒有參數。 因此,與putStr不同,它只能執行一個操作,然后返回一些String -> ()類型的函數,它只有一個選項const () (假設沒有使用errorundefined )。


只是作為一個方面沒有,反過來可以做到,它增加了結果動作取決於參數的概念,而實際上沒有。 它可以寫成

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

雖然它適用於任何monad,或以適用的形式使用其他theOtherConvert mx = m <*> pure x

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)是“applicative” - 意味着你需要monadic操作來組成看起來像a -> IO b ,但只是應用IO (a -> 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)除非您同時預先確定將采用哪個分支。

類似的評論適用於奧古斯都的回應。 通過要求提前指定值列表,它正在做的是讓您選擇提前采取哪些分支,全部采用,然后允許您在結果之間進行選擇。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM