你可以做到這一點,沒有任何黑客。
如果您的目標僅僅是將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
動作的計算,直到它被要求。
來源
2018-02-17 22:19:40
Fox
工程就像一個魅力:)仍然補充說'unsafeInterleaveIO'必須從'System.IO.Unsafe'導入。 – johanneslink