Haskell Quiz No.7

難易度: λλ

以下の Conduit を使ったコードの実行結果を予想してみてください!

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

myTakeWhileC :: Monad m => (i -> Bool) -> ConduitM i i m ()
myTakeWhileC f = loop
  where
    loop = do
      mx <- await
      case mx of
        Nothing -> return ()
        Just x
          | f x       -> yield x >> loop
          | otherwise -> return ()

main :: IO ()
main = print $ runConduitPure $ yieldMany [1..10] .| do
  x <- myTakeWhileC (<= 5) .| sinkList
  y <- sinkList
  return (x, y)

答えは次回

はじめに

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

問題

難易度: λλ

以下の Conduit を使ったコードの実行結果を予想してみてください!

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

trans :: Monad m => ConduitM Int Int m ()
trans = do
  takeC 5 .| mapC (+ 1)
  mapC (* 2)

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

こたえ

実際に実行してみましょう!

$ ./Quiz6.hs
2
3
4
5
6
12
14
16
18
20

Haskell Quiz No.6 の解説

この問題で重要なのは trans 関数です。

trans :: Monad m => ConduitM Int Int m ()
trans = do
  takeC 5 .| mapC (+ 1)
  mapC (* 2)
  • 1つめのパイプ takeC 5 .| mapC (+ 1) によって [1..10] のうち [1..5]mapC (+ 1) によって処理されます。
  • 2つめのパイプ mapC (* 2) によって残りの [6..10]mapC (* 2) によって処理されます。

そのため、結果として以下のような出力となりました。

2  -- 1+1
3  -- 2+1
4  -- 3+1
5  -- 4+1
6  -- 5+1
12 -- 6*2
14 -- 7*2
16 -- 8*2
18 -- 9*2
20 -- 10*2

大前提として、このパイプにデータが流れるのは mapM_C print によって上流のデータが無くなるまでデータを要求するという操作があるためです。

そのため、 mapM_C printsinkNull にすると何も表示されなくなります。

まとめ

Conduit は何も知らずにいつも通りの感覚で利用すると、直感と違う動きをすることがあるので、簡単な例を通して慣れていくと良いと思います。

以上です。