Functor 深い

HaskellFunctor について調べてたらこんなのを見つけた。 stackoverflow.com

ここで挙げられているエクササイズを見てみると Functor の深さにめまいがしてちょっとだけウンコを漏らした。

2問目

Functor の composition をデータ型で定義しろという問題で、実装はこうなる。

{-# LANGUAGE TypeOperators #-}

newtype (:.) f g x = Comp {comp :: f (g x)}

instance (Functor f, Functor g) => Functor (f :. g) where
  fmap f (Comp x) = Comp $ fmap (fmap f) x

instance (Applicative f, Applicative g) => Applicative (f :. g) where
  pure = Comp . pure . pure
  Comp f <*> Comp x = Comp $ fmap (<*>) f <*> x

instance (Foldable f, Foldable g) => Foldable (f :. g) where
  foldMap f (Comp x) = foldMap (foldMap f) x

instance (Traversable f, Traversable g) => Traversable (f :. g) where
  traverse f (Comp x) = Comp <$> traverse (traverse f) x

TraversableFoldable な値はこれまでに扱ったことはあるが自前のデータ型に実装するのは初めてだったのでよくわからんという感じになりながらも定義した。

特に f (g x) を掘っていくという感覚になれるのがちょっとてこずったが、一度感覚をつかめばまあこうなるかという感じでうまく言語化するには多分圏論の知識が必要なんだと思う。

それはいいとして、Comp を定義することによって以下のような使い方ができる。

data Triple a = Tr a a a

instance Functor Triple where
  fmap f (Tr a b c) = Tr (f a) (f b) (f c)

instance Applicative Triple where
  pure a = Tr a a a
  Tr f g h <*> Tr a b c = Tr (f a) (g b) (h c)

instance Foldable Triple where
  foldMap f (Tr a b c) = mconcat [f a, f b, f c]

instance Traversable Triple where
  traverse f (Tr a b c) = Tr <$> f a <*> f b <*> f c

type Zone = Triple :. Triple

type Board = Zone :. Zone

Triple は3要素のタプルとみなせるので Zone3x3 の配列で Board 9 x 9 の配列と解釈することができる。これを実現するのが Comp であり、その奥には Functor の存在がある。

ところで、Zonetraverse すると何が起きるのか把握しておきたい。

ghci> z = Comp (Tr (Tr 1 2 3) (Tr 4 5 6) (Tr 7 8 9)) :: Zone Int
ghci> traverse (Just . (+1)) z
Just (Comp {comp = Tr (Tr 2 3 4) (Tr 5 6 7) (Tr 8 9 10)})

なるほど!

というわけで、Functor の composition と Traversable の組み合わせは非常に強力だということが分かった。

5問目

設問としては

(5) Consider the constant functors

であって、実装含めたのがこれ

newtype K a x = K {unK :: a}

instance (Monoid a) => Functor (K a) where
  fmap _ (K a) = K a

instance (Monoid a) => Applicative (K a) where
  pure _ = K mempty
  K a <*> K b = K $ a <> b

crush :: (Traversable f, Monoid b) => (a -> b) -> f a -> b
crush f t = unK $ traverse (K . f) t

newtype K a x は設問にある通り constant な Functor であるがゆえに fmap の実装は K a をそのまま返していて、Applicative の実装をするにあたっては Monoid であるという型制約を考慮した実装をしていて特段難しいことは無い。がゆえに K がどういう意図のデータ型なのかあまりわからなかった。(Monoid のラッパーであるようにしか見えない)

crush における K の使い方を見てみると Functor スゲーとなる。この関数が何をしているかと言えばシグネチャのまんまではあるけど Traversable な値を Monoid に変換してその値を unwrap するということになるので、順次 traverse した結果を取り出すということになるのでこのように使える。

ghci> import Data.Monoid (Sum(Sum))
ghci> crush (Sum . (+10)) [1, 2, 3] :: Sum Int
Sum {getSum = 36}

これの何が利点なのかと言えば、とあるデータ型について閉じた演算が定義されているので値を直接こねくり回すことなくとても簡潔に関数を定義できることにある。(はず。Haskell で実現しているのはそういうもんのはず。)

同エクササイズで allany を定義しろというのだけどもこれも以下の通りデータ型を実装できる。

import Prelude hiding (all, any)

newtype Any = Any {unAny :: Bool}

instance Semigroup Any where
  (Any x) <> (Any y) = Any $ x || y

instance Monoid Any where
  mempty = Any False

newtype All = All {unAll :: Bool}

instance Semigroup All where
  (All x) <> (All y) = All $ x && y

instance Monoid All where
  mempty = All False

any :: (Traversable t) => (a -> Bool) -> t a -> Bool
any p = unAny . crush (Any . p)

all :: (Traversable t) => (a -> Bool) -> t a -> Bool
all p = unAll . crush (All . p)

こう見ると Traversable そのものもものすごく強力なように思える。