2016-12-03 41 views
5

在我朝哈斯克爾抓偷懶IO journing重新實現getContents我試過如下:使用的getchar

main = do 
    chars <- getContents 
    consume chars 

consume :: [Char] -> IO() 
consume [] = return() 
consume ('x':_) = consume [] 
consume (c : rest) = do 
    putChar c 
    consume rest 

剛剛回聲在標準輸入所有鍵入的字符,直到我打「X」。

所以,我天真地以爲它應該有可能使用getChar做大意如下的東西重新實現getContents

myGetContents :: IO [Char] 
myGetContents = do 
    c <- getChar 
    -- And now? 
    return (c: ???) 

原來它不是那麼簡單,因爲???需要IO [Char] -> [Char]類型的函數,它會 - 我認爲 - 打破IO monad的整個想法。

檢查getContents(或更確切地說hGetContents)的實施揭示了一個整個香腸工廠的髒東西。我的假設是否正確,myGetContents不能使用骯髒,即monad-breaking,代碼不能實現?

回答

6

您需要一個新的基元unsafeInterleaveIO :: IO a -> IO a,它延遲其參數操作的執行,直到該操作的結果被評估爲止。然後

myGetContents :: IO [Char] 
myGetContents = do 
    c <- getChar 
    rest <- unsafeInterleaveIO myGetContents 
    return (c : rest) 
+3

工程就像一個魅力:)仍然補充說'unsafeInterleaveIO'必須從'System.IO.Unsafe'導入。 – johanneslink

1

如果可能的話,你應該真的避免使用任何東西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() 
+0

第一段中的建議在大多數情況下都是有意義的,但在這種情況下,它並不適用,因爲OP明確且故意嘗試重新實現惰性I/O。 – duplode

+0

爲了記錄我的回答是基於與原始問題沒有反映的OP的twitter交流。我印象中他們可能沒有明確地在「懶惰IO」之後,只想讓他們的代碼運行。然而,考慮到實際問題的精神,這確實是一個錯誤的答案。 – bojo

0

你可以做到這一點,沒有任何黑客。

如果您的目標僅僅是將stdin全部讀入String,則不需要任何unsafe*函數。

IO是Monad,Monad是Applicative Functor。

fmap :: Functor f => (a -> b) -> f a -> f b 

滿足這兩項法律:

​​

有效地,fmap應用一個函數包裹值函子由函數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 = 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。沒有與此類型類很像「函子定律」相關的「應用型法律」,但我們會在<*>具體看:

<*> :: Applicative f => f (a -> b) -> f a -> f b 

這幾乎是相同的fmap(又名<$>),除了功能申請也包裹。然後,我們可以通過使用應用型風格避免我們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動作的計算,直到它被要求。