プログラミング Haskell 第 2 版

プログラミング Haskell 第 2 版

 プログラミングHaskell 第2版
Grahum Hutton
ラムダノート
2019-08-02
¥ 3,456

第3章 型と型クラス

p.23

型は、互いに関連する値の集合です。

たとえば、Bool型にはFalseとTrueという二つの真理値が含まれます。
また、「Bool -> Bool」という型には、Bool型をBool型へ変換する否定演算子 notのような関数がすべて含まれます。

vが型 Tの値であるという意味で、「v :: T」という表記を使います。
この形式を型注釈と呼びます。
「v :: T」は、「vの型は Tである」と読みます。

以下に例を示します。

  1
  2
  3
False :: Bool
True :: Bool
not :: Bool -> Boo

p.24

GHCi では、:typeコマンドを式の前に付けると式の型が表示されます。

p.25

3.2 基本型

Char ――― 文字
Unicode のすべての単一文字が値として含まれる型です。
'a'、'A'、'3'、'_'といった通常の英語キーボードにあるすべての図形文字だけでなく、'\n'(改行文字)や '\t'(タブ文字)といった制御文字も含まれます。
他の一般的なプログラミング言語と同様に、単一文字はシングルクォート(' ')で囲みます。

String ――― 文字列
"abc"、"1+2=3"、空文字列 ""のような文字の並びが値として含まれる型です。
他の一般的なプログラミング言語と同様に、文字列はダブルクォート(" ")で囲みます。

Haskellにおけるシングルクォートとダブルクォートの使い分け。

p.26

3.3 リスト型

リストとは、同じ型の要素の並びであり、それらの要素を角括弧で囲みカンマで区切ったものです。
T型の要素を持つリストの型を [T]と書きます。

以下に例を示します。

  1
  2
  3
[False,True,False] :: [Bool]
['a','b','c','d'] :: [Char]
["One","Two","Three"] :: [String]

試しに確かめてみたら、String型にならなかった。Windows10だと挙動が違うとか?

*Main> :t ["One","Two","Three"]
["One","Two","Three"] :: [[Char]]


リストの要素の個数を、そのリストの長さと呼びます。

リスト[]は、長さが0 であり、空リストと呼ばれます。
[False]、['a']、[[]]は、要素が一つのリストです。

[[]]と []は異なるリスト型の値であることに注意してください。
前者は空リストのみを含む長さ 1 のリストであり、後者は要素を持たない単なる空リストです。

p.27

3.4 タプル型

タプルとは、有限個の要素の組です。
各要素の型が異なってもかまいません。
要素は括弧で囲み、カンマで区切ります。
タプルの型は、i番めの要素が型 Tiを持つとき、(T1,T2,...,Tn)と表記します(iは1からnまでの値を取り、nは2 以上です)。

  1
  2
  3
(False,True) :: (Bool,Bool)
(False,'a',True) :: (Bool,Char,Bool)
("Yes",True,'a') :: (String,Bool,Char)

試しに確かめてみたら、String型にならなかった。Windows10だと挙動が違うとか?

*Main> :t (False,True)
(False,True) :: (Bool, Bool)
*Main> :t (False,'a',True)
(False,'a',True) :: (Bool, Char, Bool)
*Main> :t ("Yes",True,'a')
("Yes",True,'a') :: ([Char], Bool, Char)

気になったので検索してみたら、String型は[Char]型の別名、ということらしい。

  • 基本的なデータ型 - ウォークスルー Haskell http://walk.northcol.org/haskell/basic-data-types/

    5. 文字列型
    文字列型の型名は String で,リテラルは "hello" のようにダブルクォートを用いて書きます.
    実は,文字列の実体は文字のリストであり,String は [Char] の別名に過ぎません.
    文字列リテラル "hello" は文字のリスト ['h', 'e', 'l', 'l', 'o'] を表します.

それを先に言え!ってかんじですねw

Haskellのタプルは、注意点が多々あるので、何度もよく読んでおくこと。

