虽然有点离题,但我想我会评论关注点分离和模块化。
通常,我们会尝试将程序的纯部分与不纯 (IO) 部分分开。
我们可以读取包含不纯代码的Ints 列表,然后使用纯函数对其进行处理以找到最大值、总和和长度以计算平均值。
下面,readInts 从标准输入读取Ints,直到它读取一个非正值,在列表中返回正的Ints(在IO 中)。 maxSumLength 获取目前处理的元素的当前最大值、总和和长度
作为一个元组,以及要处理的下一个元素,并返回一个包含下一个元素的新元组。最后,main 读取 Ints 的列表,并使用 @ 应用严格的左折叠 (foldl') 987654331@ 和 (0, 0, 0) 的初始状态来计算最终的最大值、总和和长度。然后它会根据总和和长度打印最大值和平均值。
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)
此代码比以前更加模块化。我们可以在需要Ints 列表的其他程序中重用readInts。此外,算法的纯部分不再关心Ints 列表的来源。但是,此代码存在问题。以这种方式编写时,必须在纯代码开始处理之前将整个列表缓冲在内存中,即使处理代码可以在输入到达时使用它。
这就是conduit 包可以提供帮助的地方。 conduit 包允许由不纯的Source 生成流并连接到纯的Consumer,并允许纯代码与不纯代码交错。 conduit-combinators 包提供了组合器,允许像列表一样对待流(特别是,foldlC 允许我们对管道流而不是列表执行严格的左折叠)。
在下面的代码中,readInts 函数现在是在 IO monad 中运行的 Ints 的 Source。它使用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 与不纯readInts 交错,以便Ints 在创建时被使用,但不会牺牲模块化。 readInts流可以用在其他需要Ints流的程序中,纯代码仍然不再关心Ints来自哪里。