haskell Advent Calendar 2017に参加しました!!
https://qiita.com/advent-calendar/2017/haskell3
はい、というわけで! 初心者ですが頑張って書きます!
やること
説明を交えながら、Maybeを自作していく。
型クラスもやる。
なぜMaybeを自作しようと思ったのか。
Haskellを理解するには、実際に何らかの既存の型を自作してみて、体に馴染ませていくのが良いと思ったからである。
#Maybeとは
Maybeは標準で使える代数データ型の一つであり、何らかの計算が成功・失敗したかを安全に扱える便利なやつである。
計算が成功すると、Just <計算結果>を返し、
失敗なら、Nothingを返す。
#Maybeの代数データ型を定義してみる
というわけで、オリジナルのMaybe型を定義しよう。
名前は…うーん……Elipmocで!!
data Elipmoc a = Bat | Good a
実際に使用してみよう。
data Elipmoc a = Bat | Good a
main::IO()
main = do
print . Good $ 2
print . Good $ "hogehoge"
print (Bat::Elipmoc Int)
ファイル名はMain.hsにした
コンパイルはめんどいのでrunghcで直接実行する。
$ stack runghc Main.hs
実行結果
Main.hs:5:5: error:
? No instance for (Show (Elipmoc a0)) arising from a use of ‘print’
? In the first argument of ‘(.)’, namely ‘print’
In the expression: print . Good
In a stmt of a 'do' block: print . Good $ 2
以下略
おっとエラーになってしまったぞ。
#derivingしよう
先程の例のprint関数は内部でshow関数を使っている。
Show型クラスはオブジェクトから文字列を返すshow関数を定義しており。
ElipmocはShow型クラスのインスタンスでないので、show関数が使えずエラーとなった。
とりあえず、Maybeが持つ型クラスを調べてみることにしよう。
REPLを使用する。
$ stack ghci
―略―
Prelude> :i Maybe
data Maybe a = Nothing | Just a -- Defined in ‘GHC.Base’
instance Eq a => Eq (Maybe a) -- Defined in ‘GHC.Base’
instance Monad Maybe -- Defined in ‘GHC.Base’
instance Functor Maybe -- Defined in ‘GHC.Base’
instance Ord a => Ord (Maybe a) -- Defined in ‘GHC.Base’
instance Read a => Read (Maybe a) -- Defined in ‘GHC.Read’
instance Show a => Show (Maybe a) -- Defined in ‘GHC.Show’
instance Applicative Maybe -- Defined in ‘GHC.Base’
instance Foldable Maybe -- Defined in ‘Data.Foldable’
instance Traversable Maybe -- Defined in ‘Data.Traversable’
instance Monoid a => Monoid (Maybe a) -- Defined in ‘GHC.Base’
Maybe型の持つ型クラスがずらずら列挙された。
この内Eq,Ord,Read,Showはderivingを使って自動でインスタンスにできるので、早速やってみる。
修正後のコード
data Elipmoc a = Bat | Good a deriving (Eq,Ord,Read,Show)
main::IO()
main = do
print . Good $ 2
print . Good $ "hogehoge"
print (Bat::Elipmoc Int)
実行結果
$ stack runghc Main.hs
Good 2
Good "hogehoge"
Bat
うん、ちゃんとできた。
ついでに、Eq,Ord,Readについても確かめてみよう。
その前に各種型クラスの説明をすこしだけ。
Eqは==と/=演算子が定義されていて、
等しいかどうかを調べることができる型クラスだ。
OrdはEqを継承した型クラスであり、
<と<=と>と>=演算子を定義されていて、
大小比較ができる型クラスだ。
ReadはShowクラスとは逆で、
文字列からオブジェクトを構成するread関数が定義されている。
Eq,Ord,Readも試してみよう。
data Elipmoc a = Bat | Good a deriving (Eq,Ord,Read,Show)
main::IO()
main = do
print (Good 2 == Good 2)
print (Good 5 /= Good 5)
print (Good 20 > Good 2)
print $ Good 20 < Good 8
print $ (read "Bat"::Elipmoc Int)
print $ (read "Good 35"::Elipmoc Int)
実行結果
True
False
True
False
Bat
Good 35
Monoid実装
Monoidは二項演算の型クラスである。
定義を示す
class Monoid a where
mempty :: a
mappend :: a -> a -> a
mconcat :: [a] -> a
mconcat = foldr mappend mempty
memptyは単位元を返す関数で、
mappendは2つの値を結合して1つの値にする関数だ。
例としてリストで説明しよう。
リストには++という二項演算子がある。
この演算子は
( ( [1]++[2] )++[3] )
でも
( [1]++( [2]++[3] ) )
でも
結果は[1,2,3]であり、どの順番で計算しても問題無いことがわかる。
このような性質をMonoidと呼び、
haskellではMonoid型クラスのインスタンスにすることができる。
このとおり、リストはMonoid型クラスのインスタンスであるので、
上記のコードをMonoid型クラスのmappendで書き換えることができる。
[1] `mappend` [2] `mappend` [3]
このように、リストでmappendを使うと++演算子と同じ働きをする。
Monoidになるためにはさらにもう一つ条件があり、それは単位元が定義できることだ。
単位元とは、 a `mappend` e → a
を満たすeがあることである。
例えばリストの単位元は[]である。
[1]++[] → [1]
ほらね。
Monoid型クラスではmemptyとして定義されている。
上記をmemptyで書き換えると
[1] `mappend` mempty → [1]
で、MaybeもMonoidのインスタンスなので
Elipmocにも実装してみよう!
ではElipmocをMonoidのインスタンスにしてみよう。
instance Monoid a => Monoid (Elipmoc a) where
mempty = Bat
Bat `mappend` m = m
m `mappend` Bat = m
Good m1 `mappend` Good m2 = Good (m1 `mappend` m2)
mconcatはデフォルトで実装されているので、
実装する必要はない。
では実際に使用してみよう。
main::IO()
main = do
print (Good [5] `mappend` Good [4])
print (Bat `mappend` Good [3])
print (Good [2] `mappend` Bat)
実行結果
Good [5,4]
Good [3]
Good [2]
#Functorを作ろう
Functorの定義を示す。
class Functor f where
fmap :: (a -> b) -> f a -> f b
(<$) :: a -> f b -> f a
(<$) = fmap . const
Functorには、mapという関数が定義されている。
例えば、
f x = 2*x
という関数があったとして、
f (Just 5)
のように値を渡すことはできない。5はJustで包まれているからだ。
こういうときにfmapを使うと
fmap f (Just 5)
というふうに書くことができる。
fmapはfで包まれたaを取り出して、関数(a->b)に渡して加工して、加工したbを再びf型に包むという操作を行う。
さらにもう一つ、Functorには<$という演算子が定義されている。
<$の左辺には任意の型aを、右辺にはf型で包まれているbを渡す。
そうすると、左辺のaを右辺のf型で包んだaを返す。右辺の値は捨てられる。
例を挙げる。
176 <$ Just 810
右辺はMaybeで包まれているので、Just 176が返り、Just 810は捨てられる。
では、サクッと実装しますね。
<$はデフォルトで実装されるので自分で定義する必要はありません。
instance Functor Elipmoc where
fmap _ Bat = Bat
fmap f (Good a) = Good (f a)
プログラム例
main::IO()
main = do
print ( fmap (\x->[4+x]) (Good 5))
print ( fmap (\x->[4+x]) (Bat))
このようにfmapに加工する関数を渡すと、Elipmocの中身を取り出し加工して再び包んで返す。
実行結果
Good [9]
Bat
#Applicativeを作ろう
Applicativeの実装を示す。
class Functor f => Applicative f where
pure :: a -> f a
(<*>) :: f (a -> b) -> f a -> f b
liftA2 :: (a -> b -> c) -> f a -> f b -> f c
liftA2 f x = (<*>) (fmap f x)
(*>) :: f a -> f b -> f b
a1 *> a2 = (id <$ a1) <*> a2
(<*) :: f a -> f b -> f a
(<*) = liftA2 const
pure関数は任意のa型の値を受け取って、それをfの型で包んで返します。
<\*>演算子はfで包まれた左辺f(a->b)と任意の型aの右辺を受け取り、左辺の中身の関数(a->b)に、右辺の中身bを引数として渡し、結果をfで包んで返します。
<*演算子は左辺と右辺を受け取り、右辺を捨てて左辺を返す。
*>演算子は左辺を捨てて右辺を返す。
あと<$>演算子も紹介しよう。
<$>の定義を示す。
(<$>) :: Functor f => (a -> b) -> f a -> f b
(<$>) = fmap
<$>演算子は<\*>ととても似ていて、違いは左辺がfで包まれていないだけである。
というか、ただのfmapである。
これらを使うことで見やすいコードを書くことができる。(投げやり)
じゃあ型クラス作りましょう。
instance Applicative Elipmoc where
pure a = Good a
(<*>) (Good f) x = fmap f x
(<*>) Bat _ = Bat
サンプルコード
func x y= x+y
main::IO()
main = do
print ( func <$> Bat <*> Good 2)
print ( func <$> Good 5 <*> Good 3 <* Good 2)
print ( func <$> Good 5 <*> Good 3 <* Bat)
実行結果
Bat
Good 8
Bat
#Monadを作ろう
どんどん作っていきます
Monadの定義を示す
class Applicative m => Monad m where
(>>=) :: m a -> (a -> m b) -> m b
(>>) :: m a -> m b -> m b
m >> k = m >>= \_ -> k
return :: a -> m a
return = pure
Monadを使うことで、複数の処理を連結させて、1つの処理にまとめることができる。
ElipmocをMonadのインスタンスにする。
instance Monad Elipmoc where
(Good x) >>= k = k x
Bat >>= _ = Bat
(Good _) >> k = k
Bat >> _ = Bat
return = Good
fail _ = Bat
サンプルコード
func 0= Bat
func x= return (x*10)
func2 = return (931::Int)
main::IO()
main = do
print ( Good 2 >>= func)
print ( Good 0 >>= func)
print ( Good 1 >>= func >>= func)
print ( Good 32 >> func2)
計算結果
Good 20
Bat
Good 100
Good 931
#この記事を書いた感想
うん。正直どこまで説明を細かく書くべきかわからず苦しんだよ。
モナドについてはもはや説明という説明すらない。
途中で、あれ?これ定義を書いとけば日本語でいちいち説明する必要なくね?と思ったからだ。