💊

bimap であそぶ

2021/02/22に公開

transformers パッケージの WriterT の定義を考えることにします。

newtype WriterT w m a = WriterT { runWriterT :: m (a, w) }

Functor のインスタンス

Functor のインスタンスは簡単にポイントフリーで書けます。

import Data.Bifunctor (first)

instance Functor m => Functor (WriterT w m) where
  fmap :: (a -> b) -> WriterT w m a -> WriterT w m b
  fmap f = WriterT . fmap (first f) . runWriterT

Applicative Functor のインスタンス

次に Applicative Functor のインスタンスを書いてみます。

instance (Applicative m, Monoid w) => Applicative (WriterT w m) where
  pure :: a -> WriterT w m a
  pure a = WriterT $ pure (a, mempty)

この定義をポイントフリーにするのは、さっきよりも大変そうですが、まだまだ簡単。

import Data.Bifunctor (second)
import Control.Monad (join)

instance (Applicative m, Monoid w) => Applicative (WriterT w m) where
  pure :: a -> WriterT w m a
  pure = WriterT . pure . second (const mempty) . join (,)

<*> も定義してみましょう。

instance (Applicative m, Monoid w) => Applicative (WriterT w m) where
  (<*>) :: WriterT w m (a -> b) -> WriterT w m a -> WriterT w m b
  mf <*> ma = WriterT $ h <$> runWriterT mf <*> runWriterT ma
    where
      h (f, w1) (a, w2) = (f a, w1 <> w2)

だいたいこんな感じです。ここで、局所定義した h をポイントフリーで書くにはどうしたら良いでしょうか?

そのためには、まず一般化した h を考えることにします。

bimap

h を一般化すると次のようになるでしょう。

h :: (a1 -> a2 -> a3) -> (b1 -> b2 -> b3) -> (a1, b1) -> (a2, b2) -> (a3, b3)
h f g (a1, b1) (a2, b2) = (f a1 a2, g b1 b2)

タプルの各要素に異なる関数を適用しているため、bimap が使えそうです。

h' :: (a1 -> a2) -> (b1 -> b2) -> (a1, b1) -> (a2, b2)
h' f g = bimap f g

しかしこれは期待した通りに動きません。なぜなら、(a2->a3, b2->b3) のように、結果の型のタプルの内側に関数が入っているためです。

-- a2 = a2 -> a3, b2 = b2 -> b3 として型を変形
h' :: (a1 -> a2 -> a3) -> (b1 -> b2 -> b3) -> (a1, b1) -> (a2 -> a3, b2 -> b3)
h' f g = bimap f g

つまり、 (a2 -> a3, b2 -> b3) -> (a2, b2) -> (a3, b3) の関数を見つければ、関数 h をポイントフリーで書けるようになります。

h'' :: (a -> b) -> (c -> d) -> (a, c) -> (b, d)
h'' = undefined

こたえは自分で考えてね (僕は1時間ぐらいかかりました)

Discussion