HOME | 6. Haskell で最小二乗法 | Haskell のお勉強 | 8. Monad | download | 書き込む |
data Jannkenn = Guu | Choki | Paa
Haskell の class はオブジェクト指向で云うところの 総称関数 (generic function)の組にほぼ相当します。 Python の特殊メソッドに相当するといったほうが分かりやすいかもしれません。
show という関数は Show class に属する(総称)関数で、
それのインスタンス(総称関数モデルで云うところのメソッド)
をある data 型について定義することによって、
その data を表示する方法が定義されます。
例えば、data Jannkenn における show の instance は次のように定義します。
instance Show Jannkenn where show Guu = "Guu" show Choki = "Choki" show Paa = "Paa"同様に、(==) は Eq class に属する総称関数で、 data Jannkenn については次のように定義します。
instance Eq Jannkenn where Guu == Guu = True Paa == Paa = True Choki == Choki = True _ == _ = False
実はこれはかなり当たり前です。このような当たり前のことは
deriving を使うと処理系がやってくれます。
つまり、はじめのデータの定義で、次のようにしておけば、
Show や Eq を自分で定義する必要はありません。
data Jannkenn = Guu | Choki | Paa deriving (Eq, Show)
なお、 付録にじゃんけんをするプログラム jannkenn.hs が付いていますので遊んでみてください。
まず、data a からなる data Vector a を a のリスト [a] に constructor Vec を 作用させて作ります。(1行目)
class Show の instance の作り方が Jannkenn の場合と少し異なっています。 data Vector の表示方法を定義するには、 元になる data a の表示方法が 定義されている必要があるので、6行目のように書きます。 意味は、"data a が class Show の instance であれば、 Vector a は class Show の instance である。" です。ここでは、先頭に 'V' を表示することで Vector を表すようにします。
class Eq の instance も同様にして作ります。
class Num は演算を定義しています。Haskell はデータ型にうるさいので、
演算の前後で型が同じになる必要があります。つまり、内積は 'v1 * v2'
のように定義できません。
ここでは、 (+), (-), negate を定義しています。
内積と長さはそれぞれ 15--17, 19--21 行目のように定義します。
01: data Vector a = Vec [a] 02: 03: instance Show a => Show (Vector a) where 04: show (Vec xs) = 'V' : show xs 05: 06: instance Eq a => Eq (Vector a) where 07: Vec xs == Vec ys = xs == ys 08: 09: -- Num((+), (-), (*), negate, abs, signum, fromInteger), 10: instance Num a => Num (Vector a) where 11: Vec xs + Vec ys = Vec $ zipWith (+) xs ys 12: Vec xs - Vec ys = Vec $ zipWith (-) xs ys 13: negate (Vec xs) = Vec $ map negate xs 14: 15: -- inner product 16: ipro :: (Num a) => Vector a -> Vector a -> a 17: ipro (Vec xs) (Vec ys) = sum $ zipWith (*) xs ys 18: 19: -- length of vector 20: vabs :: Vector Double -> Double 21: vabs (Vec xs) = sqrt $ sum $ map (^2) xs
Mydat> Vec [1,2,3] + Vec [4,5,6] V[5,7,9]
data [a] = [] | a : [a] deriving (Eq, Ord)ここでは、お決まりの二分木を定義してみます。 まず、1--3 行目で、data BST (binary state tree) は Eot (End of tree, 終端) か Node (節)だと定義します。 Node は、挿入位置を決める整数のラベル、値、左右の BST からなっています。 BST をそのまま表示すると見にくいので、連想配列にしてから表示します。 (5--7 行目)
行 | 関数 | 説明 |
---|---|---|
10--15 | bst_find bst key | bst のなかから key をもつレコードを探します。 |
18--23 | bst_insert bst (key, val) | bst に (key, val) のレコードを挿入します。 |
26--28 | bst_map f bst | 各レコードを f で変換し、全体を連想リストにして返します。 |
31-32 | bst2alist | = bst_map id |
[code 4.a]
01: data BST a = Eot 02: | Node Int a (BST a) (BST a) -- key, val, left, right 03: deriving Eq 04: 05: instance Show a => Show (BST a) where 06: show Eot = "Null" 07: show node = "BST" ++ (show $ bst2alist node) 08: 09: -- find a record in a BST 10: bst_find :: BST a -> Int -> Maybe (Int, a) 11: bst_find Eot _ = Nothing 12: bst_find (Node key0 val0 left right) key 13: | key0 == key = Just (key0, val0) 14: | key0 < key = bst_find right key 15: | otherwise = bst_find left key 16: 17: -- insert a record into a BST 18: bst_insert :: BST a -> (Int, a) -> BST a 19: bst_insert Eot (key, val) = Node key val Eot Eot 20: bst_insert (Node key0 val0 left right) (key, val) 21: | key0 == key = Node key0 val left right -- if same key value, replace val 22: | key0 < key = Node key0 val0 left (bst_insert right (key, val)) 23: | otherwise = Node key0 val0 (bst_insert left (key, val)) right 24: 25: -- traverse 26: bst_map :: (a -> b) -> BST a -> [(Int,b)] 27: bst_map _ Eot = [] 28: bst_map f (Node key val left right) = bst_map f left ++ (key, f val) : bst_map f right 29: 30: -- convert to an association list 31: bst2alist :: BST a -> [(Int, a)] 32: bst2alist = bst_map id 33: 34: -- test data 35: bst1 = foldl bst_insert Eot [(5, "five"), (6,"six"), (4,"four"), 36: (3, "three"), (7, "seven"), (1,"One"), (10, "ten") ]
そのような不便を避けるため、名前付きフィールドが用意されています。 4 節の Node には 4 個のフィールドがあるので、名前付きフィールドの例として [code 4.a] を名前付きフィールドを用いて書き直してみました ([code 5.a])。 フィールドの数が増えると効果はもっと顕著でしょう。
[code 5.a]
01: -- Binary state tree 02: data BST a = Eot 03: | Node { 04: node_key :: Int, 05: node_val :: a, 06: node_left :: BST a, 07: node_right :: BST a 08: } 09: deriving Eq 10: 11: instance Show a => Show (BST a) where 12: show Eot = "Null" 13: show node = "BST" ++ (show $ bst2alist node) 14: 15: -- find a record in a BST 16: bst_find :: BST a -> Int -> Maybe (Int, a) 17: bst_find Eot _ = Nothing 18: bst_find node key 19: | key0 == key = Just (key0, node_val node) 20: | key0 < key = bst_find (node_right node) key 21: | otherwise = bst_find (node_left node) key 22: where key0 = node_key node 23: 24: -- insert a record into a BST 25: bst_insert :: BST a -> (Int, a) -> BST a 26: bst_insert Eot (key, val) = Node{ 27: node_key = key, 28: node_val = val, 29: node_left = Eot, 30: node_right = Eot 31: } 32: bst_insert node (key, val) 33: | key0 == key = node -- if same key value, replace val 34: | key0 < key = node{node_right = bst_insert right0 (key, val)} 35: | otherwise = node{node_left = bst_insert left0 (key, val)} 36: where key0 = node_key node 37: left0 = node_left node 38: right0 = node_right node 39: 40: -- traverse 41: bst_map :: (a -> b) -> BST a -> [(Int,b)] 42: bst_map _ Eot = [] 43: bst_map f node = bst_map f (node_left node) ++ 44: (node_key node, f (node_val node)) : bst_map f (node_right node) 45: 46: -- convert to an association list 47: bst2alist :: BST a -> [(Int, a)] 48: bst2alist = bst_map id 49: 50: -- test data 51: bst1 = foldl bst_insert Eot [(5, "five"), (6,"six"), (4,"four"), 52: (3, "three"), (7, "seven"), (1,"One"), (10, "ten") ]実行例
Mydat> bst1 BST[(1,"One"),(3,"three"),(4,"four"),(5,"five"),(6,"six"),(7,"seven"),(10,"ten")] Mydat> bst_insert bst1 (2, "two") BST[(1,"One"),(2,"two"),(3,"three"),(4,"four"),(5,"five"),(6,"six"),(7,"seven"),(10,"ten")] Mydat> bst_find bst1 7 Just (7,"seven") Mydat> bst_find bst1 11 Nothing
簡単な例として面積を表す class Area を定義してみます。 面積を求めるためには、対象となる図形を定義する必要があります。 ここでは、円、長方形を定義しましょう。
コードは次のようになります。
まず、1--2 行目で class Area を定義します。
この class には area という総称関数があります。
つぎに、円、長方形を定義し、その面積の求め方を定義します。
(4--12 行目)
01: class Area a where 02: area :: a -> Double 03: -- 04: data Circle = Circle Double 05: 06: instance Area Circle where 07: area (Circle r) = pi * r * r 08: -- 09: data Rectangular = Rectangular Double Double 10: 11: instance Area Rectangular where 12: area (Rectangular w h) = w * hちなみに、data a を Double に変えるものなら何でも area として定義することが可能で、 物理的な意味を考慮する必要はありません。 例えば、List の area を以下のように定義することが出来ます。 (なんの役に立つかは知りませんが。。)
instance Area [a] where area xs = fromIntegral $ length xs
より詳しい情報は Yet Another Haskell Tutorial にあります。
HOME | 6. Haskell で最小二乗法 | Haskell のお勉強 | 8. Monad | download | 書き込む |