fugafuga.write

日々のログ



すごいH本 part42

インスタンスの自動導出

  • 型クラスはある振る舞いを定義するインターフェースである
  • Haskell ではまずデータ型をつくり、それがどう振る舞えるかを考える
    • 仮にその型が等値制をテストできるなら Eq型クラスのインスタンスにする
    • 仮にその型が大小比較できるものなら Ord型クラスのインスタンスにする
  • Haskell には特定の型クラスのインスタンス宣言を自動導出(derive)する機能がある
    • 自動導出できる型クラスは以下の6つ
      • Eq
      • Ord
      • Enum
      • Bounded
      • Show
      • Read
    • 自動導出にはderivingキーワードを使う

実際に見てみる

以下のような人間を表現するデータ型があったとする

data Person = Person { firstName :: String
                     , lastName :: String
                     , age :: Int
                     }

人間を同一人物かどうか判定することは妥当なのでEq型クラスを自動導出する。

data Person = Person { firstName :: String
                     , lastName :: String
                     , age :: Int
                     } deriving (Eq)

この時、すべてのフィールドの型が Eq型クラスのインスタンスでないとダメ。なぜなら全てのフィールドを比較する必要があるから。

mikeD = Person {firstName = "Michael", lastName = "Diamond", age = 43}
adRock = Person {firstName = "Adam", lastName = "Horovits", age = 41}
mca = Person {firstName = "Adam", lastName = "Yauch", age = 44}

Eqインスタンスを試す

*Main> mca == adRock
False
*Main> mikeD == adRock
False
*Main> mikeD == mikeD
True

Eq a という型クラス制約のついた関数なら Person が使える

*Main> let beastieBoys = [mca, adRock, mikeD]
*Main> mikeD `elem` beastieBoys
True

所感

用語がごっちゃになってきているので整理する必要がある。型クラス、型コンストラクタ、型変数、インスタンスあたり。教えてもらったとおりに頭を真っ白にしてインプットしたほうが良さそう。

[asin:B009RO80XY:detail]

fish shell でコマンドを繋げて実行したい

andを使う

> docker stop ansible_web;and docker rm ansible_web

それまでのコマンドが失敗した場合にorが使える

> docker stop ansible_web;and docker rm ansible_web;or echo 'failed'
Error response from daemon: No such container: ansible_web
failed

(参考) http://fish.rubikitch.com/and/

すごいH本 part41

三次元ベクトルの型とベクトルの演算を作る

data Vector a = Vector a a a deriving (Show)

vplus :: (Num a) => Vector a -> Vector a -> Vector a
(Vector i j k) `vplus` (Vector l m n) = Vector (i+l) (j+m) (k+n)

dotProd :: (Num a) => Vector a -> Vector a -> a
(Vector i j k) `dotProd` (Vector l m n) = i*l + j*m + k*n

vmult :: (Num a) => Vector a -> a -> Vector a
(Vector i j k) `vmult` m = Vector (i*m) (j*m) (k*m)

これらの関数は、

  • ベクトルの加算
  • ベクトルの内積
  • ベクトルをスカラー倍する

実行結果

*Main> Vector 3 5 8 `vplus` Vector 9 2 8
Vector 12 7 16
*Main> Vector 3 5 8 `vplus` Vector 9 2 8 `vplus` Vector 0 2 3
Vector 12 9 19
*Main> Vector 3 9 7 `vmult` 10
Vector 30 90 70
*Main> Vector 4 9 5 `dotProd` Vector 9.0 2.0 4.0
74.0
*Main> Vector 2 9 3 `vmult` (Vector 4 9 5 `dotProd` Vector 9 2 4)
Vector 148 666 222

ポイント

  • Vector a という型なので Vector Int も Vector Float が使える
  • 型クラス制約の Num a が関数に指定されているので Vector Char などは使えない
  • Vector Int と Vector Double を加算したりはできない

所感

ベクトルの内積を調べていたが、高校時代に数学がよくわからなかった理由が「何故そうなるのか?それをなんのために使うのか?」がわからなかったため。教師もそこまで説明していると消化するべきカリキュラムが終わらなかったのだと思う。しかし、以下の記事を読むとしっくり来る回答があった。

ベクトルの内積とは - 大人になってからの再学習

たまたまそうなる、という捉え方ができていればもう少し数学を楽しめたかもしれない。化学や物理と同じように現象としてそうなっているということをもっと不思議に感じておけば興味を持って取り組めたのではないかと今更ながら思った。

[asin:B009RO80XY:detail]

すごいH本 part40

型コンストラクタの話

data Maybe a = Nothing | Just a 

aが型引数でMaybeは型コンストラクタという。型コンストラクタからMaybe Int, Maybe Char, Maybe Stringなどの型を作ることができる。

Haskell には型推論があるので、たいてい明示的に型コンストラクタに型引数を渡さなくてもよい。

*Main Lib> :t Just 'a'
Just 'a' :: Maybe Char
*Main Lib> :t Just 3
Just 3 :: Num a => Maybe a
*Main Lib> :t Just [1,2,3]
Just [1,2,3] :: Num t => Maybe [t]

明示的に型引数を渡す場合は、::をつける

*Main Lib> Just 10 :: Maybe Int
Just 10
*Main Lib> Just 10 :: Maybe Double
Just 10.0

NothingMaybe a型となるので、引数に Maybe Intを取る関数があればNothingを渡すことができる。

どういう場合に型引数をとるのがよいか

ある型がなんらかの箱のように振る舞うなら、型引数をつかうとよい。

data Car = Car { company :: String
               , model :: String
               , year :: Int
               } deriving (Show)

これに型引数を使うとこうなる

data Car a b c = Car { company :: a
                     , model :: b
                     , year :: c
                     } deriving (Show)

しかし、メリットはあまりない。

自動車の情報を表示する関数

tellCar :: Car -> String
tellCar (Car {company = c, model = m, year = y}) = 
    "This " ++ c ++ " " ++ m ++ " was made in " ++ show y

実行結果

*Main> let stang = Car {company="Ford", model="Mustang", year=1967}
*Main> tellCar stang
"This Ford Mustang was made in 1967"

型引数有りで書くとこうなる

tellCar :: (Show a) => Car String String a -> String
tellCar (Car {company = c, model = m, year = y}) =
    "This " ++ c ++ " " ++ m ++ " was made in " ++ show y

実行結果

*Main> tellCar (Car "Ford" "Mustang" 1967)
"This Ford Mustang was made in 1967"
*Main> tellCar (Car "Ford" "Mustang" "nineteen sixty seven")
"This Ford Mustang was made in \"nineteen sixty seven\""

cになんの型でもとれるようになったぐらいしかメリットがない。

このように自動車の型を多相的に表現してもあまり有用ではない。型引数を使うのは、データ型の値コンストラクタに収納された型がデータ型自体の動作にそこまで重要な影響を与えない時。

例えば、リスト型、Maybe型のように値自体がなんであってもその型が機能できるような場合に有用とのこと。

また、データ型宣言には型クラス制約を書かない。なぜなら、データ型宣言時に書いても関数の型宣言時に再び書く必要があるため。(データ宣言に型クラス制約を付ける機能自体が無くなっているはず)

所感

型引数を使うのはどのような場合かが判明した。

[asin:B009RO80XY:detail]