基本用語
data宣言 -- 値コンストラクタ
-- data 型名 = 値コンストラクタ
data Bool = False | True
型名は型の名称を、値コンストラクタはデータ型が取りうる値を表す。
-- 値コンストラクタは名称をつけることができる
data Shape = Circle Float Float Float |
Rect Float Float Float Float
CircleはFloat型引数を三つとる値(コンストラクタ)である。
※「Circleというデータ型を定義している」のではなく「Circleという名称の値を宣言している」というイメージ。
Shape型の取りうる値はCircleとRectの2種類。
ghci> :t Circle // 宣言前
<interactive>:1:1: error: Data constructor not in scope: Circle
ghci> data Shape = Circle Float Float Float // 宣言
ghci> :t Circle // ghciに認識される
Circle :: Float -> Float -> Float -> Shape
Circleのその実は、ただの関数であるともいえる。
data Shape = Circle { x ::Float, y:: Float, r:: Float } |
Rect {left::Float , top::Float, right::Float, bottom::Float}
レコード構文という上記書き方で引数にラベル付が可能
-- 面積を求める関数
area :: Shape -> Float
area (Circle _ _ r) = pi * r^2
area (Rext x1 y1 x2 y2) = (abs $ x2-x1) * (abs $ y2-y1)
---
ghci> area $ Rect 0 0 10 10
100
-- Circleは値なので以下の書き方はできない
area :: Circle -> Float
area (Circle _ _ r) = pi * r^2
関数を作成
data宣言 -- 型コンストラクタ
-- data 型コンストラクタ 型引数 = 値コンストラクタ
data Maybe a = Nothing | Just a
型コンストラクタは型を引数にとって新しい型を生み出す。
Maybeは型コンストラクタといい、aという型(引数)から新しい型を生み出す。
ghci> :t Nothing
Nothing :: Maybe a
ghci> :t Just "hoge"
Just "hoge" :: Maybe String -- Maybe String 型
ghci> :t Just 1
Just 1 :: Num a => Maybe a -- 型推論
Maybeは型を生み出す型コンストラクタであるため、単独では値になれない。
型引数がない型(IntやBool)、型引数があるが全て埋まっている型(Maybe String)型 を具体型という。
-- この関数はNothingを引数に取れる
fn :: Maybe Int -> Int
Nothing :: Maybe a は多相的、つまりa型がなんであれ、Maybe型コンストラクタを持つすべての型にはNothingを入れられる。
この仕組みを使って、Maybeという値を持つ(Just a) か持たないか(Nothing)を表す値といった抽象な概念を表現している。
-- ベクトル加算を表現
data Vector a = Vector a a a deriving (Show)
vplus:: (Num a) => Vector a -> Vector a -> Vector a
vplus (Vector x y z) (Vector i j k) = Vector (x+i) (y+j) (z+k)
main = do
print $ vplus (Vector 3 5 8) (Vector 9 2 8)
型クラス
class Foo a where
foo :: a -> String
instance Foo Bool where
foo True :i = "True"
foo False = "False"
instance Foo Int where
foo x = show x
main = do
print $ foo True -- "True"
print $ foo False -- "False"
print $ foo (2 ::Int) -- "2"
classは特定の振る舞いを表す。同じ振る舞いをする型たちに共通のインターフェースを提供する。
新たな型クラスを定義するのがclass、型を型クラスのインスタンスにするのがinstance
種類
ghci> :i Int
type Int :: *
ghci> :i Maybe
type Maybe :: * -> *
data Maybe a = Nothing | Just a
型にはラベルがある。型の型なんて言ったりもする。
「*」が具体型を表している。
上記Maybeは「一つの具体型を受け取って具体型となる」と読み、型コンストラクタであることを示している。
Fanctor
ghci> :i Functor
type Functor :: (* -> *) -> Constraint
class Functor f where
fmap :: (a -> b) -> f a -> f b
type より 型コンストラクタから型制約ありの型(※1)を返すことがわかるので、fはなんらかの型コンストラクタである読める。
fmapは 「a->b の関数」と「型コンストラクタfに型引数aを適応した型」を受け取り、「型コンストラクタfに型引数bを適応した型」を返すと読む。
混同するかもだが、f関数にaを適応すると言った意味ではなく、「f a」 で一つの具体型を表す。
instance Functor Maybe where
fmap _ Nothing = Nothing
fmap f (Just a) = Just (f a)
型コンストラクタを伴う具象型はFunctorになり得る(*2)。Maybe型コンストラクタはFunctorのインスタンスである。
Maybeの中身がNothingならNothingを返し、なんらかの値をもったMaybeであれば,第一引数のfをaに適応する。
同じ単語がでてくるのでややこいが、ここでは
fmap f (Just a) = Just (f a)
のf は (a -> b)の関数のことであり、aは(Just a)の中身の値aである。 Functor型クラスのf,aとは異なるので注意。
ghci> fmap (*3) (Just 11) -- 値があれば関数を適応する
Just 33
ghci> fmap (*3) Nothing -- なにもなければ何もないを返す
Nothing
Just 11のような「型コンストラクタで意味付けされたなんらかの値」の中身11に関数*3を適応して、「同じく型コンストラクタで意味付されたなんらかの値」 Just 33 を返す。
「型コンストラクタで意味付けされたなんらかの値」のことを文脈といったりする。fmapは文脈を変えずに中身の値だけ変える道具」といったイメージ。
instance Functor (Either a) -- Defined in ‘Data.Either’
instance Functor [] -- Defined in ‘GHC.Base’
instance Functor Solo -- Defined in ‘GHC.Base’
instance Functor Maybe -- Defined in ‘GHC.Base’
instance Functor IO -- Defined in ‘GHC.Base’
instance Functor ((->) r) -- Defined in ‘GHC.Base’
instance Functor ((,,,) a b c) -- Defined in ‘GHC.Base’
instance Functor ((,,) a b) -- Defined in ‘GHC.Base’
instance Functor ((,) a) -- Defined in ‘GHC.Base’
Functorのinstanceは上記、一般的には「全体を写せるもの」は全部Functor なんていったりする。
Maybeの例でも,Maybeの文脈はそこねず、aをbに写したものと考えることができる。
Functorの説明表現
- なんらかの値を写せるもの
- 値を入れるなんらかのコンテナのような構造を持つ型
- なんらかの型をラッピングした(包み込んだ)型を
※1
ghci> :t (==)
(==) :: Eq a => a -> a -> Bool
この記事ではあまり気にしなくて良いが、Haskellには型制約という機能がある。(==>)がそれで、型引数aはEq(等値性比較クラス)のサブクラスである必要があるといった制約を設けている。
※2
fmap id = id
fmap (f . g) == fmap f . fmap g
Fanctorである条件は上記Functor即を満たすものである必要がる。