3.5 関数型

関数は、ある型の引数を他の型の結果に変換します。
型 T1の引数を型 T2の返り値に変換する関数の型を「T1 -> T2」と書きます。

p.28

関数には、引数の型と結果の型に制限がありません。

そこで、複数の値をリストかタプルで表現すれば、一つの引数を取り一つの結果を返す関数で、複数の引数を取ったり、複数の結果を返したりできます。

たとえば、整数の組を取り足し合わせた値を返す関数 addと、0から与えられた上限までの整数のリストを返す関数 zerotoは、以下のように定義できます。

  1
  2
  3
  4
add :: (Int,Int) -> Int
add (x,y) = x+y
zeroto :: Int -> [Int]
zeroto n = [0..n]

この例では、Haskell の慣習に従って、関数定義の前で関数の型を(型注釈を使って)宣言しています。
関数の型は、関数についての有益な説明になっています。
このような、プログラマーにより指定された型の情報は、型推論を使って自動的に決定された型と照合されます。

3.6 カリー化された関数

p.29

一度に一つの引数を取る関数を、カリー化されていると表現します。

カリー化された関数を扱うときに括弧が過剰になるのを避けるため、二つの規則が採用されています。

一つは、型の中の ->は右結合であるという規則です。
たとえば、以下の型を考えましょう。

Int -> Int -> Int -> Int

これは、以下を意味します。

Int -> (Int -> (Int -> Int))

もう一つは、空白文字を用いて表す関数適用は、必然的に左結合であるという規則です。
たとえば、以下の関数適用を考えます。

mult x y z

これは、以下を意味します。

((mult x) y) z

Haskell では、明示的に括弧付けをしなくても、複数の引数を取る関数はカリー化された関数として定義されます。
また、二つの規則のおかげで、必要な括弧の個数を少なくできます。

p.30

3.7 多相型

型変数

型に型変数を含めることで正確に表現できます。
型変数の先頭は小文字でなければなりません。
通常は、単にa、b、cという名前を使います。
たとえば、lengthの型は次のようになります。

length :: [a] -> Int

すなわち、任意の型 aに対して、関数lengthは型「[a] -> Int」を持ちます。
一つ以上の型変数を含む型や、そのような型を持つ式は、多相的と呼ばれます。
したがって、型「[a] -> Int」は多相型で、lengthは多相関数です。
一般的に、プレリュードで提供される関数の多くは多相的です。

「型変数」という用語の意味の定義が不十分な気がしたので、調べてみた。

  • Haskell の文法

    型変数を使って、型をパラメータ化することもできます。

    data Maybe a = Nothing | Just a

    型変数には、当然ですが具体的な型が入ります。

  • Haskellの型入門 - NaCl非公式ブログ

    型変数、型クラス

    add2 :: a -> a -> a
    add2 x y = x + y

    上記コードのaが 型変数 です。
    サンプルでは型変数名にaを使っていますが、b, cや複数文字を使っても問題ありません。
    この型変数は任意の型が入ることを示しています。
    ただし同じ名前の型変数の型は、全て同じであることに注意してください。

    *Main> :t (+)
    (+) :: Num a => a -> a -> a

    Num a => は初めて見る書き方ですが、これは型変数aに対して型クラス制約を行っています。
    型変数をそのまま使うと任意の型を許容してしまうため、型クラス制約を型変数にかける必要があるということです。



とりあえず、今の段階での理解を整理しておく。

  • 型変数は、任意の型(型情報)を保存する変数。
  • 型について記述するときに、とりあえず型変数を使って宣言しておくことができる。
  • 型変数は、通常「a、b、c、…」のように小文字アルファベットを使っていくみたい。
  • 型変数は、最終的には具体的な型が当てはめられて処理される。
    型を扱うための道具ということみたい。

(参考)型変数の意味とは直接関係ないかもしれないけど、型変数の活用方法=「型依存プログラミング」「型レベルプログラミング」という考え方が紹介されていた。

