Haskell入門
対象
「Scala モナド」などでググったけどHaskellでの説明が出てきて、Scalaっていうたやん!とキレたことのある人。
目標
Haskellで書かれた式が読める
モナド則
return x >>= f == f x
m >>= return == m
(m >>= f) >>= g == m >>= (\x -> f x >>= g)
※注
僕はまだこれしか読んでません。
Haskellとは?
- 強い静的型付け
- 関数型言語
- 遅延評価
- ロゴがかっこいい
基本
> 1 + 2
3
> True && False
False
> 1 == 1
True
> "Hello" /= "hello"
False
> 1 : 2 : 3 : []
[1,2,3]
関数
普通の関数(前置記法)
関数名の後にスペース区切りで引数を続ける
> min 4 10
10
> max 100 (10 * 50)
500
中置記法
引数を2つ取る関数は `` で囲む事で2つの引数の真ん中に置くことが出来る。
関数の名前に記号も使える。(!#$%&*+./<=>?@\^|-~
)
`*` などの演算子も関数で、記号のみの関数はデフォルトで中置記法になる
中置記法がデフォルトの関数を前置記法で使いたい場合は`()`で括る
> div 5 2
2
> 5 `div` 2
2
> (+) 5 10
15
関数定義
関数名 引数 = 式
とするとことで定義可能。
引数が複数ある場合はスペース区切りで渡す。
記号のみの関数名は()
で括る
> sum5 x = x + 5
> sum5 10
15
> (@~@!) x y = x + y
> 10 @~@! 10
20
無名関数
いわゆるラムダ式
\
から始まる式が無名関数となる
> (\x -> x + 100) 1
101
> (\x y -> x + y) 1 2
3
> map (\x -> x + 100) [1..10]
[101,102,103,104,105,106,107,108,109,110]
セクション
中置演算子を()
を使って部分適用したものをセクションと呼び、よく使う。
さっきの\x -> x + 100
はセクションの(+100)
と等しい
> map (+100) [1..10]
[101,102,103,104,105,106,107,108,109,110]
> map (*2) [1..5]
[2,4,6,8,10]
型
型定義
関数定義の場合に型推論が可能なら型定義は省略できる。
型定義は 関数名 :: 型
という形で行う。
->で複数の型が繋がっている場合、一番最後の型が戻り値の型で、それ以外は引数の型
cuboid :: Float -> Float -> Float -> Float
cuboid width height length = width * height * length
> cuboid 3.4 2.5 4
34.0
なぜ引数と返り値の方が同列に定義されるのか?
全ての関数はカリー化されているため。
cuboid に width を渡すと (height と length を取り直方体の体積を返す関数) が得られる
同様にして↑の関数に height を渡すと…
つまり型定義は全て引数 -> 戻り値
となっており、
Float -> Float -> Float -> Float
は
Float -> (Float -> (Float -> Float))
と同じ
型変数
リストの関数のように色々な型に対して動作するものには、型定義に型変数を用いる。
ジェネリクスのようなもの。
> head [1,2,3]
1
> :t head
head :: [a] -> a
型クラスと型クラス制約
型クラスとは振る舞いを定義するインターフェース。
例えばある型に対してEq
型クラスのメソッド==
を実装していれば等値性比較の振る舞いが定義できる。
> :t (==)
(==) :: Eq a => a -> a -> Bool
↑の=>
記号が型クラス制約で、
その名の通り型a
がEq
の型クラスに属しているという制約を表している。
(つまりEq型クラスに属しているので 型aの値 == 型aの値
が定義されているという事)
パターンマッチ
checkList :: (Show a) => [a] -> String
checkList (x : y : []) = "Two elements " ++ show x ++ " and " ++ show y
checkList (x : []) = "Just one element " ++ show x
checkList [] = "Empty!"
checkList xs = "Many elements " ++ show xs
> checkList [1,2,3,4]
"Many elements [1,2,3,4]"
> checkList ["hello"]
"Just one element \"hello\""
Show型クラスは文字列に変換できるインターフェース。
++ は文字列(リスト)の結合演算が行える関数
データ型定義、型コンストラクタ
新しい型は data 型名 = 型 (| 型2 (| 型3 ...))
という様に定義できる。
|
で複数の型が繋がっている場合はいずれかの型がそのdataの型となりうる事を表している。
またこの時型名
を型コンストラクタと呼ぶ。
data Point = Pt2d Double Double | Pt3d Double Double Double
distance :: Point -> Point -> Double
distance (Pt2d x1 y1) (Pt2d x2 y2) = sqrt((x1 - x2)**2 + (y1 - y2)**2)
distance (Pt3d x1 y1 z1) (Pt3d x2 y2 z2) = sqrt((distance (Pt2d x1 y1) (Pt2d x2 y2))**2 + (z1 -z2)**2)
> distance (Pt2d 0 0) (Pt2d 3 4)
5.0
例
data Maybe a = Just a | Nothing
Maybeは Just a
とNothing
のどちらかを取るデータ型で、Maybe
がその型コンストラクタ
型クラス定義
型クラスはclass
キーワードを使って行う。
class Eq a where
(==) :: a -> a -> Bool
(/=) :: a -> a -> Bool
x == y = not (x /= y)
x /= y = not (x == y)
whereはブロック{}
のようなもので、Eq a
の定義の為にスコープを作ると思ってくれれば良い。
(==)
と(/=)
がその型クラスに属する型が実装すべきメソッド
x == y
とx /= y
はその実装を行っている。実装は行わなくても型クラスの定義は可能。
Eq型クラスでは==
と/=
が相補的に実装されているので、実際にはどちらかのメソッドを実装するだけで良い。
型クラスのインスタンス作成
ある型をある型クラスに属させるのにはinstance
キーワードを使って行い、これをインスタンス化と呼ぶ。
instance Eq Point where
Pt2d x1 y1 == Pt2d x2 y2 = x1 == x2 && y1 == y2
Pt3d x1 y1 z1 == Pt3d x2 y2 z2 = x1 == x2 && y1 == y2 && z1 == z2
_ == _ = False
> Pt2d 1 1 == Pt2d 1 1
True
> Pt2d 1 1 == Pt3d 1 2 3
False
モナド則
return x >>= f == f x
m >>= return == m
(m >>= f) >>= g == m >>= (\x -> f x >>= g)
> :t f
f :: Monad m => a -> m b
> :t g
g :: Monad m => b -> m c
> :t return
return :: Monad m => a -> m a
> :t (>>=)
(>>=) :: Monad m => m a -> (a -> m b) -> m b
Functor型クラス
Monadのベースのベース
Functor型クラスは 全体を写せる 振る舞いを持ったインターフェース。
定義は
> :i Functor
class Functor (f :: * -> *) where
fmap :: (a -> b) -> f a -> f b
(<$) :: a -> f b -> f a
{-# MINIMAL fmap #-}
-- Defined in ‘GHC.Base’
となっており、最低限fmap
を実装していればFunctor型クラスのインスタンスになることが出来る。
(Functor (f :: * -> *)
は fは型を取って型を返す型コンストラクタ という意味)
fmap
改めてfmapの型定義を見るとfmap :: (a -> b) -> f a -> f b
となっている。
型コンストラクタfはコンテナ型のようなものなので、その文脈で考えると「ある型に包まれた値a」と「値aを値bに変換できる関数」を引数に取って「ある型に包まれた値b」を返すような定義をしている。
実際にリストではinstance Functor [] where fmap = map
と定義されているため、map関数をイメージすると解りやすい。
ただし、リストのように値が連続している必要はなく、実際の値がある型で包まれていればFunctor型クラスになりうるというのが重要。
fmap使用感
> fmap (*2) [1..3]
[2,4,6]
> fmap (++ "I'm alice") (Just "Hi bob.")
Just "Hi bob.I'm alice"
> fmap (++ "I'm alice") Nothing
Nothing
fが型コンストラクタなので、取りうる型(MaybeならJust|Nothing)についてfmapが問題なく使える。
Applicative
Monadのベース, Functorの強化版
Applicativeは強化版Functorの様な型クラス
定義は
> :i Applicative
class Functor f => Applicative (f :: * -> *) where
pure :: a -> f a
(<*>) :: f (a -> b) -> f a -> f b
(*>) :: f a -> f b -> f b
(<*) :: f a -> f b -> f a
{-# MINIMAL pure, (<*>) #-}
-- Defined in ‘GHC.Base
重要なのはpure
と<*>
の2つ。(最低限の実装は<*>
だけで良い)
pure
pure :: a -> f a
定義から、値aをアプリカティブ値として包む事がわかる。
リストならpure 1
は[1]
、Maybeなら1をJust 1の様になる。
<*>
(<*>) :: f (a -> b) -> f a -> f b
<*>
は「aをbに変換する関数のアプリカティブ値」と「aのアプリカティブ値」を取って「bのアプリカティブ値」を返す。
また、記号のみの関数なのでデフォルトで中置記法での使用になる (f (a->b) <*> fa
と使う)
ここでfmapの定義はfmap :: (a -> b) -> f a -> f b
なので、これと見比べるとa -> b
がアプリカティブ値に包まれているだけと分かる。
つまりfmapの変換関数がJustなどで包まれている場合でもfmap
の代わりに<*>
を使えば同じことが出来る。
<*>使用感
> Just (\x -> x + 10) <*> Just 5
Just 15
> Just (\x -> x + 10) <*> Nothing
Nothing
> Nothing <*> Just 5
Nothing
> pure (\x -> x + 10) <*> Just 5
Just 15
> Just (\x -> x + 10) <*> pure 5
Just 15
\x -> ...
はxを引数に取る無名関数を表す。...
の部分が実装
Monad
Applicativeの強化版
> :i Monad
class Applicative m => Monad (m :: * -> *) where
(>>=) :: m a -> (a -> m b) -> m b
(>>) :: m a -> m b -> m b
return :: a -> m a
fail :: String -> m a
{-# MINIMAL (>>=) #-}
-- Defined in ‘GHC.Base’