Haskell Quiz No.13

難易度: λλ

以下のプログラムの実行結果はどうなるでしょうか?

#!/usr/bin/env stack
-- stack script --resolver lts-12.0
import Conduit

main :: IO ()
main = runConduit $ yieldMany [1..10] .| iterMC print .| return () .| sinkNull

答えは次回

はじめに

前回の問題と答えは以下の通りです。

問題

難易度: λ

以下のプログラムの実行結果はどうなるでしょうか?

#!/usr/bin/env stack
-- stack script --resolver lts-11.17
import Conduit

main :: IO ()
main = runConduit $ yieldMany [1..10] .| iterMC print .| sinkNull

こたえ

$ ./Quiz12.hs
1
2
3
4
5
6
7
8
9
10

解説

Quiz10 でよく似た問題を出題しました。

# Quiz10
main :: IO ()
main = runConduit $ yieldMany [1..10] .| iterMC print .| return ()

# Quiz12
main :: IO ()
main = runConduit $ yieldMany [1..10] .| iterMC print .| sinkNull

異なるのは最後の return ()sinkNull だけです。

Quiz10 の結果は何も表示されませんでしたが、今回は 1 ~ 10 の数字が表示されています。

この挙動を理解するために sinkNull の実装を確認してみましょう。

sinkNull

sinkNull 関数の実装は以下の通りです。

sinkNull :: Monad m => ConduitT i o m ()
sinkNull = awaitForever $ \_ -> return ()

非常にシンプルな実装になっています。 awaitForever の実装は以下の通りです。

awaitForever :: Monad m => (i -> ConduitT i o m r) -> ConduitT i o m ()
awaitForever f = ConduitT $ \rest ->
    let go = NeedInput (\i -> unConduitT (f i) (const go)) rest
     in go

つまり、上流に対して値を要求し、その値に対して、引数として与えられた関数 f を適用するという関数です。 Forever という名前の通り、この関数は上流の値が無くなるまで値を要求し続けます。(そのため、yieldMany [1..] のような無限リストの場合は停止しません。)

await 関数はこのような定義だったので、とても似ていますね。

await :: Monad m => ConduitT i o m (Maybe i)
await = ConduitT $ \f -> NeedInput (f . Just) (const $ f Nothing)

まとめ

  • sinkNull は上流で用意した値を全て処理するために使うと便利
  • await は上流の値を一度だけ要求する
  • awaitForever は上流の値が無くなるまで要求する

以上です。