Haskell Quiz No.10

難易度: λ

以下の実行結果はどうなるでしょう!

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

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

答えは次回

はじめに

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

問題

難易度: λλ

以下の2つのコードのうち、1つめはコンパイルできますが、2つめはコンパイルできません。

なぜでしょう!

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

main :: IO ()
main = print $ runConduitPure $ return () .| do
  mapM_ leftover [1..10]
  sinkList
#!/usr/bin/env stack
-- stack script --resolver lts-11.16
import Conduit

main :: IO ()
main = print $ runConduitPure $ do
  mapM_ leftover [1..10]
  sinkList

エラーメッセージ

error:
    • No instance for (Num ()) arising from the literal ‘1’
    • In the expression: 1
      In the second argument of ‘mapM_’, namely ‘[1 .. 10]’
      In a stmt of a 'do' block: mapM_ leftover [1 .. 10]

    mapM_ leftover [1..10]

解説

先に2つ目の例がエラーとなってしまう理由を確認します。

まずは関数の型を確認しておきましょう。

runConduitPure :: ConduitT () Void Identity r -> r
leftover :: i -> ConduitT i o m ()
sinkList :: Monad m => ConduitT a o m [a]

mapM_ :: (Foldable t, Monad m) => (a -> m b) -> t a -> m ()

mapM_ leftover :: t i -> ConduitT i o m ()
mapM_ leftover [1..10] :: (Enum i, Num i) => ConduitT i o m ()

次にモナドの型クラスも一応確認しておきます。

class Applicative m => Monad (m :: * -> *) where
  return :: a -> m a
  (>>=)  :: m a -> (a -> m b) -> m b
  (>>)   :: m a -> m b -> m b

さらに型を確認していきます。

mapM_ leftover [1..10] >> sinkList
  :: (Monad m, Enum i, Num i) =>  ConduitT i o m [i]

さて、ここまで確認するとなぜ2つ目の結果がエラーとなることがわかります。

runConduitPure :: ConduitT () Void Identity r -> r
mapM_ leftover [1..10] >> sinkList
  :: (Monad m, Enum i, Num i) =>  ConduitT i o m [i]

ConduitT のパラメータはそれぞれ以下のような具体的な型になります。

  • i = ()
  • o = Void
  • m = Identity
  • r = [i]

さらに mi にはクラス制約があるため以下のインスタンス定義が要求されます。

  • instance Monad Identity
  • instance Enum ()
  • instance Num ()

ここで Num クラスのインスタンス定義には () が含まれないため、エラーとなってしまいました。

1つ目の例がエラーにならない理由

ではなぜ return () .| が追加されるとエラーにならないのでしょうか?

型を確認してみます。

(.|)   :: Monad m => ConduitT a b m () -> ConduitT b c m r -> ConduitT a c m r
return :: Monad m => a -> m a
return ()      :: Monad m => ConduitT i o m ()
(return () .|) :: Monad m => ConduitT o c m r -> ConduitT i c m r
mapM_ leftover [1..10] >> sinkList
  :: (Monad m, Enum i, Num i) =>  ConduitT i o m [i]
(return () .| mapM_ leftover [1..10] >> sinkList)
  :: Monad m => ConduitT i o m [i]

2つ目の例では runConduitPure に直接適用してしまったため、型クラス制約が必要になりましたが、今回は return () と合成した結果を runConduitPure に適用するため、この問題を回避することができています。

まとめ

  • runConduitPure に適用する時に今回のようなエラーが出てしまう場合は return () .| として合成してから適用すると良い。

以上です。