宣言と束縛
この章では、Haskellの 宣言 の構文と簡略した意味論を説明する。
module | → | module modid [exports] where body | |
| | body | ||
body | → | { impdecls ; topdecls } | |
| | { impdecls } | ||
| | { topdecls } | ||
topdecls | → | topdecl1 ; … ; topdecln | (n ≥ 1) |
topdecl | → | type simpletype = type | |
| | data [context =>] simpletype [= constrs] [deriving] | ||
| | newtype [context =>] simpletype = newconstr [deriving] | ||
| | class [scontext =>] tycls tyvar [where cdecls] | ||
| | instance [scontext =>] qtycls inst [where idecls] | ||
| | default (type1 , … , typen) | (n ≥ 0) | |
| | foreign fdecl | ||
| | decl | ||
decls | → | { decl1 ; … ; decln } | (n ≥ 0) |
decl | → | gendecl | |
| | (funlhs | pat) rhs | ||
cdecls | → | { cdecl1 ; … ; cdecln } | (n ≥ 0) |
cdecl | → | gendecl | |
| | (funlhs | var) rhs | ||
idecls | → | { idecl1 ; … ; idecln } | (n ≥ 0) |
idecl | → | (funlhs | var) rhs | |
| | (empty) | ||
gendecl | → | vars :: [context => ] type | (type signature) |
| | fixity [integer] ops | (fixity declaration) | |
| | (empty declaration) | ||
ops | → | op1 , … , opn | (n ≥ 1) |
vars | → | var1 , … , varn | (n ≥ 1) |
fixity | → | infixl | infixr | infix |
構文的カテゴリtopdeclsに属する宣言はHaskellモジュール(5章)の最上位のみ許す一方で declsは最上位またはネストされたスコープのいずれかで使ってもよい(例えば、let
かwhere
の内でtopdeclsを構築する)。
説明のため、type
とnewtype
、data
宣言からなるユーザー定義のデータ型(セクション4.2)とclass
とinstance
、default
宣言からなる型クラスとオーバーロード(セクション4.3)、値束縛と型シグネチャ、固定の宣言からなるネストされた宣言(セクション4.4)の3つのグループに宣言を分割する。
haskellは(整数や浮動小数点数のような)"ハードウェアで実現された"であるいくつかのプリミティブなデータ型を持つ。しかし、多くの"ビルドイン"なデータ型は通常のHaskellコードによって定義されていて、通常type
やdata
宣言に使われる。これらの"ビルドイン"のデータ型はセクション6.1で詳細に説明される。
型とクラスの概要
Haskellは静的型意味論4,6を提供するために伝統的なHindley-Milner多相型システムを使用するが、その型システムは構造化された手法にオーバーロード関数を導入するために提供する 型クラス (または クラス )で拡張されている。
class
宣言(セクション4.3.1)は新しい 型クラス とあらゆるそのクラスのインスタンスの型によってもサポートされなければいけないオーバーロードされた操作を導入する。instance
宣言(セクション4.3.2)は型がクラスの インスタンス であり、名付けられた型でインスタンス化されるオーバーロードされたオペレーション、クラスメソッドと呼ばれる、の定義を含むということを宣言する。
例えば、型Int
とFloat
で操作(+)
とnegate
をオーバーロードしたいと考えたとしよう。Num
と呼ばれる新しい型クラスを導入する。
class Num a where -- Numの単純化されたクラス宣言
(+) :: a -> a -> a -- (NumはPreludeで定義されている)
negate :: a -> a
この宣言は「型a
がもし与えられた型で定義されたクラスメソッド(+)
とnegate
があるならクラスNum
のインスタンスである」と読めるであろう。
このクラスのインスタンス化のときにInt
とFloat
をその際に宣言できる。
instance Num Int where -- Num Intの単純化されたインスタンス
x + y = addInt x y
negate x = negateInt x
instance Num Float where -- Num Floatの単純化されたインスタンス
x + y = addFloat x y
negate x = negateFloat x
addInt
やnegateInt
、addFloat
、negateFloat
はこのケースでプリミティブ関数で想定されるが、一般的にはユーザー定義関数になり得る。
上のはじめの宣言は「Int
はクラスNum
のインスタンスであり、その証拠として(クラスメソッド)(+)
とnegate
が定義されている」と読まれる。
型クラスのより多くの例はJones8かWadlerとBlott13による論文で見つけられる。用語'型クラス'はオリジナルのHaskell1.0型システムを記述するために使われてあって、'コンストラクタクラス'はオリジナルの型クラスへ拡張を記述することに使われていた。ふたつの異なる用語を使う理由はもはやなく、この規格書において、'型クラス'という単語は元々のHaskell型クラスとJonesによって導入されたコンストラクタクラスの両方を含んでいる。
カインド
型の表現が有効である確証を得るために、型の表現を異なるカインド(カインド, kind)へと分類され、以下の2つの可能な形式の内、1つを取る。
- シンボル
*
は全ての引数のない型コンストラクタのカインドを意味する。 - もしK1とK2がカインドならば、K1 → K2はカインドK1の型を取り、K2の型を返す型のカインドである。
型推論が値の表現の正当性をチェックするのと同様にして、カインド推論は型の表現の正当性をチェックする。しかしながら、型とは違い、カインドは完全に暗黙的であり、言語の見て分かる部分には存在しない。カインドの推論はセクション4.6で議論される。
型の構文
type | → | btype [-> type] | (function type) |
btype | → | [btype] atype | (type application) |
atype | → | gtycon | |
| | tyvar | ||
| | ( type1 , … , typek ) | (tuple type, k ≥ 2) | |
| | [ type ] | (list type) | |
| | ( type ) | (parenthesised constructor) | |
gtycon | → | qtycon | |
| | () | (unit type) | |
| | [] | (list constructor) | |
| | (->) | (function constructor) | |
| | (,{,}) | (tupling constructors) |
Haskellの型の表現のための構文は上に与えられる。データ値と同じようにデータコンストラクタを使って作られ、型の値(訳注: 型の表現でそれ以上簡約出来ないもののこと)は 型コンストラクタ から作られる。データコンストラクタと同様に、型コンストラクタの名前は大文字で始められる。データコンストラクタとは違い、中置型コンストラクタは許されない((->)
以外)。
型の表現の主要な形式は次のものになる。
-
小文字で始まる識別子のように書かれた型変数。変数のカインドは現れた文脈によって暗黙的に決定される。
-
型コンストラクタ。多くの型コンストラクタは大文字から始まる識別子のように書かれている。 例えば、
Char
やInt
,Float
,Double
、Bool
はカインド*
で構築される型である。Maybe
とIO
は単項型コンストラクタで、カインド*→*
をもつ型として扱われる。- 宣言
data T ...
またはnewtype T ...
は型のボキャブラリーに型コンストラクタT
を追加する。T
のカインドはカインドの推論によって決定される。
特殊な構文は特定のビルドインの型コンストラクタに提供される。
- 自明型 は
()
のように書かれ、カインド*
を持つ。それは"引数のないタプル"型を示し、()
と書かれるが、値をちゃんと持つ(セクション3.9と6.1.5を参照)。 - 関数型 は
(->)
のように書かれ、カインド∗→∗→∗
を持つ。 - リスト型 は
[]
のように書かれ、カインド∗→∗
を持つ。 - タプル型 は
(,), (,,)
等のように書かれる。それらのカインドは∗→∗→∗, ∗→∗→∗→ ∗
などである。
(->)
と[]
の定数の使用は下でより詳しく説明される。 -
型適用。もし、t1がカインドK1 → K2の型でt2がカインドK1の型であるなら、その時t1, t2はカインドK2の型の表現である。
-
括弧つき型 、形式(t)を持つ、型tと同一である。
例えば、型の表現IO a
は変数a
に定数IO
への適用のように理解されることが可能だ。IO
型コンストラクタはカインド∗→∗
を持ち、変数a
と式全体の両方を従え、式IO a
はカインド*
を持たなければならない。一般的に 型の推論 (セクション4.6)の処理は適切なカインドをユーザー定義のデータ型や型のシノニム、クラスへ決定することを必要とされる。
特別な構文は特定の型の表現がより伝統的なスタイルで書かれることを許すために提供される。
- 関数型 は形式t1 -> t2を持ち、型(->) t1 t2に等しい。アロー関数は左に関連づける。例えば、
Int -> Int -> Float
はInt -> (Int -> Float)
を意味する。 - タプル型 はK ≥ 2である形式t1, ..., tkを持ち、括弧の間にk-1個のカンマがある型(,…,) t1 … tkと等しい。それは型t1をはじめの要素に、型t2を2番目の要素に持つなど、k要素のタプルの型を示す(セクション3.8と6.1.4を参照)。
- リスト型 は形式[t]を持ち、型[] tと等しい。それは型tの要素を伴うリストの型を示す(セクション3.7と6.1.3を参照)。
これらの特別な構文的形式は何がスコープに入っているかにかかわらず関数、タプル、リストのビルドイン型のコンストラクタを常に示す。同様な方法で、プリフィックスな型コンストラクタ(->), [], (), (,)
等はビルドインの型コンストラクタを常に示す。それらは修飾子を付けることはできず、そしてまたリストのimport/exportするもののリストに入れることもできない(5章)。(上述の特定の生成規則、"gtycon"から)
リストとタプル型が特別な構文を持つのだが、それらの意味論はユーザー定義された代数データ型と同じである。
式と型は一貫した構文を持つことに注意すること。もし、tiは式またはパターンeiの型ならその時、式(\ e1 -> e2), [e1]と(e1,e2)は各々、型(t1 -> t2), [t1]と(t1,t2)を持つ。
ひとつの例外(クラス宣言内の区別された型変数のこと(セクション4.3.1))を除いて、Haskellの型の表現内の型変数は一般に全て全称量化されていると仮定され、全称量化のための明示的な文法はない4。例えば、型の表現a -> a
は型∀ a. a → a
を示す。明確にするために、しかしながら、Haskellプログラムの型を議論する時に明示的な個々の区分をしばしば書く。明示的に個々に区分された型を書く時、∀
のスコープは可能な限り左側へ拡張する。例として、∀ a. a → a
は∀ a. (a → a)
を意味する。
クラス表明と文脈の構文
context | → | class | |
| | ( class1 , … , classn ) | (n ≥ 0) | |
class | → | qtycls tyvar | |
| | qtycls ( tyvar atype1 … atypen ) | (n ≥ 1) | |
qtycls | → | [ modid . ] tycls | |
tycls | → | conid | |
tyvar | → | varid |
クラス表明 は形式qtycls tyvarを持ち、クラスqtyclsの型tyvarのメンバを示す。クラス識別子は大文字で始める。 文脈 は0個以上のクラス表明からなり、一般に( C1 u1, …, Cn un )の形式をもつ。ここでC1, …, Cnはクラス識別子であり、( u1, …, un)はそれぞれ変数型または一つ以上の型への変数型の適用のいずれかである。n = 1のとき括弧の外側は省かれるであろう。一般的に、文脈を示すためにcxを使用し、cx => tを文脈cxによって制限された型tを示すために書く。文脈cxはtによって参照される変数型のみを含まなければいけない。利便性のために、文脈cxが空であっても、具体的な構文は=>を含まないケースであるが、cx => tを書く。
型とクラスの意味論
このセクションは、型システムの簡略的な詳細を提供する。(WadlerとBlott[13]、Jones[8]は各々より詳細に型とコンストラクタクラスを議論している。)
Haskellの型システムは 型 をプログラム内の各式に割り当てる。一般的に、型は形式∀ u. cx ⇒ t
である。uは変数型の集合u1, ..., unである。どのような型であっても、cxに束縛がない一般的な個々に区別された変数型uiはtでも束縛がないものでなければならない。その上、内容cxはセクション4.1.3上で与えられた形式でなければならない。例として、ここにいくつかの正常な型がある。
Eq a => a -> a
(Eq a, Show a, Eq b) => [a] -> [b] -> String
(Eq (f a), Functor f) => (a -> b) -> f a -> f b -> Bool
3つの型で、制約Eq (f a)
はf
が全称量化されているためもっと単純にはできない。
式eの型は型をeに束縛のない変数へ与えるため 型環境 に依存し、いずれかの型を宣言する クラス環境 はいずれかのクラスのインスタンスである。(型はインスタンス
宣言または派生
節の存在によってのみクラスのインスタンスになる。)
型は一般化による半順序集合(下に明記される)で関連する。多くの一般的な型は、一般化の先行順によって同等まで導かれ、(与えられた環境の)個々の式は 主要な型 と呼ばれるものに割り当てられる。Haskellの拡張されたHindley-Milner型システムは全式の主要な型を推論でき、オーバーロードされたクラスメソッドの妥当な使用を含んでいる(セクション4.3.4で説明するように、確実に曖昧なオーバーロードが起こり得るのだが)。したがって、明示的な型付け(型シグネチャと呼ぶ)は通常、オプションである(セクション3.16と4.4.1を参照)。
型∀ u. cx1 ⇒ t1
は領域が以下のようなuの代用Sがある場合に限り、型∀ w. cx2 ⇒ t2
より一般的 である。
- t2はS(t1)と同じである。
- cx2はそのクラスの環境を保持し、S(cx1)も保持する。
型∀ u. cx ⇒ t
の値は内容cx[s;/u]を保持する場合に限り型sでインスタンス化されるかもしれない。例えば、関数double
について考えてみる。
double x = x + x
double
の最も一般的な型は∀ a. Num a⇒ a → a
である。double
は(Int
にインスタンス化する)型Int
の値に適用されるかもしれない、なぜならNum Int
が成り立つ、すなわちInt
はクラスNum
のインスタンスだからである。しかしながら、double
が型Char
の値に通常の意味で適用されることはないであろう。なぜなら、Char
は通常クラスNum
のインスタンスではないからだ。ユーザーはインスタンスのような宣言を選択するかもしれない。その場合、double
が型Char
の値に通常の意味で適用されることはないであろう。
ユーザー定義のデータ型
このセクションでは、代数のデータ型(data
宣言)や新たに命名するデータ型(newtype
宣言)、型のシノニム(type
宣言)を説明する。これらの宣言はモジュールのトップレベルでのみ現れてよい。
代数データ型宣言
topdecl | → | data [context =>] simpletype [= constrs] [deriving] | |
simpletype | → | tycon tyvar1 … tyvark | (k ≥ 0) |
constrs | → | constr1 | … | constrn | (n ≥ 1) |
constr | → | con [! ] atype1 … [! ] atypek | (arity con = k, k ≥ 0) |
| | (btype | ! atype) conop (btype | ! atype) | (infix conop) | |
| | con { fielddecl1 , … , fielddecln } | (n ≥ 0) | |
fielddecl | → | vars :: (type | ! atype) | |
deriving | → | deriving (dclass | (dclass1, … , dclassn)) | (n ≥ 0) |
dclass | → | qtycls |
constrの優先順位は式と同じである。通常のコンストラクタの適用が中置コンストラクタの適用より高い優先順位を持つ(そのためa : Foo a
はa : (Foo a)
のように解析する)。
代数的なデータ型の宣言は、cxが内容である形式data cs => T u1 ... uk = K1 t1 1 ... t1k1 | ... | Kn tn1 ... tnkn
を持つ。
この宣言は0個以上の構成要素であるデータコンストラクタ K1, …, Knをもつような新しい型コンストラクタTを導入する。
このリポートで、修飾されていない用語"コンストラクタ"は"データコンストラクタ"を常に意味する。
データコンストラクタの型はKi :: ∀ u1 … uk. cxi ⇒ ti1 → ⋅⋅⋅ → tiki → (T u1 … uk)
によって与えられる。
ここでcxiは、ti1, …, tikiの型に自由に出現する型変数たちのみを含むようなcxの部分集合の中で最大なものである。
型変数u1からukは互いに異なるものでなければならず、cxとtijに出現してもよい。
また、他の型変数がcxやそれより右側に出現すると静的なエラーとなる。
新しい型定数Tは引数の変数uiのカインドκiがセクション4.6で説明されるカインド推論によって決定される形式κ1 →… → κk →∗のカインドを持つ。
これはTが0からk引数のどこでも型の表現に使われるかもしれないということを意味する。
例えば、以下の宣言は
data Eq a => Set a = NilSet | ConsSet a (Set a)
カインド∗→∗
の型コンストラクタSet
を導入し、型ありのコンストラクタNilSet
とConsSet
は以下のものである。
NilSet :: ∀ a. Set a
ConsSet :: ∀ a. Eq a ⇒ a → Set a → Set a
与えられた例では、ConsSet
にオーバーロードされた型はConsSet
は型がクラスEq
のインスタンスである値に提供されることのみ可能であることを保証する。ConsSet
に対照したパターンマッチングはEq a
拘束にも発生する。例えば、
f (ConsSet a s) = a
関数f
は推論された型Eq a => Set a -> a
を持つ。data
宣言の内容は他に何も効果を持たない。
データ型のコンストラクタの可視性(すなわちデータ型の"抽象度")は、それが定義されたモジュールの外では、セクション5.8で説明されるエクスポートリスト内のデータ型の名前の形式によって制御される。
data
宣言の内容は他に何も効果を持たない付加的なderiving
の部分は 派生されたインスタンス と関係しており、セクション4.3.3で説明される。
ラベル付けされたフィールド 引数k個とるデータコンストラクタはk要素のオブジェクトを作成する。これらの要素は通常、式またはパターンの中のコンストラクタへの引数のように位置付けして呼び出される。巨大なデータ型のために、データオブジェクトの要素に フィールドラベル を割り当てることは便利である。これはコンストラクタ内でその位置を独立して参照されるために明記するフィールドを許す。
data
宣言のコンストラクタ定義はラベルを記録構文(C { ... })
を使用するコンストラクタのフィールドに割り当てられるだろう。フィールドラベルを使用するコンストラクタはそれらなしにコンストラクタを自由に組み合わされるかもしれない。フィールドラベルに関係するコンストラクタは通常のコンストラクタのようにまだ使われるだろう。ラベルを使う機能は基礎となる位置上のコンストラクタを使う操作のための単純な簡略記法である。その位置上のコンストラクタの引数はラベル付けされたフィールドのように同じ順序で発生する。例えば、以下の宣言は
data C = F { f1,f2 :: Int, f3 :: Bool }
下のように生成されるものと同一な型とコンストラクタを定義する。
data C = F Int Int Bool
フィールドラベルを使用する操作はセクション3.15で説明される。data
宣言においては、型シノニムを展開した後にそのフィールドが使われている場所すべてで同じ型がつく場合に限り、同じフィールドラベルを複数のコンストラクタで使ってもよい。ラベルはスコープ内の型以上で共有されることはできない。フィールド名は通常の変数とクラスメソッドを使う最上位の名前空間を共有し、スコープ内の他の最上位の名前と衝突してはいけない。
パターンF {}
はコンストラクタF
が 記録構文を使って宣言されているかどうかにかかわらず 、F
によって構築された任意の値と一致する。
正格なフラグ データコンストラクタが適用されるたびに、代数的データ型の宣言に対応する型が感嘆符!
で表される正格なフラグを持つ場合に限り、コンストラクタの各引数は評価される。
"!"
は通常のvarsymであって、reservedopとしては字句解析されない。
それはデータ宣言の引数の型の内容にのみ特別な意味を持つ。
変換: 各siが形式!tiかtiのいずれかである形式data cx => T u1 … uk = … | K s1 … sn | …
の宣言は(\ x1 … xn -> ( ((K op1 x1) op2 x2) … ) opn xn)という式の中のKの全ての発生を置き換える。
opiはもしsiが形式tiなら、正格ではない適用関数$
であり、opiはもしsiが形式! ti
であるなら正格に適用する関数$!
である(セクション6.2を参照)。
K上のパターンマッチングは正格なフラグによる影響を受けられない。
型シノニムの宣言
topdecl | → | type simpletype = type | |
simpletype | → | tycon tyvar1 … tyvark | (k ≥ 0) |
型シノニムの宣言は古い型と等しい新しい型を生成する。
それは新しいコンストラクタTを生成する形式type
T u1 ... uk = tを持つ。
型(T t1 …tk)は型t[t1∕u1, …, tk∕uk]に等しい。
型変数u1からukは互いに異なるものでなければならず、t上のみにスコープされる。
そしてそのtの中に他の型変数が現れたら静的エラーになる。
新しい型コンストラクタTのカインドは引数uiのカインドκiは形式κ1 →… → κk → κであり、tの右側のκはセクション4.6で説明されるカインドの推論によって決定される。
例えば、次の定義はリスト型のコンストラクタを書く方法の代替案を提供することに使用されることができる。
type List = []
型シノニムの宣言によって生成された型コンストラクタのシンボルTは一部のみを提供されることはできない。十分な数の引数なしにTを使うことは静的エラーになる。
再帰的と相互再帰的なデータ型は許されるのだが、 代数的データ型 が入り込む限り、型シノニムではそうではない。例えば、
type Rec a = [Circ a]
data Circ a = Tag [Rec a]
は許されるが、それに反して、
type Rec a = [Circ a] -- invalid
type Circ a = [Rec a] -- invalid
はそうではない。似たもので、type Rec a = [Rec a]
も許されない。
型シノニムはより読みやすい型シグネチャを作る便利な、しかし厳密な構文的仕組みである。同義語とその定義はinstance
宣言のインスタンス型を除いて、完全に置き換えできる(セクション4.3.2を参照)。
データ型の改名
topdecl | → | newtype [context =>] simpletype = newconstr [deriving] | |
newconstr | → | con atype | |
| | con { var :: type } | ||
simpletype | → | tycon tyvar1 … tyvark | (k ≥ 0) |
newtype cs => T u1 ... uk = N t
の形の宣言は新しい型を導入し、その表現はすでに存在している型と同じである(訳注: TはNから新たに作られた型であるが、実行時表現が等しい)。
型(T u1… uk)はデータ型tを改名する。
それは型シノニムからオリジナルな型からまたはその型へ明示的に強制されなければならない厳密な型を作成することとは異なる。
また型シノニムと異なり、newtype
は再帰的な型を定義することに使用されるかもしれない。
式の中のコンストラクタNは型tから型(T u1 … uk)へ値を強制する。
パターンの中のNは型(T u1 … uk)から型tへ値を強制する。
これらの強制は実行時のオーバーヘッドなしに実装されるかもしれない。
newtype
はオブジェクトの根底にある表現を変更しない。
新しいインスタンス(セクション4.3.2を参照)はnewtype
によって定義された型に定義されることができるが、型シノニムに定義されることはないかもしれない。
newtype
によって作成された型は代数的データ型が追加の間接レベルを持つ表現内の代数的データ型とは異なる。
この差は効率が悪い表現にアクセスするかもしれない。
この差はパターンマッチングのための異なるルールに反映される(セクション3.17を参照)。
代数的なデータ型とは異なり、新しい型コンストラクタNは リフトしない 、そのため、N ⊥
は⊥
と同じである。
次の例はdata
(代数的データ型)とtype
(型シノニム)、newtype
(型の改名)との差を明確にする。以下の宣言が与えられる。
data D1 = D1 Int
data D2 = D2 !Int
type S = Int
newtype N = N Int
d1 (D1 i) = 42
d2 (D2 i) = 42
s i = 42
n (N i) = 42
式(d1 ⊥)
と(d2 ⊥)
、(d2 (D2 ⊥))
は⊥
と全て等しい。一方で、(n ⊥)
と(n (N ⊥))
、(d1 (D1 ⊥))
、(s ⊥)
は42
と全て等しくなる。特別に、(N ⊥)
は(D1 ⊥)
が⊥
と等しくないときは⊥
と等しくなる。
newtype
宣言のオプション的に派生部分はdata
宣言の派生要素と同じ方法で扱われる。セクション4.3.3を参照すること。
newtype
宣言はフィールド名をつける構文を使用するかもしれず、もちろんそのフィールドしかないかもしれない。従って、
newtype Age = Age { unAge :: Int }
はコンストラクタとデコンストラクタの両方をスコープに持ち込む。
Age :: Int -> Age
unAge :: Age -> Int
型クラスとオーバーロード
クラス宣言
topdecl | → | class [scontext =>] tycls tyvar [where cdecls] | |
scontext | → | simpleclass | |
| | ( simpleclass1 , … , simpleclassn ) | (n ≥ 0) | |
simpleclass | → | qtycls tyvar | |
cdecls | → | { cdecl1 ; … ; cdecln } | (n ≥ 0) |
cdecl | → | gendecl | |
| | (funlhs | var) rhs |
クラス宣言 は新しいクラスとその中のオペレーション(クラスメソッド)を生成する。クラス宣言は次の一般的な形式を持つ。
class cx => C u where cdecls
これは新しいクラスの名前Cを生成し、型変数uはそのクラスの本体のクラスメソッドシグネチャ上でのみスコープされる。内容cxは下で説明するCのスーパークラスを明記する。cxの中で参照されるであろう型変数のみがuである。
スーパークラスの関係は循環してはいけない。例)指示された非環式のグラフを構成しなければいけない。
class
宣言のcdecls部分は3種類の宣言を含む。
- クラス宣言は新しいクラスメソッドviを生成し、スコープはclass宣言の外側に展開する。
クラス宣言のクラスメソッドはcdecls内の明示的な型シグネチャvi :: cxi => tiにあるviそのものである。
クラスメソッドは変数束縛とフィールド名と一緒に最上位の名前空間を共有する。
それらはスコープの他の最上位の束縛と衝突してはならない。
そのため、クラスメソッドは最上位の定義やフィールド名、他のクラスメソッドのように同じ名前を持つことはできない。
トップレベルのクラスメソッドviの型はvi :: ∀u,w.(Cu,cxi) ⇒ tiである。 tiはuを言及しなければいけないし、uより型変数wを言及するかもしれない。 その場合、viの型はuとwの両方に多相的である。 cxiはwのみ束縛するだろう。 特に、cxiはuを束縛しなくともよい。 例えば、class Foo a where op :: Num b => a -> b -> a
ここでのop
型は∀ a, b.(
である。Foo
a, Num b) ⇒ a → b → a. - cdeclsは(他の値ではなく)そのクラスのメソッドに対する 結合性宣言 を含んでもよい。 しかしながら、クラスメソッドはトップレベルの値を宣言することから、他の選択肢としてクラスメソッドの結合性宣言はクラス宣言の外側であるトップレベルに現れてもよい。
- 最後に、cdeclsはviのいずれかの デフォルトクラスメソッド を含められる(セクション4.3.2)。
デフォルトメソッドの宣言は通常、左手側が変数か関数定義のみであろうことを除いて値の定義である。
例えば、
class Foo a where op1, op2 :: a -> a (op1, op2) = ...
は、許可されない。デフォルト宣言の左手側がパターンだからだ。
これらのケース以外に、cdecls内の宣言は許されない。
where
部を伴わないclass
宣言はオリジナルの全てのクラスメソッドを継承する巨大なクラスのコレクションを合成することに便利かもしれない。
例えば、
class (Read a, Show a) => Textual a
このような場合、型が全スーパークラスのインスタンスであるなら、たとえサブクラスが直ちにクラスメソッドを持たなくても、サブクラスのインスタンスに 自動的にはならず 、instance
宣言はwhere
部を伴わず明示的に与えられなければならない。
インスタンス宣言
topdecl | → | instance [scontext =>] qtycls inst [where idecls] | |
inst | → | gtycon | |
| | ( gtycon tyvar1 … tyvark ) | (k ≥ 0, tyvars distinct) | |
| | ( tyvar1 , … , tyvark ) | (k ≥ 2, tyvars distinct) | |
| | [ tyvar ] | ||
| | ( tyvar1 -> tyvar2 ) | (tyvar1 and tyvar2 distinct) | |
idecls | → | { idecl1 ; … ; idecln } | (n ≥ 0) |
idecl | → | (funlhs | var) rhs | |
| | (empty) |
インスタンス宣言 はクラスのインスタンスを生成する。クラス宣言はclass cx => C u where { cbody }
という風に行う。対応するインスタンス宣言の一般的な形式は次のものになる:instance cx′ => C (T u1 … uk) where { d } where k ≥ 0
。型(T u1 … uk)はシンプルな型変数u1, … ukに提供される型コンストラクタTの形式を取らなければいけない。さらにTは型シノニムであってはならず、uiは全て互いに異ならなければならない。
以下のようなインスタンス宣言は禁止である。
instance C (a,a) where ...
instance C (Int,a) where ...
instance C [[a]] where ...
宣言dはCのクラスメソッドのみの束縛を含められる。スコープにないクラスメソッドへの束縛を与えることは不正であるが、スコープ内にあるものの名前は重要ではない。特に、それは修飾子付きの名前でもよいであろう。(このルールはセクション5.2のエクスポートリスト内に従属する名前に使われることと同一である。)例として、range
は修飾子付きの名前Data.Ix.range
のみがスコープ内にあるが、これは正当である。
module A where
import qualified Data.Ix
instance Data.Ix.Ix T where
range = ...
class
宣言内にすでに与えられているゆえに、宣言はあらゆる型シグネチャまたは固定宣言を含めないであろう。デフォルトのクラスメソッド(セクション4.3.1)の場合のように、メソッド宣言は変数または関数定義の形式を取らなければならない。
もし、いくつかのクラスメソッドに束縛が与えられなければ、その時、class
宣言内の対応するデフォルトのクラスメソッドは(提供しているなら)使われる。デフォルトが存在しないなら、その時、このインスタンスのクラスメソッドはundefined
に束縛され、コンパイル時エラーは発生しない。
型TをクラスCのインスタンスであるよう生成するinstance
宣言はC-Tインスタンス宣言と呼ばれ、以下の静的な制約に従うべきである。
-
型はプログラム上で1回以上個々のクラスのインスタンスのように宣言されるだろう。
-
クラスと型は同じカインドを持たなければいけない。これはセクション4.6で説明される使用するカインドの推論を決定されることが可能だ。
-
インスタンス型(T u1 … uk)内の型変数がインスタンス内容cx'に束縛を満たすということを推測するべきだ。この推測の元、次の2つの状態も満たされなければならない。
- Cのスーパークラスの内容cx[(T u1 … uk)∕u] によって表現された束縛が満たされなければならない。言い換えると、TはCのスーパークラスの各インスタンスでなければならず、全スーパークラスのインスタンスの内容はcx'によって暗示されなければいけない。
- 正しく型付けされたd内のクラスメソッド宣言に要求されるインスタンス型内の型変数上の束縛も満たされなければいけない。
実際に異常なケースを除いては、インスタンス宣言から上記2つの制約を満たす最も一般的なインスタンス文脈cx'を推論することが可能である。だが、それでも明示的なインスタンスの内容を書くことは強制される。
次の例はスーパークラスインスタンスによって強いられた制限事項を説明する。
class Foo a => Bar a where ...
instance (Eq a, Show a) => Foo [a] where ...
instance Num a => Bar [a] where ...
この例はHaskellにおいて正常である。Foo
はBar
のスーパークラスであるため、2つ目のインスタンス宣言は[a]
が仮定Num a
の下でFoo
のインスタンスである時に正常である。1つめのインスタンス宣言はこの仮定の下で[a]
がFoo
のインスタンスであると実際に告げる。なぜなら、Eq
とShow
はNum
のスーパークラスだからだ。
もし2つのインスタンス宣言が代わりにこのように読むなら。
instance Num a => Foo [a] where ...
instance (Eq a, Show a) => Bar [a] where ...
そのとき、そのプログラムは不正である。2つ目のインスタンス宣言は[a]
が仮定(Eq a, Show a)
の下、Foo
のインスタンスであるときのみ正常である。しかし、[a]
がもっと強い仮定Num a
の下でFoo
のインスタンスのみであることから、これは保持しない。
instance
宣言のさらに進んだ例は9章で見つけられるだろう。
導出されたインスタンス
セクション4.2.1で言及したように、data
とnewtype
の宣言は任意のderiving
の形式を含んでいる。
もしその形式が含まれていたら、そのデータ型に対して 導出されたインスタンス宣言 が指定されたクラスのそれぞれについて自動的に生成される。
これらのインスタンスはユーザー定義されたインスタンスと同じ制限事項に従うべきである。
型TへクラスCを導出している時、Cの全スーパークラスのインスタンスはTのために存在しなければならず、明示的なinstance
宣言を経由またはderiving
句にスーパークラスを含むことを経由するかのいずれかである。
導出されたインスタンスはユーザー定義のデータ型へ便利なよく使われるオペレーションを提供する。
例えば、クラスEq
の中のデータ型に導出されたインスタンスが==
と/=
オペレーションを定義すると、それらを定義する必要からプログラマーを解放する。
導出されたインスタンスが許されるPreludeのクラスはEq
とOrd
、Enum
、Bounded
、Show
、Read
のみであり、図6.1で全て図示される。
これらの句それぞれに生成される導出されたインスタンスの様相の精密な詳細は11章に提供されており、そこにはそのような導出されたインスタンスが可能である時の仕様書を含んでいる。
標準ライブラリによって定義された句も導出可能であろう。
もしderiving
の形式で名付けられたクラス上でinstance
宣言を導出することが可能でないなら、静的エラーが結果となる。
例えば、すべてのデータ型が正しくEnum
のクラスメソッドをサポートできるわけではない。
それはまた導出されたクラスに明示的なinstance
宣言を与えるため静的エラーになる。
もしderiving
形式がdata
またはnewtype
宣言から省略されたなら、そのときそのデータ型への いかなる インスタンス宣言も導出されない。
すなわち、deriving
形式を省くことは空の導出の形式: deriving()
を含んでいることと同等である。
曖昧な型とオーバーロードされた数値演算子の既定値
topdecl | → | default (type1 , … , typen) | (n ≥ 0) |
Haskellスタイルのオーバーロード固有の問題は 曖昧な型 の可能性があるということである。
例えば、11章で定義されたread
とshow
関数を使い、もし単なるInt
とBool
がRead
とShow
のメンバーなら、その時の次の式
let x = read "..." in show x -- invalid
は曖昧である。なぜならshow
とread
の型は、下の2つの式の
show :: ∀ a. Show a ⇒ a → String
read :: ∀ a. Read a ⇒ String → a
両方のケースでa
をInt
またはBool
のどちらでもインスタンス化で満たすことが可能だからだ。
そのような式は不適切な型付けだと考えられ、静的エラーである。
型∀ u. cx ⇒ t
内で、もしtではなくexの中に存在するuに型変数uがあるなら、式e
が 曖昧な型 を持つと言う。
そのような型は不正である。
例えば、先程のshow
とread
を伴う式はその型が∀ a. Show a, Read a ⇒ String
であるから曖昧な型を持つ。
曖昧な型はユーザーからの入力によってのみ回避できる。 その方法のひとつはセクション3.16で説明された 式の型シグネチャ の使用を通じてである。 例として、先程与えられた曖昧な式において、以下のように書くことで、
let x = read "..." in show (x::Bool)
型から曖昧さを取り除く。
型シグネチャにより固定された型を与える以外にも、曖昧な式はある変数と同じ型にしなければならない場合がしばしばある。
これは関数asTypeOf
:x
(9章)の用途がxの値を持つということであるが、xとyは同じ型を持つように強制される。
例えば、asTypeOf
y
approxSqrt x = encodeFloat 1 (exponent x ‘div‘ 2) ‘asTypeOf‘ x
(encodeFloat
とexponent
の説明についてはセクション6.4.6を参照)
Num
クラスの曖昧さはとてもありふれているので、Haskellはこれを解決する別の方法を提供している。
それはデフォルト宣言である。
n ≥ 0で、各tiがNum ti
が保持するdefault (t1 , … , tn)
。
曖昧な型が発見された状態で、もし以下の条件を満たすなら、曖昧な型変数v
はデフォルト可能である。
- vは
C v
の形の制約の中でのみ出現し(ただしCはクラスである)、かつ - 少なくともこれらのクラスの一つが数値クラス(
Num
、またはNum
のサブクラス)であり、 - これらのクラスの全てがPreludeまたは標準ライブラリの中で定義されている(図6.2-6.3は数値クラスを示し、図6.1はPrelude内で定義されたクラスを示す)。
各デフォルト可能な変数は全ての曖昧な変数のクラスのインスタンスであるデフォルトリストの初めの型によって置き換えられる。 そのような型が見つからなければ、静的エラーである。
ひとつのデフォルト宣言のみがモジュールごとに許可され、その効果はそのモジュールに制限される。 もしデフォルト宣言がモジュール内で与えられないなら、その時は次のようなものであると仮定する。
default (Integer, Double)
空のデフォルト宣言default ()
はモジュール内の全てのデフォルトをオフにする。
ネストされた宣言
次の宣言はモジュールのトップレベルを含むどんな宣言リストでも使用される。
型シグネチャ
gendecl | → | vars :: [context => ] | type |
vars | → | var1 , …, varn | (n ≥ 1) |
型シグネチャは可能な限り内容を尊重して変数の型を明示する。型シグネチャはiがそれぞれ1からnを取るvi :: cx => t
を表明することと同等な形式: v1, …, vn :: cx => t
を持つ。
各viは型シグネチャを含む同じ宣言リストに束縛する値を持たなければいけない。
例えば、型シグネチャをスコープ外へ束縛された変数へ与えることは不正である。
その上、たとえそのシグネチャが同一であっても、一つの変数に一つ以上の型シグネチャを与えることは不正である。
セクション4.1.2で言及されたように、シグネチャに現れた全ての型変数はそのシグネチャ上で全称量化され、従って型変数のスコープはそれを含む型シグネチャに制限される。 例として、次の宣言、
f :: a -> a
f x = x :: a -- invalid
2つの型シグネチャ内のa
は完全に異なる。
それにまたx
が型∀ a. a
を持たないことから、これらの宣言は静的エラーを含む。
(x
の型はf
の型次第であり、現状ではHaskellで依存型をもつ変数のシグネチャを指定する方法はない。このことはセクション4.5.4で説明される)
もし、与えられた問題が変数fのシグネチャを含むなら、その時各々のfの使用は宣言された型を持つように扱われる。 もし同じ型が定義しているfの実体へ推論されることもできなければ、静的エラーである。
もし変数f
が対応する型シグネチャ宣言を提供しないで定義されるなら、その時それ自身の宣言グループ(セクション4.5)の外側の各f
の使用は対応する推論される型、または 主要な 型を持つように扱われる。
しかしながら、型推論がまだ可能であることを保証するために、定義する実体とその宣言グループを伴う全てのf
の使用が同じ単相型を持たなければいけない。(セクション4.5.2で述べるように、主要な型は一般化によって得られる。)
例えば、もし以下のように定義したら、
sqr x = x⋆x
その時その主要な型はsqr :: ∀ a. Num a ⇒ a → a
であり、sqr 5
またはsqr 0.1
のような適用を許す。
次のようにより明確に型を宣言することも正常である。
sqr :: Int -> Int
しかし、この場合sqr 0.1
のような適用は不正である。
次のような型シグネチャは
sqr :: (Num a, Num b) => a -> b -- invalid
sqr :: a -> a -- invalid
それらはsqr
の主要な型より一般的であるが、不正である。
型シグネチャは 多相的再帰 をサポートすることにも使われることができる。 次の定義は異常であるが、型シグネチャを使って推論されるものよりも一般的な型をどのようにして指定すればよいか、ということのよい説明である。
data T a = K (T Int) (T a)
f :: T a -> a
f (K x y) = if f x == 1 then f y else undefined
もしシグネチャ宣言を取り除くなら、f
の型はf
への引数がT Int
である初めの再帰的な呼び出しの結果、T Int -> Int
のように推論される。
多相的再帰はユーザーがT a -> a
のように、より一般的な型シグネチャを供給することを許可する。
結合性宣言
gendecl | → | fixity [integer] ops | |
fixity | → | infixl | infixr | infix | |
ops | → | op1 , … , opn | (n ≥ 1) |
op | → | varop | conop |
結合性宣言は一つ以上の演算子の結合性と束縛の優先順位を与える。 結合性宣言の中のintegerは0から9の範囲でなければならない。 結合性宣言は型シグネチャが現れるところや、型シグネチャのように各自の演算子のプロパティを宣言する場所ならどこでも現れることができる。 また型シグネチャのように、結合性宣言は演算子の宣言と同じ宣言の列でのみ出現することができ、そして演算子に対しては高々1つの結合性宣言のみ与えてよい。 (クラスメソッドは小さな例外であり、それらの結合性宣言はクラス宣言の中またはトップレベルに出現できる。)
non-とleft-、right-結合(それぞれinfix
とinfixl
、infixr
)の3種類の結合性と、0から9を含んだ(レベル0は最も小さい結合を束縛し、レベル9は最も大きい結合を束縛する)10の優先順位のレベルがある。
もし 桁 が省略されるなら、レベル9と仮定される。
結合性宣言を欠いた演算子はinfixl 9
と仮定される(結合性の使い方のより詳細はセクション3を参照)。
テーブル4.1はPreduleに定義された演算子の結合性と優先順位をリストアップする。
優先順位 | 左結合演算子 | 非結合演算子 | 右結合演算子 |
---|---|---|---|
9 | !! | . | |
8 | ^ , ^^ ,⋆⋆ | ||
7 | ⋆ ,/ ,'div' ,'mod' ,'rem' , 'quot' | ||
6 | +,- | ||
5 | : , ++ | ||
4 | == , /= , < , <= , > , >= ,'elem' , 'notElem' | ||
3 | && | ||
2 | || | ||
1 | >> , >>= | ||
0 | $ , $! , 'seq' |
テーブル4.1 Prelude演算子の優先順位と結合性
結合性は型と同様に特定の実体(コンストラクタまたは変数)の性質であり、実体がもつ 名前 のプロパティではない。 例えば、
module Bar( op ) where
infixr 7 ‘op‘
op = ...
module Foo where
import qualified Bar
infix 3 ‘op‘
a ‘op‘ b = (a ‘Bar.op‘ b) + 1
f x = let
p ‘op‘ q = (p ‘Foo.op‘ q) ⋆ 2
in ...
ここでの、'Bar.op'
はinfixr 7
で、‘Foo.op‘
はinfix 3
であり、f
の右手側のop
のネストされた定義はinfixl 9
のデフォルトの結合性を持つ。
(ネストされた結合性宣言を伴うop
のネストされた定義に結合性を与えることもまた可能であろう。)
関数とパターン束縛
decl | → | (funlhs | pat) rhs | |
funlhs | → | var apat { apat } | |
| | pat varop pat | ||
| | ( funlhs ) apat { apat } | ||
rhs | → | = exp [where decls] | |
| | gdrhs [where decls] | ||
gdrhs | → | guards = exp [ gdrhs] | |
guards | → | | guard1, …, guardn | (n ≥ 1) |
guard | → | pat <- infixexp | (pattern guard) |
| | let decls | (local declaration) | |
| | infixexp | (boolean guard) |
この文法に置いて我々は次の2つのケースを区別する: パターンの束縛 がおきているとき(そのとき左手側はpat
である)と、それ以外の 関数束縛 と呼ばれる束縛がおきているときである。
どちらの束縛もモジュールのトップレベルでまたはwhere
かlet
の構成物の範囲で現れることができる。
関数束縛
関数束縛は関数の値に変数を束縛する。 変数xへ束縛する関数の一般的な形式は以下のものである。
x p11 … p1k match1
…
x pn1 … pnk matchn
上の各pijはパターンで、各matchiは次の一般的形式
= ei where { declsi }
または、
| gsi1 = ei1
…
| gsimi = eimi
where { declsi }
であり、n ≥ 1, 1 ≤ i ≤ n, mi ≥ 1である。 前者は後者の特別な場合であるので、簡略記法として扱われる。 すなわち、
| `True` = ei `where` { declsi }
注意点として関数を定義している全ての句は隣接しなければならず、各句の中のパターンの数は同じでなければならない。 各適合に対応するパターンのセットは線形でなければならず、変数がその実体のセットの中に一回以上現れることは許されていない。
関数の値を中置演算子に束縛するための代わりの構文が与えられている。 例えば、これらの3つの関数定義は全て等しい。
plus x y z = x+y+z
x ‘plus‘ y = \ z -> x+y+z
(x ‘plus‘ y) z = x+y+z
結合性の解決は、関数の束縛を中置にしたものにも式の場合と同様に適用することに注意せよ(セクション10.6)。 関数束縛に等しい左側に適用している結合性の解決はトップレベルで定義されているvaropを残さなければいけない。 例えば、もし優先度6で新しい演算子##を定義しているなら、その時この定義は不正である。
a ## b : xs = exp
なぜなら、:
は優先度5を持ち、従って左手側は(a ## x) : xs
に解決され、この式は(a ## x)
が正常なパターンでないためパターン束縛にはできない
変換: 関数への一般的な束縛の形式はこの等式に構文的に等しい(すなわち:シンプルなパターン束縛)。
x = \ x1 … xk -> case (x1, …, xk) of
(p11, …, p1k) match1
…
(pn1, …, pnk) matchn
xiは新しい識別子である。
パターン束縛
パターン束縛は値に変数を束縛する。
シンプルな パターン束縛はp = eの形式を持つ。
まるでその前に暗黙の~
があるかのように、パターンpは反駁できないパターンとして「遅延的に」適合される。
セクション3.12にその変換を確かめられる。
パターン束縛の 一般的な 形式は p適合 であり、適合 は上記の関数束縛において同じ構造である。 言い換えると、パターン束縛は以下のものである。
p | gs1 = e1
| gs2 = e2
…
| gsm = em
where { decls }
変換: 上記のパターン束縛はこのシンプルなパターン束縛と構文的に等しい。
p = let decls in
case () of
() | gs1 -> e1
| gs2 -> e2
…
| gsm -> em
_ -> error "Unmatched pattern"
関数とパターン束縛の静的な意味論
関数の静的意味論とlet
式またはwhere
句のパターン束縛はこのセクションで論じる。
依存の解析
一般的に静的意味論は通常のHindley-Milner推論規則を適用することによって与えられる。 多相性を増加するために、これらの規則は 依存の解析 によって見分けられる束縛のグループに適用される。
もし次のいずれかであるなら、束縛b1は同じ宣言のリストの中の束縛b2に依存する。
- b1は自由識別子で、型シグネチャを持たずb2によって束縛されるようなものを含む。または、
- b1はb2に依存する束縛に依存する。
宣言のグループ は相互依存の束縛の最小限のひと組である。
Hindley-Milner型推論は依存の順序に各宣言グループに適用される。
where/let
の構成物の中の宣言の順序は無意味である。
一般化
Hindley-Milner型システムは次の2段階でlet式に型を割り当てる。
- 宣言グループは依存している順に考慮される。 各グループにおいて、全称量化を伴わない型がそのグループに束縛される各変数へ推論される。 その時、これらの型に現れる全ての型変数は型環境に束縛された変数を連帯されるにも限らず全称量化される。 このことを一般化と呼ばれる。
- 最後に、let式の本体は型付けされる。
例えば、次の宣言を考えてほしい。
f x = let g y = (y,y)
in ...
g
の定義の型はa → (a,a)である。
一般化は、g
に対して多相型∀ a. a → (a,a)を紐づけ、そのあと"..."
の部分の型付けが続けられる。
オーバーロードされた定義に型を付ける時、単一の宣言グループにおけるすべてのオーバーロードしている制約は集められる。それはグループで宣言された各変数の型の文脈を作るためである。 例えば、以下の定義において
f x = let g1 x y = if x>y then show x else g2 y x
g2 p q = g1 q p
in ...
g1
とg2
の定義の型は両方a → a → String
であり、
累算された制約はOrd a
(>
の使用から発生する)と、Show a
(show
の使用から発生する)である。
この制約の収集に現れる型変数は制約された型変数と呼ばれる。
一般化の手順は型∀ a. (Ord a, Show a) ⇒ a → a → String
というg1
とg2
両方に帰属する。
>
とshow
がg1
の定義にあるのに、g2
はg1
と同じ方法にオーバーロードされることに注目してほしい。
プログラマがある宣言グループの2つ以上の変数に明示的な型シグネチャを与えたならば、それらのシグネチャの文脈は型変数の名前の取り換えを除いて一致しなければならない。
文脈の簡約エラー
セクション4.1.4で言及されたように、型の文脈は型変数または一つ以上の型変数の適用のみ制約できる。 よって、一般化によって生成された型は全ての文脈の制約が「頭部正規形」に簡約されなければいけない形式に表現されなければならない。 例として、次の定義を考えてもらいたい。
xs y = xs == [y]
その型は次のもので与えられ、
f :: Eq a => [a] -> a -> Bool
以下のものではない。
f :: Eq [a] => [a] -> a -> Bool
等号はリストに対して使われているが、文脈は簡単な形にしなければならず、一般化の前にリストに対するEq
のインスタンス宣言を用いて行う。
もしそのようなインスタンスがスコープ内に無いなら、静的エラーが起きる。
ここにはmが一般化された型変数のひとつである形式C (m t)の制約のための要求を示す例がある。 すなわち、クラスCを型変数または型コンストラクタではない型式に適用する場合である。 次のものを考えてもらいたい。
f :: (Monad m, Eq (m a)) => a -> m a -> Bool
f x y = return x == y
return
の型はMonad m => a -> m a
であり、(==)
の型はEq a => a -> a -> Bool
である。
f
の型は従って(Monad m, Eq (m a)) => a -> m a -> Bool
であるべきで、その文脈はそれ以上簡易化されることはできない。
データ型deriving
句(セクション4.3.3)から派生したインスタンス宣言は、あらゆるインスタンス宣言のように、簡単な文脈を持たなければならない。
すなわち、全ての制約はaが型変数である形式C aでなければならない。
例えば、以下の型において
data Apply a b = App (a b) deriving Show
導出されたShowインスタンスは文脈Show (a b)
を生成し、そしてそれは簡約されることはできず、かつシンプルではないゆえに静的エラーが結果として生じる。
単相性
時々、その定義の型の中に使用されるすべての型変数にわたる一般化ができないことがある。 例えば、次の宣言を考える。
f x = let g y z = ([x,y], z)
in ...
x
が型aを持ち、g
の定義の型はa → b → ([a],b)という環境である。
一般化の手順は型∀ b. a → b → ([a],b)
というg
に帰属する。
aが型環境に現れているため、bのみが全称量化されることが可能である。
このことをg
の型が型変数aに単相的であると言う。
そのような単相性の結果としてg
の全ての適用の初めの引数が単一の型でなければならない。
例えば、次の式は”...”
に有効であろう。
(g True, g False)
(ついでに言うとx
をBool
型を持つよう強制させる。) しかし次のものは不正である。
(g True, g 'c')
一般的にもしa
が∀ u. cx ⇒ t.
の中で自由なら、型∀ u. cx ⇒ t
は型変数aに単相的であると言われる。
Haskellによって与えられる明示的な型シグネチャは単相的な型変数を含む型を表現するのに十分な力がないことには注意すべきである。 例えば、次のものは書くことができない。
f x = let
g :: a -> b -> ([a],b)
g y z = ([x,y], z)
in ...
なぜなら、あれはg
がa
とb
の両方に多相的であることを要求されるからだ(セクション4.4.1)。
このプログラムでは、g
は最初の引数が型変数を含まないものに制限された場合にのみ型シグネチャを与えられる。
例えば、
g :: Int -> b -> ([Int],b)
このシグネチャはInt
型を持つx
ももたらすだろう。
単相性の制限
Haskellは上記の標準Hindley-Milner制限の範囲を超えて、一般化の手順に一定の余分な制限を配置し、特定の場合において多相性をさらに削減する。
単相性の制限は変数の束縛する構文に依存する。 変数は 関数束縛 または パターン束縛 のどちらかによって束縛されることを思い出してほしい。 しかも、 単純な パターン束縛はそのパターンが単一の変数のみで構成するパターン束縛である(セクション4.4.3)。
次の2つのルールは単相性の制限を定義する。
単相性の制限
-
ルール1
次の条件を満たすとき、与えられた宣言グループが 非制限的 であるという。- (a):
グループ内の各変数は関数束縛または単純なパターン束縛によって束縛される(セクション4.4.3.2)。かつ、 - (b):
単純なパターン束縛によって束縛されるようなグループ内のすべての変数に対して明示的な型シグネチャが与えられている。
Hindley-Milnerの多相性に関する通常の制限とは、環境に自由に出現しない型変数のみが一般化されるというものである。 それに加えて、該当するグループの一般化のステップにおいて、制限的な宣言グループの制約された型変数は一般化されない場合がある。 (もし同じ型クラスに属さないといけないなら、型変数が制約されていることを思い出すこと。セクション4.5.2を参照。)
- (a):
-
ルール2
全部のモジュールへの型推論が完了した時に残るあらゆる単相的な型変数は曖昧だと考えられ、そしてデフォルトのルールを使用した各々の型に解決される(セクション4.3.4)。
動機 ルール1は2つの理由により要求され、両方ともかなり巧妙である。
-
ルール1は思いがけなく繰り返されることから計算を防止する。 例えば、
genericLength
は型が次のもので与えられる(Data.List
ライブラリの)標準関数である。genericLength :: Num a => [b] -> a
では、次の式を考えて欲しい。
let { len = genericLength xs } in (len, len)
まるで
len
が一度だけ計算されるべきかのように見れるが、ルール1がなければ2回の異なるオーバーロード毎に一回づつで、計二回計算されるであろう。 もしプログラマが実際に計算が繰り返されてほしいと考えているなら、明示的な型シグネチャを追加するだろう。let { len :: Num a => a; len = genericLength xs } in (len, len)
-
ルール1は曖昧さを防止する。例えば、次の宣言グループを考えてほしい。
[(n,s)] = reads t
reads
は型がシグネチャによって与えられる標準関数であることを思い出してほしい。reads :: (Read a) => String -> [(a,String)]
ルール1なしで、
n
は型∀ a. Read a ⇒ a
を割り当てられるであろう。そして、s
は型∀ a. Read a ⇒ String
である。 後者は不正な型である、なぜなら本質的に曖昧であるからだ。s
を使うオーバーロードするもので決定することは可能ではなく、そしてs
に型シグネチャを追加することで解決されることもできない。 このゆえに、単純ではないパターン束縛が使われている時(セクション4.4.3.2)、型シグネチャが与えられるかどうかにかかわりなく、推論された型はそれらの制約された型変数の中で常に単相的である。 この場合において、n
とs
の両者はaに単相的である。同じ制約はパターン束縛関数に適用する。 例えば、次の中の
(f,g) = ((+),(-))
f
とg
の両者はf
またはg
に与えられる型シグネチャにかかわらず単相的である。
ルール2は現在のモジュールの外側のモジュール上で型推論を行うことを除いて、エクスポートされた束縛の単相的な使用を強いるための方法がないことから要求される。 ルール2はモジュール内に束縛された全変数の正確な型がそのモジュールのみで決定されなければならないことを述べ、それをインポートするモジュールによってではないことを述べている。
module M1(len1) where
default( Int, Double )
len1 = genericLength "Hello"
module M2 where
import M1(len1)
len2 = (2⋆len1) :: Rational
モジュールM1
上の型推論が完了した時、len1
は(ルール1によって)単相的な型Num a => a
を持つ。
ルール2はそのとき単相的な型変数a
が曖昧であると述べ、セクション4.3.4の既定のルールを使い解決されなければならない。
それゆえに、len1
が型Int
を取得し、len2
の中の使用は誤った型である。
(もし上記のコードが実際に求められるなら、len1
上の型シグネチャはその問題を解決するであろう。)
この問題はネストされた束縛では起きない。なぜなら、それらの全体のスコープがコンパイラに見えているからだ。
結果 単相性ルールはプログラマに多くの結果をもたらす。 関数構文を用いて定義されたどんなものも、通常期待されるような関数に一般化される。 例として、
f x y = x+y
関数f
はクラスNum
にオーバーロードするもので使われる。
ここに再計算の危険はない。
しかしながら、次のパターン構文を伴い定義される同じ関数は、
f = \x -> \y -> x+y
もしf
が完全にオーバーロードされるなら型シグネチャを要求する。
多くの関数は単純なパターン束縛を使用してたいてい自然に定義される。
ユーザーは、完全なオーバーローディングを保つためには型シグネチャを添えることを心掛けなければならない。
標準Preludeはこのことのたくさんの例を含む。
sum :: (Num a) => [a] -> a
sum = foldl (+) 0
ルール1はトップレベルとネストされた定義の両方に適用する。 次のものを考えてほしい。
module M where
len1 = genericLength "Hello"
len2 = (2⋆len1) :: Rational
ここで、型推論はlen1
が単相的な型(Num a => a)
を持つことを探し、その型変数a
はlen2
上の型推論を行う時にRational
に解決される。
カインドの推論
このセクションは カインドの推論 、すなわち与えられたプログラムに現れる各型コンストラクタとクラスに適切なカインドを計算することに使われるルールを述べる。
カインドの推論の工程の最初の手順は依存グループデータ型やシノニム、クラス定義のセットを整理することである。
これはセクション4.5で述べられた値宣言の依存解析のように同じ方法を多く成し遂げられることができる。
例えば、次のプログラムの断片はデータ型コンストラクタD
とシノニムS
、クラスC
の定義を含み、
その全ては同じ依存グループに含まれているであろう。
data C a => D a = Foo (S a)
type S a = [D a]
class C a where
bar :: a -> D a -> Bool
各グループ内の変数やコンストラクタ、クラスのカインドは型推論とカインドを保存するユニフィケーションの標準的なテクニックを用いて決定される[8]。
例えば上で定義した中で、パラメータa
はbar
の型の関数コンストラクタ(->)
の引数のように現れ、これゆえにカインド∗
を持たなければならない。
それはD
とS
の両方がカインド∗→∗
を、かつクラスC
の全インスタンスがカインド∗
を持たなければならないということを結果として生ずる。
推論されたカインドのいくつかの部分は対応する定義によっておそらく完全に決定されないであろうことも起こりうる。
そのような場合、∗
の既定値は推測される。
例えば、次の例の各々のa
パラメータに任意のカインドκを推測できるだろう。
data App f a = A (f a)
data Tree a = Leaf | Fork (Tree a) (Tree a)
これは任意のカインドκ
に対して、それぞれApp
とTree
にカインド(κ →∗) → κ →∗
とκ →∗
を与えるであろう。
そして、多相的なカインドを許すように拡張を要求するであろう。
代わりに、デフォルト束縛κ = ∗
を用いれば、これら2つのコンストラクタへ与えられる実際のカインドはおのおの、(∗→∗) →∗→∗
と∗→∗
になる。
既定値は特別な型コンストラクタ定数またはクラスが後ろの依存グループまたはプログラムの他の場所で使われる方法の検討なしで各依存グループに与えられる。
例として、上のソースコードに次に続く定義を追加することは(例えば(∗→∗) →∗
に変化することによって)Tree
へ推論されたカインドに影響を及ぼさない。
そして代わりに静的エラーを生成する。
なぜなら[], ∗→∗,
のカインドはTree
の引数へ期待されるカインド∗
と適合しないからだ。
type FunnyTree = Tree [] -- invalid
これは各コンストラクタとクラスがスコープ内にある時はいつでも同じカインドで一貫して使われていることを保証するため重要である。