少し話題はありますが、私は懸念とモジュール性の分離についてコメントしたいと思いました。
通常、プログラムの純粋な部分を純粋でない部分(IO
)と分けて保存しようとします。
純粋でないコードを持つInt
のリストを読んでから、純粋な関数で処理して平均を計算するために最大、合計、および長さを見つけることができます。以下
それは(IO
に)リストに正Int
Sを返し、非正の値を読み取るまで、readInts
はSTDINからInt
Sを読み出します。 maxSumLength
は、これまでに処理された要素の現在の最大値、合計値、および長さをタプルとして扱い、次の要素を折りたたんで新しいタプルを返します。最後にmain
はInt
のリストを読み取り、 maxSumLength
を使用して厳密な左フォールド(foldl'
)を適用し、最終状態の最大、合計、および長さを計算するために(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)
このコードは以前よりもモジュール化されています。 Int
のリストを必要とする他のプログラムでは、readInts
を再利用することができます。また、アルゴリズムの純粋な部分はもはやInt
のリストがどこから来るかを気にしません。しかし、このコードには問題があります。このように記述すると、処理コードが到着したときに入力を消費することができても、純粋なコードが処理を開始する前に、リスト全体をメモリにバッファリングする必要があります。
これは、conduit
パッケージが役立つところです。 conduit
パッケージを使用すると、不純なストリームを生成し、Consumer
に接続して、純粋なコードを不純なコードとインターリーブすることができます。 conduit-combinators
パッケージでは、ストリームをリストのように扱うことができるコンビネータが用意されています(特に、foldlC
はリストの代わりにコンジットストリームで厳密な左折を実行できます)。以下のコードで
、readInts
機能についてIO
モナドで実行Source
Int
のsです。 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)
このコードは、それらが作成されるようInt
sが消費されるように、不純なreadInts
で純粋maxSumLength
インターリーブが、モジュール性を犠牲にすることなくなります。 readInts
ストリームは、Int
のストリームを必要とする他のプログラムで使用することができます。純粋なコードは、Int
がどこから来ているかにはまだ気付きません。