[英]Haskell GHCi - Using EOF character on stdin with getContents
[英]Reimplementing getContents using getChar
在我對Haskell中抓住懶惰IO的哀悼中,我嘗試了以下方法:
main = do
chars <- getContents
consume chars
consume :: [Char] -> IO ()
consume [] = return ()
consume ('x':_) = consume []
consume (c : rest) = do
putChar c
consume rest
它只是回顯了所有在stdin中鍵入的字符,直到我點擊'x'。
所以,我天真地認為應該可以使用getChar
重新實現getContents
, getChar
執行以下操作:
myGetContents :: IO [Char]
myGetContents = do
c <- getChar
-- And now?
return (c: ???)
原來它不是那么簡單,因為???
需要IO [Char] -> [Char]
類型的函數,我認為這會打破IO monad的整個想法。
檢查getContents
(或者更確切地說是hGetContents
)的實現揭示了整個香腸工廠的臟IO東西。 我的假設是否正確, myGetContents
不使用臟的,即monad-breaking代碼, myGetContents
就無法實現?
您需要一個新的原語unsafeInterleaveIO :: IO a -> IO a
,它會延遲其參數操作的執行,直到評估該操作的結果。 然后
myGetContents :: IO [Char]
myGetContents = do
c <- getChar
rest <- unsafeInterleaveIO myGetContents
return (c : rest)
如果可能的話,你應該真的避免在System.IO.Unsafe
使用任何東西。 除非絕對必要,否則它們往往會破壞參照透明度並且不是Haskell中常用的函數。
如果你稍微改變你的類型簽名,我懷疑你可以對你的問題采取一種更慣用的方法。
consume :: Char -> Bool
consume 'x' = False
consume _ = True
main :: IO ()
main = loop
where
loop = do
c <- getChar
if consume c
then do
putChar c
loop
else return ()
如果您的目標只是將所有stdin
讀入String
,則不需要任何unsafe*
函數。
IO
是Monad,Monad是應用程序的Functor。 Functor由函數fmap
定義,其簽名為:
fmap :: Functor f => (a -> b) -> f a -> f b
滿足這兩個定律:
fmap id = id
fmap (f . g) = fmap f . fmap g
實際上, fmap
將函數應用於包裝值。
給定一個特定的字符'c'
, fmap ('c':)
的類型是fmap ('c':)
? 我們可以將這兩種類型寫下來,然后將它們統一起來:
fmap :: Functor f => (a -> b ) -> f a -> f b
('c':) :: [Char] -> [Char]
fmap ('c':) :: Functor f => ([Char] -> [Char]) -> f [Char] -> f [Char]
回想一下IO
是一個myGetContents :: IO [Char]
函數,如果我們要定義myGetContents :: IO [Char]
,使用它似乎是合理的:
myGetContents :: IO [Char]
myGetContents = do
x <- getChar
fmap (x:) myGetContents
這是接近的,但不完全等同於getContents
,因為此版本將嘗試讀取文件末尾並拋出錯誤而不是返回字符串。 只要看一下它就應該說清楚:沒有辦法返回一個具體的清單,只有一個無限的連鎖鏈。 知道EOF的具體情況是""
(並使用fmap
的中綴語法<$>
),我們將:
import System.IO
myGetContents :: IO [Char]
myGetContents = do
reachedEOF <- isEOF
if reachedEOF
then return []
else do
x <- getChar
(x:) <$> myGetContents
回想一下IO
是一個Applicative Functor,而不僅僅是任何舊的Functor。 這個類型類有“應用法則”,就像“Functor Laws”一樣,但我們會特別注意<*>
:
<*> :: Applicative f => f (a -> b) -> f a -> f b
這與fmap
(aka <$>
)幾乎完全相同,只是應用的函數也被包裝。 然后我們可以通過使用Applicative樣式來避免在我們的else
子句中綁定:
import System.IO
myGetContents :: IO String
myGetContents = do
reachedEOF <- isEOF
if reachedEOF
then return []
else (:) <$> getChar <*> myGetContents
還記得當我說你不想unsafe*
函數時,如果你只是想把所有的stdin
讀成一個String
嗎? 好吧,如果你只想要一些輸入,你就可以。 如果您的輸入可能無限長,那么您肯定會這樣做。 最終的程序在一個導入和一個單詞中有所不同:
import System.IO
import System.IO.Unsafe
myGetContents :: IO [Char]
myGetContents = do
reachedEOF <- isEOF
if reachedEOF
then return []
else (:) <$> getChar <*> unsafeInterleaveIO myGetContents
惰性IO的定義函數是unsafeInterleaveIO
(來自System.IO.Unsafe
)。 這會延遲IO
動作的計算,直到需要它為止。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.