雖然有點偏離主題,但我認爲我會評論關注點和模塊性的分離。
通常,我們會盡量保持程序的純粹部分與不純(IO
)部分分開。
我們可以讀取帶有不純代碼的Int
s的列表,然後使用純函數處理它以查找最大值,和和長度以計算平均值。
下面,readInts
讀取從stdin Int
s,至它讀取非正值,在列表中返回正Int
S(在IO
)。 maxSumLength
將當前處理的元素的當前最大值,總和和長度作爲元組處理,然後處理下一個元素,並返回一個新的元組,並摺疊下一個元素。最後,main
讀取Int
s的列表,並使用maxSumLength
和(0, 0, 0)
的初始狀態應用嚴格的左側摺疊(foldl'
)來計算最終的最大值,總和和長度。然後打印總和和長度的最大值和平均值。
module Main where
import Data.List (foldl')
readInts :: IO [Int]
readInts = do
i <- read <$> getLine
if i <= 0
then return []
else (i:) <$> readInts
maxSumLength :: (Int, Int, Int) -> Int -> (Int, Int, Int)
maxSumLength (m, s, l) x = (max m x, s+x, l+1)
main :: IO()
main = do
(m, s, l) <- foldl' maxSumLength (0, 0, 0) <$> readInts
putStrLn $ "max=" ++ show m ++ ", avg=" ++ show (fromIntegral s/fromIntegral l)
該代碼比以前更模塊化。我們可以在需要列表Int
s的其他程序中重新使用readInts
。此外,算法的純粹部分不再關心Int
的列表來自哪裏。但是,這個代碼存在問題。當以這種方式編寫時,即使處理代碼在到達時消耗輸入,整個列表也必須在純代碼開始處理之前緩衝在內存中。
這是conduit
軟件包可以提供幫助的地方。 conduit
包允許通過不純的Source
生成一個流,並將其連接到純粹的Consumer
,並允許純代碼與不純代碼交錯。 conduit-combinators
包提供的組合器允許將流處理得像列表一樣(特別是,foldlC
允許我們對導管流執行嚴格的左摺疊而不是列表)。
在下面的代碼,所述readInts
函數現在是Int
的Source
A S,在IO
單子運行。它使用repeatWhileMC
組合器來執行循環和終止測試。純maxSumLength
不變;然而,在main
中,而不是使用foldl'
,我們使用foldlC
摺疊導管流。
module Main where
import Conduit
readInts :: Source IO Int
readInts = repeatWhileMC (read <$> getLine) (> 0)
maxSumLength :: (Int, Int, Int) -> Int -> (Int, Int, Int)
maxSumLength (m, s, l) x = (max m x, s+x, l+1)
main :: IO()
main = do
(m, s, n) <- runConduit (readInts =$= foldlC maxSumLength (0, 0, 0))
putStrLn $ "max=" ++ show m ++ ", avg=" ++ show (fromIntegral s/fromIntegral n)
此代碼將純粹的交錯與maxSumLength
,使他們在創建Int
s的消耗不純readInts
,但不犧牲模塊化。該流可用於其他需要Int
s流的程序,純代碼仍不再關心Int
是從哪裏來的。