p.31

3.8 多重定義型

型クラス制約

型クラス制約は C aという形式で書きます。
ここで、Cは型クラス名、aは型変数です。

たとえば、+の型は次のようになります。

(+) :: Num a => a -> a -> a

多重定義型

一つ以上の型クラス制約を持つ型は、多重定義型と呼ばれます。
そのような型を持つ関数は、多重定義されていると言われます。

つまり、Num a => a -> a -> aは多重定義型であり、(+)は多重定義された関数です。
プレリュードで提供される数値関連の関数の多くは多重定義されています。

3.9 基本クラス

型は、互いに関連する値の集合であったことを思い出してください。
この考え方に従えば、型クラス(または単にクラス)とは、共通のメソッドを提供する型の集合です。
ここで、メソッドとは、多重定義された操作のことを指します。
Haskell ではたくさんの基本クラスが標準で提供されています。



ここでいったん停止。

  • 「型クラス」
  • 「型クラス制約」
    の説明が分かりづらかったので、別の説明方法を調べてみる。

型クラスの補足

  • 型クラス - ウォークスルー Haskell

    1. 型クラスとは
    型クラス(type class)は,データ型をカテゴライズする役割を持つ概念です.
    例えば,数値型全般を表す Num という型クラスは,数値型全般を表します.
    Num 型クラスの インスタンス(instance)は,具体的な数値型である Int や Double などです.
    num_class.png


  • 型は、互いに関連する値の集合
    たとえば、Bool型にはFalseとTrueという二つの真理値が含まれます。
  • 型クラスは、互いに関連する「型」の集合
    たとえば、Num型クラスには、具体的な数値型である Int や Double などが含まれます。

とりあえず今の段階では、型の集合が「型クラス」という理解でいいかな?

2. 型クラス制約
型クラスの存在意義がわかる簡単な例題として,リストの要素の和を求める関数 sum を考えてみましょう.

sum []     = 0
sum (x:xs) = x + sum xs

この関数 sum の型はどのように書くのが適切でしょうか.
次のように書いてしまうと,Int 以外の数値型のリストに sum を適用できなくなってしまいます.

sum :: [Int] -> Int

かといって,次のような型だと,数値以外のリストにも sum を適用できることになってしまい,不適切です.

sum :: [a] -> a

そこで,型クラスを用いることで,数値のリストにのみ sum を適用できるようにすることができます.

sum :: Num a => [a] -> a

この型シグネチャ宣言における => の左辺 Num a は,"型 a は Num クラスのインスタンスである" という制約を表します.
この制約は,型クラス制約,あるいは文脈(context)と呼ばれます.

複数の制約がある場合には,次の例のように書きます.

f :: (C a, D a, E b) => [a] -> [b] -> [a]



この説明が分かりやすい!

p.33

Read ――― 読込可能クラス

p034.hs

  1
  2
  3
  4
  5
  6
main = do
  read "False" :: Bool
  read "'a'" :: Char
  read "123" :: Int
  read "[1,2,3]" :: [Int]
  read "('a',False)" :: (Char,Bool)

このスクリプトをWinGHCiでロードして実行したら、エラーになった。

p034.hs:4:1: error:
    ? Couldn't match expected type ‘IO t0’ with actual type ‘[Bool]’
    ? In the expression: main
      When checking the type of the IO action ‘main’
  |
4 | main = do   | ^

何かをimportしないと、read関数は使えないのだろうか?
もしくは、このdo記法では何か間違いがあるのだろうか?
エラーメッセージの意味もよく理解できない。
また、勉強が進んだ後で、デバッグしてみよう。


添付ファイル: filenum_class.png 2件 [詳細]

トップ   編集 凍結 差分 バックアップ 添付 複製 名前変更 リロード   新規 一覧 単語検索 最終更新   ヘルプ   最終更新のRSS
Last-modified: 2019-08-24 (土) 21:54:00 (26d)