日記 (2019 年 3 月 7 日)
昨日から、 かねてからやりたいと言っておいて手を付けてなかった Haskell の勉強を始めました。 ただ目的がないと飽きるので、 手始めに Tsuro の実装をすることにしました。 書いたコードは Gist に置いてあります。
さて、 特定の条件を満たしていたら何らかの値を返し、 満たしていなかったら Nothing を返すような式を書きたかったので、 私は愚直に以下のようにしました。
if y > 0
then Just (x, y - 1)
else Nothing
すると、 Twitter で hsjoihs さんから、 これは guard (y > 0) >> Just (x, y - 1) とも書けることを教えてもらいました。
知らない関数と知らない演算子が出てきた (というか勉強 2 日目なのでほぼ知らないんですが) ので、 どんな働きをするのかを調べました。
とりあえず、 なぜそのような定義がなされているのかなどは考えずに、 この式が上の式と同じ挙動になることを理解するのを目標にします。
まず、 guard 関数ですが、 以下のような定義になっています。
guard :: Alternative f => Bool -> f ()
guard True = pure ()
guard False = empty
() はユニット型で、 まあ、 終対象です。
Alternative は Applicative を継承する型クラスです。
知らない型クラスが 2 つ出てきたわけですが、 順に見ていくことにします。
Applicative クラスは Functor クラスを拡張したもので、 以下のように定義されています。
他にも関数が定義されていますが、 最小限実装しないといけない関数は以下で示されている 2 つです (本当はもう 1 パターンありますがここでは無視しました)。
class Functor f => Applicative f where
pure :: a -> f a
(<*>) :: f (a -> b) -> f a -> f b
-- 他の定義
Maybe では以下のように実装されています。
分かりやすく型注釈を付けてあります。
instance Applicative Maybe where
pure :: a -> Maybe a
pure = Just
(<*>) :: Maybe (a -> b) -> Maybe a -> Maybe b
Just func <*> val = fmap func val
Nothing <*> _ = Nothing
Alternative クラスは Applicative クラスをさらに拡張したもので、 モノイド演算っぽいものが定義されています。
empty が単位元で、 (<|>) が演算ですね。
class Applicative f => Alternative f where
empty :: f a
(<|>) :: f a -> f a -> f a
-- 他の定義
Maybe では以下のように実装されています。
instance Alternative Maybe where
empty :: Maybe a
empty = Nothing
(<|>) :: Maybe a -> Maybe a -> Maybe a
Nothing <|> right = right
left <|> _ = left
…ということで、 実際の実装と見比べれば、 Maybe 上では guard は以下のように定義されているのと同等であることが分かります。
guard :: Bool -> Maybe ()
guard True = Just ()
guard False = Nothing
次は >> です。
これは Monad クラスに定義されているもので、 デフォルトでは >>= を用いて以下のように定義されています。
分かりやすいように括弧を補ったり変数名を変えたりしてます。
(>>) :: Monad m => m a -> m b -> m b
left >> right = left >>= (\_ -> right)
\_ -> right というのは定値のラムダ式なので、 Maybe 上での >> は実質以下と同じです。
(>>) :: Maybe a -> Maybe b -> Maybe b
Just left >> right = right
Nothing >> right = Nothing
以上で Maybe 上での guard と >> の挙動が分かったので、 これらを合わせれば以下のように評価されることが分かります。
guard True >> right→Just () >> right→rightguard False >> right→Nothing >> right→Nothing
これで晴れて、 guard を用いた表現が一番最初の表現と同等であることが分かりました。
今回いろいろ調べて Functor, Applicative, Monad, Alternative などの型クラスがたくさん出てきましたが、 まだ感覚が掴めてないので、 これから勉強したいと思います。