初めに
-
Maybe 自体は、型コンストラクタと呼ばれるものにあたるので、準備にてStep by Stepで追っていきます。
-
MaybeはFuncor, Monoid, Monad型クラスのインスタンスなのですが、Maybe 自体を理解している必要があります。ということで、Haskell勉強している身にとっては避けては通れないものなのです…
-
正確な記述を心がけていますが、初心者故間違えていたら教えてください(><)
#Maybe 定義編
準備
https://ja.wikipedia.org/wiki/Haskell
変数に束縛するのが定数であるか関数であるかにかかわらず、「変数 = 値」という一貫した形でも定義できる。
ということで、関数も値も変数と呼ぶ。
値コンストラクタ
C++でのstructにあたる構文はHaskellでは、以下で定義される。
--deriving (Show)を付加することで、コンソールに出力可能な形式にしてくれる。
Prelude> data Person = Person String String Int deriving (Show) --Person型の定義
Prelude> d = Person "K" "N" 27
Prelude> d
Person "K" "N" 27
--もしくは、レコード構文
Prelude> data Person = Person {firstName :: String, lastName :: String, age :: Int } deriving (Show) --Person型の定義
--firstNameは1変数関数(getterとsetterの機能)であることに注意。
Prelude> :t firstName
firstName :: Person -> String
Prelude> d = Person { firstName = "K", lastName = "N", age = 27 }
Prelude> firstName d
"K"
--名前付きでなくても、OK!
Prelude> Person "K" "N" 27
Person {firstName = "K", lastName = "N", age = 27}
Prelude> :t Person
Person :: String -> String -> Int -> Person
なので、Personの実体は3変数関数。
{-
キーワード データ型 値コンストラクタ フィールド
↓ ↓ ↓ ↓
-}
Prelude> data Person = Person String String Int deriving (Show) --Person型の定義
値コンストラクタとしてのPersonは型ではないことに注意。
C++でのenumにあたる構文はHaskellでは、以下で定義される1。
//cpp
enum Bool { False, True };
--hs
Prelude> :info Bool
data Bool = False | True -- Defined in ‘GHC.Types’
ここで、Boolはデータ型なので、型であるが、False,Trueは値コンストラクタなので、型ではないことに注意。
Haskellでは、値コンストラクタに関数を指定することもできる(下記の例の場合は、CircleとRectangle):
--hs
data Shape = Circle Float Float Float | Rectangle Float Float Float
冗長な説明ではあるが、Shapeはデータ型なので、型であるが、Circle,Rectangleは値コンストラクタなので、型ではない。
型コンストラクタ
Haskellでは、型自体の生成もできる。(C++におけるテンプレートに似ていますが、Haskellのほうが圧倒的に柔軟です)
型コンストラクタから、型引数を作用させて、型が生成される。
つまり、(インスタンス - 鋳型 ) ⇔ (変数 - 型) ⇔ (型 - 型コンストラクタ)に対応している。もう一段メタ的な土台に踏み入っているわけですな。2
{-
型コンストラクタ
↓
-}
data Vector a = Vector a a a
{-
↑ ↑
型引数 型
-}
型コンストラクタは型ではないことに注意。
ちなみに、C++だと、ざっくりこんな感じ:
(C++ <=> Haskell)で、(テンプレートクラス <=> 型コンストラクタ)、(テンプレート引数 <=> 型引数)に対応している。
template<typename T>
struct Vector
{
T x;
T y;
T z;
Vector(T x, T y, T z) : x(x), y(y), z(z) {}
};
haskellに戻ります。
せっかくなので、ghciで確かめておこう。
Prelude> data Vector a = Vector a a a deriving(Show)
-- :k コマンドは型の「型」(種類という)を調べるときに用いる関数。
-- :t コマンドは、変数の型を調べるものであった。
-- *は任意の型を表す。:tでいう、head :: [a] -> aのaに対応する。
-- Vectorは、* -> *の種類の型コンストラクタ
Prelude> :k Vector
Vector :: * -> *
-- 型引数が1つもないとき(*の種類の型コンストラクタ)は、具体型という。
-- Vector Intの場合、型としてみることもできるが、種類としてみた場合は、具体型として用語を使い分ける。
Prelude> :k Vector Int
Vector Int :: *
ということで、長くなってしまったが、本題。
Maybeの定義
Prelude> :info 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’
とりあえず、2行目の解読。3
-- deriving は後述の型クラスの章を参照してください。
data Maybe a = Nothing | Just a
deriving (Eq, Ord)
例えば、Maybe Intが型になり、Nothing, Just 3, Just 5などが値コンストラクタ(変数)、Maybeが型コンストラクタになる。
ちなみに、型引数aは型しか突っ込めないから、「Maybe 8」みたいな書き方はあり得ないとわかる。
Prelude> :t Maybe 8
<interactive>:1:1: error:
? Data constructor not in scope: Maybe :: Integer -> t
? Perhaps you meant variable ‘maybe’ (imported from Prelude)
-- :t は型を調べるコマンドなので、第一引数には変数をいれる。
Prelude> :t Just
Just :: a -> Maybe a
Prelude> :t Just 8
Just 8 :: Num a => Maybe a
Prelude> :t Nothing
Nothing :: Maybe a
#Maybe 型クラス編
型クラスとは
Haskellの型クラスは、C#のインターフェースと概念的に近い。
// IComparableインターフェースを導入すると、値の順序指定や並べ替えができる。
// つまり、二つの引数 aとbの比較ができる。
static T Max<T>(T a, T b)
where T : IComparable
{
return a.CompareTo(b) > 0 ? a : b;
}
Haskellでは、こんな感じ。
-- Ord:型クラス Ord aは型クラス制約
Prelude> :t max
max :: Ord a => a -> a -> a
Ordが型クラスであり、C#で言う、IComparableインタフェースに相当する。
aは任意の型で良いが、Ordという型クラスをaに作用させる事によって、aに制約を加えているので、Ord aは型クラス制約という。
型クラス制約を加える事により、型aに制約が加わる代わりに、等値性をテストできたり(Eq 型クラス)、値同士を比較できたり(Ord 型クラス)、文字列として表示できたり4(Show 型クラス)、数値として扱えたり(Num 型クラス)できる。
そのような制約を入れなければいけない理由としては、Haskellは静的型付け言語であり、どの変数が数なのか、文字列なのかなど、型がどんな要件を満たしている必要があるのかをコンパイル時に完全にわかっていないといけないから。
Maybeの型クラス
Prelude> :info 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’
3行目以降は、
instance [型クラス] [種類]
の形になっている。例えば、
-- instance : keyword
-- Eq : 型クラス
-- Eq a : 型クラス制約
-- a : 種類(今回の場合は、具体型)
instance Eq a => Eq (Maybe a) -- Defined in ‘GHC.Base’
-- instance : keyword
-- Monad : 型クラス
-- Maybe : 種類(今回の場合は、* -> *の種類の型コンストラクタ)
instance Monad Maybe -- Defined in ‘GHC.Base’
という事で、Maybe Int型やMaybe Char型などはEq,Ord,Read,Show,Monoid型クラス6のインスタンスとなっている事がわかる。
また、Maybe型コンストラクタはMonad, Functor, Applicative, Foldable, Traversable型クラスのインスタンスとなっている。
Maybeの全体像としては、こんな感じだが、MonadとかFunctorの説明をしていないので、次回行おうと思う。
参考
すごいHaskellたのしく学ぼう!
https://www.amazon.co.jp/%E3%81%99%E3%81%94%E3%81%84Haskell%E3%81%9F%E3%81%AE%E3%81%97%E3%81%8F%E5%AD%A6%E3%81%BC%E3%81%86-Miran-Lipova%C4%8Da/dp/4274068854
-
違いは、enumは整数値の暗黙の変換を認めるのに対し、Haskellでは、型変換を認めていないところ。 ↩
-
型コンストラクタが型でないとか、値コンストラクタが型でないとかは抽象度のレベルという側面から追っていけば、大した話ではないのですが、すごいH本だと初読のときは混乱しました… ↩
-
3,6~8行目は、deriving(Show)と結びつく話。4,5,9行目は、すごいH本でFunctor - Applicative Functor - Monadとつながる話ですね。 ↩
-
Person型やVector型にderiving(Show)を末尾に加えたのは、文字列として表示したかったからです。derivingキーワードを使えば、Haskellが型クラスの文脈での振る舞いを自動導出してくれます5 ↩
-
すごいHaskell たのしく学ぼう! p.126 ↩
-
Monoid 型クラスは、ある性質(単位元の存在、結合則)を満たす事を条件とするインタフェースです。詳しくは、すごいHaskellたのしく学ぼう!の12章を参照すれば良いと思います。 ↩