LoginSignup
6

More than 5 years have passed since last update.

posted at

updated at

Maybe自作から学ぶHaskell!

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

この記事を書いた感想

うん。正直どこまで説明を細かく書くべきかわからず苦しんだよ。
モナドについてはもはや説明という説明すらない。
途中で、あれ?これ定義を書いとけば日本語でいちいち説明する必要なくね?と思ったからだ。

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
What you can do with signing up
6