top page > computer > haskell > web_lecture > for_programmer > maybe_monad.html
更新日:
文責: 重城良国

失敗の可能性のある関数をつなぐ

(工事中 50%)

はじめに

(a -> m b)の型をとる関数がある。この形の関数の多くは(a -> m b)と(b -> m c)をつないで(a -> m c)とすることができると便利である。

たとえば失敗する可能性のある関数として以下の2つを考える。

lowerToCode :: Char -> Maybe Int
evenDiv2 :: Int -> Maybe Int

それぞれ小文字だけを文字コードにする関数と偶数だけを2でわる関数だ。これらをつないで

lowerToCodeDiv2 :: Char -> Maybe Int

を作ることを考える。これは小文字で文字コードが偶数であるものの文字コードを2でわった値を計算する。

関数lowerToCode

maybe_monad.hs

小文字のみの文字コードを返す関数を作る。

import Data.Char (isLower, ord)

lowerToCode :: Char -> Maybe Int
lowerToCode c
| isLower c = Just $ ord c
| otherwise = Nothing

ghci maybe_monad.hs
*Main> lowerToCode 'n'
Just 110
*Main> lowerToCode 'X'
Nothing

関数evenDiv2

偶数のみを2でわる関数を作る。

evenDiv2 :: Int -> Maybe Int
evenDiv2 n
| even n = Just $ n `div` 2
| otherwise = Nothing

*Main> :reload
*Main> evenDiv2 12
Just 6
*Main> evenDiv2 17
Nothing

関数lowerToCodeDiv2

関数lowerToCodeと関数evenDiv2とをつなげて小文字で文字コードが偶数のもののみの文字コードを2でわった値を計算する関数を作る。

lowerToCode :: Char -> Maybe Int
evenDiv2 :: Int -> Maybe Int

これらの関数をつなげて

lowerToCodeDiv2 :: Char > Maybe Int

を作る。

lowerToCodeDiv2 :: Char -> Maybe Int
lowerToCodeDiv2 c = case lowerToCode c of
Just n -> evenDiv2 n
_ -> Nothing

*Main> :reload
*Main> lowerToCodeDiv2 'n'
Just 55
*Main> lowerToCodeDiv2 'N'
Nothing
*Main> lowerToCodeDiv2 'y'
Nothing

関数lowerToCodeDiv4

さらに関数evenDiv2を2回つなげることで「小文字で文字コードが4の倍数のものの文字コードを4でわったものを計算する関数」を作る。

lowerToCode :: Char -> Maybe Int
evenDiv2 :: Int -> Maybe Int
evenDiv2 :: Int -> Maybe Int

の3つをつなぐ。

lowerToCodeDiv4 :: Char -> Maybe Int
lowerToCodeDiv4 c = case lowerToCode c of
Just n -> case evenDiv2 n of
Just n' -> evenDiv2 n'
_ -> Nothing
_ -> Nothing

*Main> :reload
*Main> lowerToCodeDiv4 'n'
Nothing
*Main> lowerToCodeDiv4 'p'
Just 28

関数pipeM

「失敗の可能性のある関数」をつないでみた。どのようなつなぎかただっただろうか。

f :: a -> Maybe b
g :: b -> Maybe c

関数fと関数gをつなぐということは関数fの結果がJust xならば値xに関数gを適用するということだ。もし関数fの結果がNothingならば結果もNothingとなる。

関数fまたは関数gのどちらかが「失敗」すれば全体も失敗する。

このようなつなぎかたをする関数pipeMを作る。

pipeM :: (a -> Maybe b) -> (b -> Maybe c) -> (a -> Maybe c)
(f `pipeM` g) v = case f v of
Just x -> g x
_ -> Nothing

関数lowerToCodeDiv4の書きかえ

関数lowerToCodeDiv4を関数pipeMを使って書きかえる。

lowerToCodeDiv4 :: Char -> Maybe Int
lowerToCodeDiv4 = lowerToCode `pipeM` evenDiv2 `pipeM` evenDiv2

*Main> :reload
*Main> lowerToCodeDiv4 'n'
Nothing
*Main> lowerToCodeDiv4 'p'
Just 28

関数lowerToCodeDiv2Mul3

2回2でわるかわりに2でわったあとに3をかけることを考えよう。かけ算は失敗することはないので

mul3 :: Int -> Int
mul3 = (* 3)

となる。これをpipeMでつなぐことを考える。このままだと型が合わないので型を合わせるための関数を作る。

arrM :: (a -> b) -> (a -> Maybe b)
arrM f = Just . f

この関数は以下のようにも書ける。

arrM = (Just .)

これらを使って関数lowerToCodeDiv2Mul3を定義する。

lowerToCodeDiv2Mul3 :: Char -> Maybe Int
lowerToCodeDiv2Mul3 =
lowerToCode `pipeM` evenDiv2 `pipeM` arrM mul3

*Main> :reload
*Main> lowerToCodeDiv2Mul3 'n'
Just 165

より単純にする

失敗の可能性のある関数同士やそれらと普通の関数をつないでいく書きかたをするために以下の2つの関数を定義した。

pipeM :: (a -> Maybe b) -> (b -> Maybe c) -> (a -> Maybe c)
arrM :: (a -> b) -> (a -> Maybe b)

これらはどちらも引数と結果の両方に'a ->'がある。これらを消して同じ情報を持つ関数を作ることができる。関数名はbindMとretMとすることにしよう。

bindM :: Maybe b -> (b -> Maybe c) -> Maybe c
retM :: b -> Maybe b

bindMとretMとを定義しよう。

Just x `bindM` f = f x
_ `bindM` _ = Nothing

retM = Just

関数lowerToCodeDiv2Mul3

関数bindMとretMのセットを使ってlowerToCodeDiv2Mul3を書きなおす。

lowerToCodeDiv2Mul3 c =
lowerToCode c `bindM` evenDiv2 `bindM` (retM . mul3)

*Main> :reload
*Main> lowerToCodeDiv2Mul3 'n'
Just 165

関数lowerToCodeDiv2Mul3の定義を以下のように整形することができる。(本当は同じじゃない。そこらへんをどう説明するか。あるいはごまかすか) (ここの説明でモナド則をこっそり説明してしまうというのがひとつの方法だ)

lowerToCodeDiv2Mul3 c =
lowerToCode c `bindM` \n ->
evenDiv2 n `bindM` \n' ->
retM $ mul3 n'

このような形にすると

のように読むことができる。参考までに上の形の定義に明示的に括弧をつけるとどうなるかを示す。

lowerToCodeDiv2Mul3 c =
lowerToCode c `bindM` (\n ->
evenDiv2 n `bindM` (\n' ->
retM $ mul3 n'))

まとめ

失敗するかもしれない計算(a -> Maybe b)がある。このような計算をつなぐ関数は以下のような型となる。

(a -> Maybe b) -> (b -> Maybe c) -> (a -> Maybe c)

普通の計算も以下の型の関数で型をそろえてつなぐことができる。

(a -> b) -> (a -> Maybe b)

これらの関数はより単純な形に直すことができる。

bindM :: Maybe a -> (a -> Maybe b) -> Maybe b
retM :: a -> Maybe a

「2引数関数の変換」へもどる 「電卓をエミュレートする」へ

正当なCSSです! HTML5 Powered with CSS3 / styling, and Semantics