|
Real World Haskell 第6章 Using Typeclasses
Living in an Open World このセクションではタイプクラスの設計が open world になるように設計されているということを説明している。つまりタイプクラスにインスタンスをどこからでも追加できるということだ。 モジュールのようなコンテクストの中でオーバーロードできるインスタンスが決まっているのを closed world と言うらしい。open world と closed world は演算子/関数のオーバロードの方式に関する専門用語らしいが、読みこなせなかった。 このセクションの最初のコード例は、toValue や fromValue のオーバーローディングがいつでもできますよという例だ。 後半の2つのコード例は、次のセクションに関連してくる。 タイプクラスによる関数のオーバーローディングのどの定義を使うかは引き数の型によって選択されるが、曖昧な引き数が複数の型に解釈できるときに、Overlapping instances エラーが発生する。そういうインスタンスを発生させやすい型によるオーバーロードの定義の例を示している。 When Do Overlapping Instances このセクションでは前のセクションで定義した、[a] というインスタンス instance (JSON a) => JSON [a] where と [(String, a)] というインスタンス instance (JSON a) => JSON [(String, a)] where の2つが、[("foo", "bar")] というデータに関して overlapping instance になるため、実行時エラーになってしまうことを示している。つまり、このデータが [a] なのか [(String, a)] なのか区別できないからだ。 理屈では分かっても実際にコードをテストしてみないとわからないので test.hs というファイルにコードを記述してテストしてみる。まず、データ型 JValue を思い切り手抜きして次のように定義する。 data JValue = NotDefined deriving (Show) これを ghci で :l test.hs でテストするとコンパイルできる。そこで、JSON クラスの宣言を追加する。 class JSON a where toJValue :: a -> JValue fromJValue :: JValue -> Either String a これも ghci の :r コマンドでテストするとコンパイルできる。さらに、String 型を JSON クラスのインスタンスにする。 instance JSON String where toJValue s = NotDefined fromJValue = undefined 今度は、インスタンスの宣言の仕方がよくないと指摘されてしまう。 Prelude> :r [1 of 1] Compiling Main ( test.hs, interpreted ) test.hs:7:0: Illegal instance declaration for `JSON String' (All instance types must be of the form (T t1 ... tn) where T is not a synonym. Use -XTypeSynonymInstances if you want to disable this.) In the instance declaration for `JSON String' Failed, modules loaded: none. そこで指摘のとおりにソースの冒頭に TypeSynonymInstances スイッチを ON にする pragma を書き込む。 {-# LANGUAGE TypeSynonymInstances #-} もう一度 :r コマンドでテストしたら今度は成功した。 Prelude> :r [1 of 1] Compiling Main ( test.hs, interpreted ) Ok, modules loaded: Main. *Main> toJValue "hello" NotDefined そこで、いよいよ [a] と [(String, a)] を JSON クラスのインスタンスにする。 instance (JSON a) => JSON [a] where toJValue [a] = NotDefined fromJValue = undefined instance (JSON a) => JSON [(String, a)] where toJValue [(s, a)] = NotDefined fromJValue = undefined 今度は :r コマンドでテストすると、次のようにインスタンスの宣言が悪いので FlexibleInstances スイッチを使うようにとのメッセージが出る。 Prelude> :r [1 of 1] Compiling Main ( test.hs, interpreted ) test.hs:17:0: Illegal instance declaration for `JSON [(String, a)]' (All instance types must be of the form (T a1 ... an) where a1 ... an are type *variables*, and each type variable appears at most once in the instance head. Use -XFlexibleInstances if you want to disable this.) In the instance declaration for `JSON [(String, a)]' Failed, modules loaded: none. そこで、FlexibleInstances スイッチを pragma に追加する。 {-# LANGUAGE TypeSynonymInstances, FlexibleInstances #-} 再度 :r でテストするとコンパイルできるが、 *Main> :r Ok, modules loaded: Main. 次のように toValue を [("foo", "bar")] に関数適用すると、RWH の本文に見られたような Overlapping instances エラーがでる。 *Main> toJValue [("foo", "bar")] <interactive>:1:0: Overlapping instances for JSON [([Char], [Char])] arising from a use of `toJValue' at <interactive>:1:0-24 Matching instances: instance (JSON a) => JSON [a] -- Defined at test.hs:13:9-28 instance (JSON a) => JSON [(String, a)] -- Defined at test.hs:17:9-38 In the expression: toJValue [("foo", "bar")] In the definition of `it': it = toJValue [("foo", "bar")] ここまでで、このセクションの内容は再現できたが、次のセクションに出てくる OverlappingInstances 言語拡張を pragma に記述してみる。 {-# LANGUAGE TypeSynonymInstances, FlexibleInstances, OverlappingInstances #-} するとインスタンスの overlapping の問題も解決されて、めでたく toValue [("foo", "bar")] が実行される。 *Main> :r [1 of 1] Compiling Main ( test.hs, interpreted ) Ok, modules loaded: Main. *Main> toJValue [("foo", "bar")] NotDefined タイプクラスを実際のプログラムで活用するためには、Haskell 98 report の仕様だけでは使い難いようだ。この場合でも、GHC の言語拡張をソースの冒頭の部分で pragma に記述することで使い易くなるのが分かる。 蛇足だが、テストプログラムの全文を次に示す。 {-# LANGUAGE TypeSynonymInstances, FlexibleInstances, OverlappingInstances #-} data JValue = NotDefined deriving (Show) class JSON a where toJValue :: a -> JValue fromJValue :: JValue -> Either String a instance JSON String where toJValue s = NotDefined fromJValue = undefined instance (JSON a) => JSON [a] where toJValue [a] = NotDefined fromJValue = undefined instance (JSON a) => JSON [(String, a)] where toJValue [(s, a)] = NotDefined fromJValue = undefined Real World Haskell 第6章 Using Typeclasses
第6章ではあまり実際のコードのテストをしていなかったのでおさらいをしてみる。 まず、自前のタイプクラスの使い方の演習。 ファイル名:eqclasses.hs class BasicEq a where isEqual, isNotEqual :: a -> a -> Bool isEqual x y = not (isNotEqual x y) isNotEqual x y = not (isEqual x y) instance BasicEq Bool where isEqual True True = True isEqual False False = True isEqual _ _ = False BasicEq タイプクラスを定義して、generic funcition として isEqual と isNotEqual を定義している。次に instance キーワードで Bool 型を BasicEq タイプクラスのインスタンスに登録する。 実行例: *Main> :l eqclasses.hs [1 of 1] Compiling Main ( eqclasses.hs, interpreted ) Ok, modules loaded: Main. *Main> isEqual True True True *Main> isNotEqual True True False 次は、Typeclasses at Worl: Making JSON Easier to Use セクションの実習。 まず、SimpleJSON モジュールが必要なので再掲する。 ファイル名:SimpleJSON -- file: chap05/SimpleJSON module SimpleJSON ( JValue(..) , getString , getInt , getDouble , getObject , getArray , isNull ) where data JValue = JString String | JNumber Double | JBool Bool | JNull | JObject [(String, JValue)] | JArray [JValue] deriving (Eq, Ord, Show) -- accessor functions getString :: JValue -> Maybe String getString (JString s) = Just s getString _ = Nothing getInt :: JValue -> Maybe Int getInt (JNumber n) = Just (truncate n) getInt _ = Nothing getDouble :: JValue -> Maybe Double getDouble (JNumber n) = Just n getDouble _ = Nothing getBool :: JValue -> Maybe Bool getBool (JBool b) = Just b getBool _ = Nothing getObject :: JValue -> Maybe [(String, JValue)] getObject (JObject o) = Just o getObject _ = Nothing getArray :: JValue -> Maybe [JValue] getArray (JArray a) = Just a getArray _ = Nothing isNull v = v == JNull JSON タイプクラスの宣言と、JBool 型についての toJValue 関数と、fromJValue 関数のテスト。 ファイル名: JSONClass.hs import SimpleJSON type JSONError = String class JSON a where toJValue :: a -> JValue fromJValue :: JValue -> Either JSONError a instance JSON Bool where toJValue = JBool fromJValue (JBool b) = Right b fromJValue _ = Left "not a JSON boolean" 実行例は次のようになる。toJValue はうまく動いた。 *Main> :l JSONClass.hs [1 of 2] Compiling SimpleJSON ( SimpleJSON.hs, interpreted ) [2 of 2] Compiling Main ( JSONClass.hs, interpreted ) Ok, modules loaded: SimpleJSON, Main. *Main> toJValue True JBool True ところが、formValue を試すと formValue のタイプパラメータの型が曖昧だというエラーになってしまう。 *Main> fromJValue (JBool True) <interactive>:1:0: Ambiguous type variable `a' in the constraint: `JSON a' arising from a use of `fromJValue' at Probable fix: add a type signature that fixes these type variable(s) 困ってしまって、"Real World Haskell fromJValue not work" で Google 検索していたら、戻値の型指定をすれば良いと教えてもらった。read の時と状況が似ている。 *Main> fromJValue (JBool True) :: Either JSONError Bool Right True *Main> fromJValue (JNumber 3) :: Either JSONError Bool Left "not a JSON boolean" 動作確認ができたので、次のセクションに読み進めることができるようになった。
ここから、
【ゆりにゃ】The World Warrior (rap ver.)【踊ってみた】 ここへ来て、 The World Warrior street fighter 2 hyadain HD ここへたどり着いた。 Hyadain/ヒャダイン 元気が出ました。ヒャダインさんありがとう。 Real World Haskell 第6章 Using Typeclasses
Typeclasses at Work: Making JSON Easier to Use このセクションでは、まず、簡単なJSONのデータをHaskellの形式に翻訳している。次に元々のJSONの記述と Haskell のデータ型に翻訳したものを引用する。 JSON版 { "query": "awkward squad haskell", "estimatedCount": 3920, "moreResults": true, "results": [{ "title": "Simon Peyton Jones: papers", "snippet": "Tackling the awkward squad: monadic input/output ...", "url": "http://research.microsoft.com/~simonpj/papers/marktoberdorf/", }, { "title": "Haskell for C Programmers | Lambda the Ultimate", "snippet": "... the best job of all the tutorials I've read ...", "url": "http://lambda-the-ultimate.org/node/724", }] } Haskell 版 result :: JValue result = JObject [ ("query", JString "awkward squad haskell"), ("estimatedCount", JNumber 3920), ("moreResults", JBool True), ("results", JArray [ JObject [ ("title", JString "Simon Peyton Jones: papers"), ("snippet", JString "Tackling the awkward ..."), ("url", JString "http://.../marktoberdorf/") ]]) ] Haskell 版の場合、リストの要素の型は全て同じでなくてはならないなどのHaskellの型システムの制約から、データは全て JValue のデータコンストラクタによってラッピングして使用しないといけない。 そのため JSON の要素の置き換えや作成などを行うときも JNumber や JString などの複数のデータコンストラクタを使い分けなければならない。これではデータ操作の関数を記述するときに関数の記述が複雑になってしまう。一方型付けのないシステムでは、そういう詳細の検討はいらないので、どんなタイプのデータも同じ関数で処理できて、表記が簡単になる。(しかしランタイムのバグが紛れ込む可能性が高い。) そこで狙いとしては、toValue x という関数を定義して、x の型を toValue が識別してデータコンストラクタを自動的に選択するようにしたい。Show クラスの関数の show が引き数の型を自動判別して文字列に変換するのに似ている。 また、fromValue :: JValue というように関数の戻値を :: (type signature) で指定することでコンテナの値を formValue 関数だけで取り出したい。これは、Read クラスの関数 read が文字列を type signature にしたがって様々な型のデータに変換するのと似ている。 ただし、read クラスの場合は引き数が全て文字列だったが、fromValue の場合は引き数は JValue 型なので type signature の指定とコンテナのデータ型が合っていない場合はエラーになる。そこで、formValue では戻値を Either 型にすることで、プログラムを止めずにエラー処理できるように工夫してある。 このように、タイプクラスのシステムを利用することで、煩雑になりがちなデータ型ごとの処理を関数でひとつにまとめることができる。 More Helpful Errors このサブセクションでは、Either 型について述べてある。Either 型は標準で組み込まれており、関数の実行が失敗するような場合の処理に利用されている。Maybeと似ているが、Maybeは関数の実行が失敗した時単に Nothing を返すだけだが、Either 型では、Left と一緒に文字列のメッセージを返すことができる。 次に Either 型のデータ型の定義をを示す。 data Either a b = Left a | Right b deriving (Eq, Ord, Read, Show) Either a b とあるが別にデータコンストラクタがパラメータを2つ取るという意味ではなく、a 型と b 型という2つの型が利用されるという意味だ。 これを使って fromValue の実行が失敗した時にエラーメッセージを返すようにするためには次のようにする。 instance JSON Bool whrere toJValue = JBool fromJValue (JBool b) = Right b fromJValue _ = Left "not a JSON boolean" Making an Instance with a Type Synonym このサブセクションではインスタンス宣言をする時に次のように type synonym を使えず、インスタンスがリストのとき、リストの要素としては type parameter のみが許されることを説明している。例えば次のような instance 宣言は許されない。 instance JSON String where String は [Char] の type synonym (型の別名)だからだ。タイプクラスのインスタンスとして [a] は使えるが、[Char] は使えない。Haskell 98 Report の文法の性質から、type synonym はインスタンスとして使えないし、また、 リストも [a] のように type parameter を使ったリストではない [Char] 等のリストはインスタンスとして使用できないからだ。 Haskell 98 Report の文法を読んでみたが、部分的な抜粋だったのかよくわからなかった。レポート全体を理解して読むのは無理のような気がする。 Haskell 98 Report では文法を規定しているだけだが、それに準じて実装すると、文法の範囲以外のものはエラーになってしまう。しかし、GHC は拡張機能を持っており、ソースにコンパイル時の行動を指示する次のような pragma を記述しておくと、type synonym もインスタンスとして使うことができる。 {-# LANGUAGE TypeSynonymInstances #-} 単純なデータ型を使うタイプクラスの使い方は割にシンプルだが、複雑なデータ型になってくると上のように運用が難しくなる。後のサブセクションでは、型同士の衝突について述べてあるようだ。これらの知識も実際にプログラムを作ってテストしてみないと分からないが、今の段階ではとりあえず説明を読んで理解するだけにする。 上の説明にしても、Haskell 98 Report に記述されている Haskell の文法を読みこなしたり、GHC の pragma とはなんだろうと調べてみたり、脱線をかなり深くしないと本当の理解には繋がらない。それだけ、プログラム言語の理解をするということは奥深いということだが、灘高の国語の先生が授業で使った「銀の匙」という小説を題材に徹底的に脱線して題材を掘り下げるという方法が、こんな所にも直接的な形で生きている。 単に、目の前の仕事を片付けるという意味では、こういう探索の仕方は時間の浪費だが、しかし、これがなされていないと突然起こったエラーメッセージの処理などが解決できず立ち往生することにもつながる。脱線の労苦を覚悟するという態度も忘れるわけにはいかない。プログラマの生産性には100倍以上の差があるという話だが、こういう無駄な努力の積み重ねが見かけの驚異的な生産性の元になるのだろう。 著者たちのそういう知識の探索の様子が、今回のRWHの本文の理解のしがたさの端々に現れているような気がする。著者たちの簡単な説明が背後の深い知識の積み重ねの上になされている場合、その背景を知らない読者は分かり難いと考えるのだ。 続きは次回で。 Real World Haskell 第6章 Using Typeclasses
Numeric Types 数の計算(+ などの演算子や sin などの関数)にタイプクラスが必要なのは不思議な感じがするが、ハスケルの扱える数の型の種類が数多いのを見ると必要性が分かる。 ちなみに列挙すると、Double, Float, Int, Int8, Int16, Int32, Int64, Integer, Rational, Word, Word8, Word16, Word32, Word64 がある。それらの詳細はRWHの表に詳しい。 これらの演算が全て同じ + 記号でできるのもタイプクラスのおかげだ。 Numeric クラスの演算子は括弧をつけることによって前置演算子に返ることができる。例えば 1 + 2 は (+) 1 2 と表記することが可能だ。 Numeric クラスの演算子や関数は非常に多いが、RWHに表でまとめてある。Numeric クラスのインスタンスの型は異なる者どうしの演算はできない。変換のための関数が用意してある。例えば Double -> Int は truncate, Int -> Double は fromIntegral などだ。異なる型のデータの演算はこれらで明示的に変換して行わないと型エラーになる。詳細は RWH に詳しい。 Equality, Ordering, and Comparisons 同等性の比較や、順序付けのためのタイプクラスがあり、それぞれ Eq, Ord が標準で定義してある。Eq クラスの演算子は == と /= で、Ord クラスには >=, <= などがある。 Automatic Derivation deriving キーワードを使って自動的にクラスにインスタンスを登録する方法について書いてある。deriving キーワドの使い方については前回の記事でも実践してみた。全てのユーザ定義のデータが自動登録できるわけではなく、できないものもある。その場合は、手動で演算子や関数を定義しなければならない。 このあたりは、本文を読んでわかりにくいと思うところはない。記事が短くなってしまったが、次の Typeclasses at Work: Making JSON Easier to Use 以下は実践的なタイプクラスの活用法の記事になっているようなので、ここで一区切りとしておく。
Real World Haskell 第6章 Using Typeclasses
Important Built-in Typeclasses このセクションではいくつかの Haskell Prelude 標準のタイプクラスについて述べてある。タイプクラスの機能はこの言語の特徴の中心的なもののひとつだ。タイプクラスの詳細についてはHaskell ライブラリ・リファレンスが良い情報源だ。 Show Show タイプクラスは各種データ型の値を文字列に変換する。Show タイプクラスで最も重要な関数は show だ。show の関数の型は次のようになる。 Prelude> :type show show :: (Show a) => a -> String すなわち、Show タイプクラスのインスタンスのデータ型の値を引数に取り、それを文字列に変換して戻す。具体的には次のようになる。 Prelude> show 3.14 "3.14" リストや配列などの型も show 関数を使って文字列に変換できる。 *Main> show [1,2,3] "[1,2,3]" *Main> show (1,2) "(1,2)" show は文字列に対しても定義されている。 *Main> show "Hello!" "\"Hello!\"" さまざまな文字列に対する show の振る舞いは RWH を参照してほしい。 show を使って3.14というDouble型のデータを "3.14" という文字列に変換しないと、putStrLn を使って数値をコンソールに印字できない。 Prelude> putStrLn 3.14 No instance for (Fractional String) arising from the literal `3.14' at Possible fix: add an instance declaration for (Fractional String) In the first argument of `putStrLn', namely `3.14' In the expression: putStrLn 3.14 In the definition of `it': it = putStrLn 3.14 show を使って次のようにすればOKだ。 Prelude> putStrLn (show 3.14) 3.14 ghci に直接値を入力すると、show 関数で文字列に変換してターミナルに出力する。 Prelude> 3.14 3.14 したがって次のようにユーザ定義データ型を data キーワードで宣言しても、 data Color = Red | Green | Blue Color 型が Show タイプクラスのデータ型でなければ ghci に直接値を入力するとエラーになってしまう。 *Main> Red No instance for (Show Color) arising from a use of `print' at Possible fix: add an instance declaration for (Show Color) In a stmt of an interactive GHCi command: print it この場合は次のように Color 型を Show クラスのインスタンスにすればエラーを回避できる。 ファイル名:test.hs data Color = Red | Green | Blue instance Show Color where show Red = "Red" show Green = "Green" show Blue = "Blue" 実行例 *Main> Red Red もっと簡単には deriving キーワードを使って data キーワードによるデータ型の宣言の時に次のように Color 型を Show タイプクラスのインスタンスにしてしまう。 data Color = Red | Green | Blue deriving (Show) Read Read タイプクラスは Show タイプクラスの逆の振る舞いをする。Read タイプクラスの関数 read は文字列を引き数にとって、Read タイプクラスのインスタンスとして含まれる色々な型の値に変換する。read 関数の型は次のようになる。 *Main> :type read read :: (Read a) => String -> a つまり文字列を引き数にとって、Read タイプクラス型のインスタンスの型のうちのどれかの値に変換する。どの型に変換するかは型指定演算子で指定する。 *Main> read "3" :: Int 3 *Main> read "3" :: Double 3.0 ユーザ定義のデータ型を Read タイプクラスのインスタンスに登録して、read 関数を定義する手続きはやや複雑になる。それは、read 関数が文字列をパースして型指定演算子で指定された型に変換しなければならないからだ。RWH 本文の例示プログラムの骨格だけを抜き出したものを次に示す。 data Color = Red | Green | Blue deriving (Show) instance Read Color where readsPrec _ value = tryParse [("Red", Red), ("Green", Green), ("Blue", Blue)] where tryParse [] = [] tryParse ((attempt, result):xs) = if (take (length attempt) value) == attempt then [(result, drop (length attempt) value)] else tryParse xs これは次のように read として働くが、なぜそうなるのかは分からない。Read タイプクラスの仕様を詳しく調べる必要がある。Read タイプクラスの関数 readPred _ value が何に使われるのかがわかっていないからだ。 *Main> read "Red" :: Color Red *Main> read "[Red,Green]" :: [Color] [Red,Green] しかし、これも次のように安直に deriving で逃げ切ることができる。 data Color = Red | Green | Blue deriving (Read, Show) 実行例 *Main> read "[Red, Green, Blue]" :: [Color] [Red,Green,Blue] Serialization with read and show メモリ内の構造化されたデータをディスクに文字列として保存することを serialization というが、read と show を使うと簡単に実行できる。次の例は ghci 上でデータの文字列化と文字列からの復元を行なってみたものだ。 *Main> let d1 = [Just 5, Nothing, Nothing, Just 8, Just 9] :: [Maybe Int] *Main> :type d1 d1 :: [Maybe Int] *Main> let str1 = show d1 *Main> :type str1 str1 :: String *Main> let d2 = read str1 :: [Maybe Int] *Main> :type d2 d2 :: [Maybe Int] *Main> d1 == d2 True [Maybe Int] 型のデータ d1 を作成し、show 関数で str1 という文字列に変換しそれを read で再び元のデータ型に復元している。 ファイルへの書き出しや、複雑なデータ型の例が RWH に紹介してある。 まとめ Show タイプクラスのインスタンスに登録されている型のデータは簡単に文字列に変換できる。また、Read タイプクラスのインスタンスに登録されている型のデータは、文字列から簡単に復元できる。read と show を組み合わせると、構造化されたデータの serialization や文字列からのデータの復元が簡単にできる。
Real World Haskell 第6章 Using Typeclasses
タイプクラス Typeclass とは何か Real World Haskell の第6章は、Using Typeclasses だ。タイプクラス Typeclass を一言で説明するのは難しいが、例えば左右の値が等しいことを示す同値演算子 == のように、いろいろな型について「等しい」という同じ意味を同じ記号でテストしたい時などに使われる。 いろいろな型が == 演算子で等しいかどうかをテストできるが、それらの型は Eq タイプクラス のメンバーとして登録されている必要がある。タイプクラスとは型の集まりだ。 このような演算子や関数のひとつに show がある。この関数は色々な型のデータを文字列に変換してくれる。この際も、それらの型は Show タイプクラスに登録されていなければならない。 このようなタイプクラスの利点は何かという説明をあまり見たことがないが、ひとつには関数を共有するデータ型をはっきりと把握することで、タイプクラスの関数による期待外の動作を防いでいることがあると思う。 もう一つは、タイプクラスのジェネリックな関数を記述できるので、ユーザ定義型でのタイプクラスの関数の定義が最小限でも多様な関数を使うことができるということだ。 例えば Eq クラスには == という値が等しいかどうかをテストする演算子があるが、Eq タイプクラスの型には、/= という等しくないかどうかをテストする演算子も使うことができる。また、新たにユーザ定義型を作ってそれを Eq クラスに登録する際も、== 演算子の振る舞いだけを記述するだけでそのユーザ定義型に /= 演算子を使えるようになる。 タイプクラスのシステムを使うための手順は、class キーワードによるタイプクラスの宣言と、instance キーワードによるデータ型をタイプクラスに登録して、タイプクラスの関数をそのデータ型に適するようにプログラムするという、やや複雑な手順をとらなくてはならないので、管理人も最初は少し混乱した。 しかし、一旦この手順を理解したら、同じ抽象的な概念であれば、どんなデータ型についても同じ名前の演算子や関数を利用することができるようになる。同じような概念であれば同じ名前の関数でプログラムできるということは、プログラムの記述上も非常に強力な武器を手に入れることになる。少し Haskell に慣れてきたら絶対に習得しなくてはならない知識だ。 また、このタイプクラスを理解することで、ユーザ定義のデータ型を定義するときに必須といえる deriving キーワードを理解することができる。deriving はユーザ定義の型を、タイプクラスに自動的に登録できる非常に便利な機能だ。しかし、これはタイプクラスを理解しておかないと、その意味が分からない。 RWHは第6章で1章を使って、このタイプクラスの解説をしている。第6章に何が書かれているかは、最後の Conclusion に記載してある。直訳になるが次のようになる。 この章では、タイプクラスの必要性とその使い方について学んだ。まず、自分でユーザ定義のタイプクラスを定義した。それから、Haskell の標準ライブラリのなかの重要なタイプクラスについて調べた。最後に、ユーザ定義のデータを自動的にタイプクラスに登録する方法を学んだ。タイプクラスの説明は、複雑になりやすいが、著者のこの構想を理解してからRWHの本文を読むと分かりやすい。 The Need for Typeclasses タイプクラスがないと同じデータの「等しさ」を調べるのに、名前の異なる複数の関数が必要になることを示してある。その例として、ユーザ型 Color を定義し、その型の値の等しさをチェックする colorEq を定義している。また、文字列の同等性をチェックする stringEq を定義し、おなじような「等しさ」をチェクする関数が複数あってはわずらわしいこと、できれば、== や /= のような演算子や関数は共通で使いたいこと、タイプクラスはそのための仕組みであることが説明してある。 What are Typeclasses? 型ごとにその値の等しさをチェックする関数 colorEq と stringEq が必要だった不便を、タイプクラスが解消してくれることを示している。まず class キーワードによってユーザ定義のタイプクラス BasicEq を宣言しその関数 isEqual のデータ型を次のように宣言する。 class BasicEq a where isEqual :: a -> a -> Bool 次に instance キーワードを使って、Bool 型の isEqual 関数の実体を定義している。 instance BasicEq Bool where isEqual True True = True isEqual False False = True isEqual _ _ = False そうして、この isEqual が String 型やColor 型にも使えるようにプログラムすることができることを説明してある。 さらに、このセクションでは、BasicEq クラスに isEqual だけでなく、isNotEqual をジェネリックな関数 generic function として定義できることを示している。詳細は本文を参照。 Declaring Typeclass Instances このセクションでは instance キーワードでタイプクラスのインスタンス(タイプクラスに属するデータ型)を定義できることを示してある。例として Color型 に isEqual が使えるように、これを instance キーワードで BasicEq のインスタンスに登録している。その際に isEqual の Color 型の実体を定義する事になる。 instance BasicEq Color where isEqual Red Red = True isEqual Green Green = True isEqual Blue Blue = True isEqual _ _ = False 上のプログラムについて、このセクションの内容とは関係ないが、条件分岐を記述するときのパターンマッチの威力を表している。 次回は、important Built-in Typeclasses セクションで、重要なビルトインのタイプクラスの使い方を調べる。
|
カテゴリ
主インデックス
jQuery デモ ツールボックス Haskell 記事リスト Prelude Haskell ボーカロイド 考えるということ XAMPP Ruby ubuntu 脳の話 話のネタ リンク 幸福論 キリスト教 心の話 メモ 電子カルテ Dojo JavaScript C# NetWalker 以前の記事
最新のトラックバック
最新のコメント
ファン
|