2015-12-30 『ふつうのHaskellプログラミング』 もくもく読書会(第9章)の振り返り
ふつうのHaskellプログラミング ふつうのプログラマのための関数型言語入門
- 作者: 青木峰郎,山下伸夫
- 出版社/メーカー: SBクリエイティブ
- 発売日: 2014/10/05
- メディア: オンデマンド (ペーパーバック)
- この商品を含むブログを見る
第9章 型と型クラス
※ この章は既に前の章で出ている内容も多い
式に対する型宣言
- 式にも型宣言が可能
コメント
例だと
luckyNumber = (7 :: Int) unluckyNumber = (13 :: Integer)
とあり、式と言うより値の「7」や「13」に対して型宣言をしている(「7」や「13」がNumではなくIntやIntegerと宣言している)ように思えたので、試しに下記を試してみた
add2 a = a + 2 add2' a = a + 2 :: Integer
ロード後に型を見てみる
> :t add2 add2 :: Num a => a -> a > :t add2' add2' :: Integer -> Integer
式とは演算子を使った計算とかのことかと思っていたが、要は評価できるものということで数値も式であると見なせるらしい (数値リテラルは式であり、結果の値でもある的な…)
こうなってくると式の定義がわからなくなてっきたのでWikipediaを見てみると
式(しき、expression)とは、プログラミングにおいて、言語によって定められた優先順位や結びつきの規定に則って評価される値、変数、演算子、関数の組み合わせである。 数学における式と同様、式は評価された値を持つ。
と言うことで数値リテラルは値のみの組み合わせであり評価された値を持っているので式ということなのだろう。
代数的データ型
- Haskellで新しいデータ型を宣言するにはdata宣言を行う
- data宣言で定義した型は代数的データ型
- 代数的データ型は別の種類の型を統一的に扱うことが可能
構造体スタイル
- 複数の値をまとめるための使われるスタイル
例(本より):
data Anchor = A String String
以下のことを宣言してる
- 新しい型(上記の例だと「Anchor」)
- 型の値を作る方法
- その型が持つフィールド
一般的な構造体スタイルの代数的データ型
data 型コンストラクタ = データコンストラクタ 型1 型2
型コンストラクタ
- 上記の例だと「A」
- アルファベット大文字で始まらないといけない
- 部分適用にも関数合成にも使えるが関数ではない
上記の例の場合、「A」は2つのStringの引数を取るAnchorのコンストラクタ
A "http://se-info-tech.connpass.com/" "SE情報技術研究会"
パターンマッチによるフィールドへのアクセス
- パターンマッチを使ってフィールドにアクセス可能
data Anchor = A String String compileAnchor = A "http://se-info-tech.connpass.com/" "Homepage" showUrl :: Anchor -> String showUrl (A url label) = url
ロード後に実行
> showUrl compileAnchor "http://se-info-tech.connpass.com/"
フィールドラベル
- フィールドラベルを使ってフィールドに名前を付けることが可能
data Anchor = A { url :: String, label :: String }
セレクタ
- フィールドラベルを付けると同名の関数が自動的に宣言される
- フィールドラベルがついたフィールドには下記でアクセス可能
フィールド名 そのフィールドを持つ型の変数名
例:
> url compileAnchor "http://se-info-tech.connpass.com/"
フィールドの更新
- Haskellでは再代入できないのでフィールドの更新はできない
> label compileAnchor "Homepage" > label compileAnchor { label="test"} "test"
これはcompileAnchorのlabelを"test"に変えたのではなく、labelに"test"を持つ新たに生成されたAnchorのurlを出力している
多相的な型の宣言
- フィールドに型変数を持つ代数的データ型も宣言可能
- その場合、型コンストラクタと一緒に型変数の宣言も必要
例(本より):
data Stack a = MkStack [a]
型コンストラクタとデータコンストラクタ
列挙型スタイル
|
で型コンストラクタが持てる値を宣言できる- 下記の例ではデータコンストラクタは「ReadOnly」、「WriteOnly」、「ReadeWrite」
- データコンストラクタに引数なしも可能
→ データコンストラクタが関数と違う点
例:
data OpenMode = ReadOnly | WriteOnly | ReadWrite
共用体スタイル
- 構造体スタイルと列挙型スタイルを融合させたようなスタイル
- 再帰的な代数的データ型の宣言も可能
型の別名と付け替え
type宣言
- 型に別名がつけれる
- 型宣言も使える
例(本より):
type FilePath = String type MyList a = [a]
newtype宣言
- 既存の型を元にして新しい型を宣言する方法
- data宣言とほぼ同じが実行時の表現が違う
コメント
newtyep宣言とdata宣言の違いが本には記述してあるが、結局どう違うのかがよくわからない。
→ 本のようにリストを例にすると、newtypeだと既に定義されている型であるリストとして認識されるが、dataだと新規の型とみなされる
→ そのため実行時にスピードの違いが出るらしい。
型クラス
- 処理できる型を多相型よりも限定したい場合(多相型に制約をつける場合)に使うもの
- 型クラスのように制約のついた多相性のことを「アドホック多相」という。 ※ 制約なしだと「パラメータ多相」という
- 「(型クラス 型変数) =>」で型変数に型クラスの制約がついていることを示す
例:
sort :: (Ord a) => [a] -> [a]
上記は型aがOrdクラスのインスタンスであり、Ordクラスの制約を満たすという意味
クラスメソッド
- 型クラスを特徴づける関数のことをクラスメソッドと呼ぶ
- Haskellでは型ごとにクラスメソッドの実装を変えられる
- 型tが型クラスcのインスタンスの場合、型クラスcのクラスメソッドをすべて実装しないといけない
型クラスの継承
class宣言
本よりEqの例:
calss Eq a where (==), (/=) :: a -> a -> Bool -- クラスメソッドの型宣言 x == y = not (x /= y) -- (==)クラスメソッドのデフォルト実装 x /= y = not (x == y) -- (/=)クラスメソッドのデフォルト実装
- 型クラスは1文字目が大文字でないといけない
- class宣言では型宣言を省略できない
- デフォルト実装はインスタンスで独自に実装しなかった場合に使われる実装
→ Eqクラスのインスタンスの場合、(==)か(/=)のどちらかを実装しないと無限ループに陥る
継承を含むclass宣言
本よりOrdの例:
class (Eq a) => Ord a where…略
instance宣言
- 型が型クラスのインスタンスであることを宣言する際に使う
例(本より):
data Anchor = A String String instance Eq Anchor where (A u l) == (A u' l') = (u == u') && (l == l')
上記はAnchor型がEqクラスのインスタンスであることを宣言している
deriving宣言
- instance宣言を自動生成させる場合に使う宣言
- deriving宣言を行うとinstance宣言の実装が不要になる
- 実装が自明の場合にしか使えない
例(本より):
data Anchor = A String String deriving Eq
コメント
class宣言の記述で、Eqクラスのインスタンスの場合、(==)か(/=)のどちらかを実装しないと無限ループに陥るとあったが、Anchorの例でどちらも実装しないでも問題ないの?
→ 実際に実行して試してみたが問題なかった。なぜ?
これについては下記のリンクにて追記
deriving Eq について (2015-12-30 『ふつうのHaskellプログラミング』 もくもく読書会(第9章)の振り返り) - SE情報技術研究会’s blog
練習問題
特に問題なし
コメント
本題とは関係ないが、リストの宣言で下記はコンパイルエラーになる
lines = [ (L 1000 "test_1"), (L 2000 "test_2"), (L 3000 "test_3") ]
最後の]
に空白を入れてなかったため
本題とは関係ないがimport Data.List
するとlines
という変数名はコンパイルエラーになる
> print $ sortLine lines <interactive>:28:18: Ambiguous occurrence ‘lines’ It could refer to either ‘Main.lines’, defined at type.hs:51:1 or ‘Data.List.lines’, imported from ‘Data.List’ at type.hs:1:1-16 (and originally defined in ‘base-4.8.1.0:Data.OldList’)