すごいHaskellたのしく学んでみる-その6
先週サボったので、二週間ぶりです。
今日のテーマは、、、
- データ型
- レコード構文
- 型引数
- 型クラス
今までずっとオブジェクト指向でやってきた自分にとっては、
ここはなかなか理解しづらく、
未だに0から自分で書ける気がしない、、、
データ型
データ型というのは、値の事。
つまり、IntやBool,Char,Maybeなどなど。
自分でデータ型を定義してみます。
type Name = String data Person = Man Name | Woman Name name :: Person -> String name (Man name) = "Mr." ++ name name (Woman name) = "Ms." ++ name
一行目は、型シノニムを使って、文字列にNameという別名をつけました。
二行目で男性と女性を表す値コンストラクタを定義しています。
では、実行してみたいと思います。
*Main> name $ Man "hoge" "Mr.hoge" *Main> name $ Woman "hoge" "Ms.hoge"
うん、意図通りに動作しました。
Haskellはオブジェクト指向ではないので、
nameという値を持つデータに対して、
hoge.name
のようにアクセスしません。
パターンマッチを使って、コンストラクタに渡した値を取り出します。
この辺がオブジェクト指向に慣れている自分には
非常に違和感がある所でもありました。
上の例では、StringにNameという別名をつけましたが、
これは、「Man String」と書かれても、その文字列は何?
というのがわかりづらいので、別名をつけました。
しかし、今回は引数が一個だから良いものの、
もっと増えてきたら、全部別名つけるの???
Haskellにはレコード構文というのが用意されています。
レコード構文
レコード構文を使えば引数に名前をつけることができます。
data Employee = Employee { name :: String , age :: Int , address :: String , unit :: String } deriving (Show, Eq)
なんとなく、ここの宣言では、関数の型を定義していて、(→name::String)
実際にコンストラクタを呼ぶ時に関数の実装を渡すみたいなイメージなのかな、(name="hoge")
と勝手に思ってみたりしています。
使ってみます。
*Main> Employee{ name="hoge", unit="foobarbaz", age=90, address="tokyo"} Employee {name = "hoge", age = 90, address = "tokyo", unit = "foobarbaz"}
いわゆる名前付き引数なので、順番は変えても大丈夫のようです。
レコード構文を使うと、自動的にフィールドを取得する関数が追加されます。
*Main> let hoge = Employee{ name="hoge", unit="foobarbaz", age=90, address="tokyo"} *Main> name hoge "hoge" *Main> unit hoge "foobarbaz" *Main> age hoge 90 *Main> address hoge "tokyo"
型引数
javaで言うジェネリクスのようなイメージでしょうか、
型を引数で渡します。
HaskellにはMaybeという型があります。
(Scalaで言うOptionのような感じ)
data Maybe a = Nothing | Just a
Maybeは引数にaを取ります。このaが型引数です。
Maybeは型引数を取っているので、「型コンストラクタ」と呼ばれるそうです。
(値コンストラクタと型コンストラクタはいつもわからなくなる、、、orz)
data Box a = EmptyBox | Box a deriving (Show) box_add :: (Num a) => Box a -> Box a -> Box a box_add EmptyBox addend = addend box_add augend EmptyBox = augend box_add (Box x) (Box y) = Box $ x + y
Boxが型コンストラクタというのは、box_addの型宣言を見ると、
なんとなく納得できます。
型引数が無しの型の場合
hoge :: Int -> Int
このIntの所は型が来るわけですが、
型引数がある場合は、Intの所にBox aのように書きます。
つまり、「Box a」という「型」なわけです。
なので、aを引数に取って、Box aという型を作る、型コンストラクタ、になるわけですね。
うむ。
さて、インスタンス自動導出はあまりおもしろくなかったので、
型クラスを自分で作ってみようぜ、
というお話に入りたいと思います。
型クラス
型クラスは、Javaで言えばインターフェースのような物、らしいです。
型クラスの振る舞いは型クラス関数として定義します。
(手続き型言語のクラスのことは全部忘れろって書いてあるけど、
忘れちゃったら仕事にならないので、それは無理っすw)
Eq型クラスを例に書いてみたいと思います。
まず、Eq型クラスの宣言。
class Eq a where (==) :: a -> a -> Bool (/=) :: a -> a -> Bool x == y = not (x /= y) x /= y = not (x == y)
はい、よくわからないです。
自分は今でもよくわからないですが、
Eqは関数の型宣言だけでなく、デフォルト実装も入っているようです。
(ScalaのTraitに近いのかな)
2〜3行目は型の宣言で、
4〜5行目は、「==は/=の逆で、/=は==の逆です」と書いてあるそうです。
(相互再帰というらしい)
これによって、この型クラスのインスタンスは、
「==」「/=」のどちらかを実装すればよくなるそうで、
こういう定義を、「最小完全定義」というらしいです。
言っていることは理解できるんですが、
なんか本質的な所が理解できていないというか、、、
ん〜なんかひっかかる。
とりあえず先に進みたいと思います。
では、こいつのインスタンスを定義してみたいと思います。
さっきのBoxをEq型クラスのインスタンスにしてみたいと思います。
data Box a = EmptyBox | Box a deriving (Show) instance (Eq a) => Eq (Box a) where EmptyBox == EmptyBox = True Box x == Box y = x == y _ == _ = False
ここにはいくつかの要素が含まれています。
1. Eqの引数は型
まず、Eqの引数は型なので、Boxは渡せません。
Boxは型コンストラクタなので、「Box a」で型になります。
2. Boxのフィールドの比較
boxが等価かどうか比較する為に、フィールドの値を比較する必要があります。
ということは、aもEqのインスタンスである必要があるため、
aにEqの型制約をつけます。
では、比較してみましょう。
*Main> EmptyBox == EmptyBox True *Main> EmptyBox /= EmptyBox False *Main> Box 10 == Box 1 False *Main> Box 10 == Box 10 True
今日はここまでにしたいと思います。
次回は、何をやろうかな、、、
本自体は、読み進んでいるんですが、
なかなか理解できないので、何度も読み返していると、
なかなか進まない、、、orz
がんばります。