宣言と束縛

この章では、Haskellの 宣言 の構文と簡略した意味論を説明する。

modulemodule modid [exports] where body
|body
body{ impdecls ; topdecls }
|{ impdecls }
|{ topdecls }
topdeclstopdecl1 ; … ; topdecln(n ≥ 1)
topdecltype 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)
declgendecl
|(funlhs | pat) rhs
cdecls{ cdecl1 ; … ; cdecln }(n ≥ 0)
cdeclgendecl
|(funlhs | var) rhs
idecls{ idecl1 ; … ; idecln }(n ≥ 0)
idecl(funlhs | var) rhs
| (empty)
gendeclvars :: [context =>] type(type signature)
|fixity [integer] ops(fixity declaration)
| (empty declaration)
opsop1 , … , opn(n ≥ 1)
varsvar1 , … , varn(n ≥ 1)
fixityinfixl | infixr | infix

構文的カテゴリtopdeclsに属する宣言はHaskellモジュール(5章)の最上位のみ許す一方で declsは最上位またはネストされたスコープのいずれかで使ってもよい(例えば、letwhereの内でtopdeclsを構築する)。

説明のため、typenewtypedata宣言からなるユーザー定義のデータ型(セクション4.2)とclassinstancedefault宣言からなる型クラスとオーバーロード(セクション4.3)、値束縛と型シグネチャ、固定の宣言からなるネストされた宣言(セクション4.4)の3つのグループに宣言を分割する。

haskellは(整数や浮動小数点数のような)"ハードウェアで実現された"であるいくつかのプリミティブなデータ型を持つ。しかし、多くの"ビルドイン"なデータ型は通常のHaskellコードによって定義されていて、通常typedata宣言に使われる。これらの"ビルドイン"のデータ型はセクション6.1で詳細に説明される。

型とクラスの概要

Haskellは静的型意味論4,6を提供するために伝統的なHindley-Milner多相型システムを使用するが、その型システムは構造化された手法にオーバーロード関数を導入するために提供する 型クラス (または クラス )で拡張されている。

class宣言(セクション4.3.1)は新しい 型クラス とあらゆるそのクラスのインスタンスの型によってもサポートされなければいけないオーバーロードされた操作を導入する。instance宣言(セクション4.3.2)は型がクラスの インスタンス であり、名付けられた型でインスタンス化されるオーバーロードされたオペレーション、クラスメソッドと呼ばれる、の定義を含むということを宣言する。

例えば、型IntFloatで操作(+)negateをオーバーロードしたいと考えたとしよう。Numと呼ばれる新しい型クラスを導入する。

class Num a  where          -- Numの単純化されたクラス宣言
  (+)    :: a -> a -> a     -- (NumはPreludeで定義されている)  
  negate :: a -> a

この宣言は「型aがもし与えられた型で定義されたクラスメソッド(+)negateがあるならクラスNumのインスタンスである」と読めるであろう。

このクラスのインスタンス化のときにIntFloatをその際に宣言できる。

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

addIntnegateIntaddFloatnegateFloatはこのケースでプリミティブ関数で想定されるが、一般的にはユーザー定義関数になり得る。 上のはじめの宣言は「IntはクラスNumのインスタンスであり、その証拠として(クラスメソッド)(+)negateが定義されている」と読まれる。

型クラスのより多くの例はJones8かWadlerとBlott13による論文で見つけられる。用語'型クラス'はオリジナルのHaskell1.0型システムを記述するために使われてあって、'コンストラクタクラス'はオリジナルの型クラスへ拡張を記述することに使われていた。ふたつの異なる用語を使う理由はもはやなく、この規格書において、'型クラス'という単語は元々のHaskell型クラスとJonesによって導入されたコンストラクタクラスの両方を含んでいる。

カインド

型の表現が有効である確証を得るために、型の表現を異なるカインド(カインド, kind)へと分類され、以下の2つの可能な形式の内、1つを取る。

  • シンボル*は全ての引数のない型コンストラクタのカインドを意味する。
  • もしK1K2がカインドならば、K1 → K2はカインドK1の型を取り、K2の型を返す型のカインドである。

型推論が値の表現の正当性をチェックするのと同様にして、カインド推論は型の表現の正当性をチェックする。しかしながら、型とは違い、カインドは完全に暗黙的であり、言語の見て分かる部分には存在しない。カインドの推論はセクション4.6で議論される。

型の構文

typebtype [-> type](function type)
btype[btype] atype(type application)
atypegtycon
|tyvar
|( type1 , … , typek )(tuple type, k ≥ 2)
|[ type ](list type)
|( type )(parenthesised constructor)
gtyconqtycon
|()(unit type)
|[](list constructor)
|(->)(function constructor)
|(,{,})(tupling constructors)

Haskellの型の表現のための構文は上に与えられる。データ値と同じようにデータコンストラクタを使って作られ、型の値(訳注: 型の表現でそれ以上簡約出来ないもののこと)は 型コンストラクタ から作られる。データコンストラクタと同様に、型コンストラクタの名前は大文字で始められる。データコンストラクタとは違い、中置型コンストラクタは許されない((->)以外)。

型の表現の主要な形式は次のものになる。

  1. 小文字で始まる識別子のように書かれた型変数。変数のカインドは現れた文脈によって暗黙的に決定される。

  2. 型コンストラクタ。多くの型コンストラクタは大文字から始まる識別子のように書かれている。 例えば、

    • CharInt,Float,DoubleBoolはカインド*で構築される型である。
    • MaybeIOは単項型コンストラクタで、カインド*→*をもつ型として扱われる。
    • 宣言data T ...またはnewtype T ...は型のボキャブラリーに型コンストラクタTを追加する。Tのカインドはカインドの推論によって決定される。

    特殊な構文は特定のビルドインの型コンストラクタに提供される。

    • 自明型()のように書かれ、カインド*を持つ。それは"引数のないタプル"型を示し、()と書かれるが、値をちゃんと持つ(セクション3.96.1.5を参照)。
    • 関数型(->)のように書かれ、カインド∗→∗→∗を持つ。
    • リスト型[]のように書かれ、カインド∗→∗を持つ。
    • タプル型(,), (,,)等のように書かれる。それらのカインドは∗→∗→∗, ∗→∗→∗→ ∗などである。

    (->)[]の定数の使用は下でより詳しく説明される。

  3. 型適用。もし、t1がカインドK1 → K2の型でt2がカインドK1の型であるなら、その時t1, t2はカインドK2の型の表現である。

  4. 括弧つき型 、形式(t)を持つ、型tと同一である。

例えば、型の表現IO aは変数aに定数IOへの適用のように理解されることが可能だ。IO型コンストラクタはカインド∗→∗を持ち、変数aと式全体の両方を従え、式IO aはカインド*を持たなければならない。一般的に 型の推論 (セクション4.6)の処理は適切なカインドをユーザー定義のデータ型や型のシノニム、クラスへ決定することを必要とされる。

特別な構文は特定の型の表現がより伝統的なスタイルで書かれることを許すために提供される。

  1. 関数型 は形式t1 -> t2を持ち、型(->) t1 t2に等しい。アロー関数は左に関連づける。例えば、Int -> Int -> FloatInt -> (Int -> Float)を意味する。
  2. タプル型K ≥ 2である形式t1, ..., tkを持ち、括弧の間にk-1個のカンマがある型(,…,) t1 … tkと等しい。それは型t1をはじめの要素に、型t2を2番目の要素に持つなど、k要素のタプルの型を示す(セクション3.86.1.4を参照)。
  3. リスト型 は形式[t]を持ち、型[] tと等しい。それは型tの要素を伴うリストの型を示す(セクション3.76.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)を意味する。

クラス表明と文脈の構文

contextclass
| ( class1 , … , classn )(n ≥ 0)
class qtycls tyvar
| qtycls ( tyvar atype1atypen )(n ≥ 1)
qtycls[ modid . ] tycls
tyclsconid
tyvarvarid

クラス表明 は形式qtycls tyvarを持ち、クラスqtyclsの型tyvarのメンバを示す。クラス識別子は大文字で始める。 文脈 は0個以上のクラス表明からなり、一般に( C1 u1, …, Cn un )の形式をもつ。ここでC1, …, Cnはクラス識別子であり、( u1, …, un)はそれぞれ変数型または一つ以上の型への変数型の適用のいずれかである。n = 1のとき括弧の外側は省かれるであろう。一般的に、文脈を示すためにcxを使用し、cx => tを文脈cxによって制限された型tを示すために書く。文脈cxtによって参照される変数型のみを含まなければいけない。利便性のために、文脈cxが空であっても、具体的な構文は=>を含まないケースであるが、cx => tを書く。

型とクラスの意味論

このセクションは、型システムの簡略的な詳細を提供する。(WadlerとBlott[13]、Jones[8]は各々より詳細に型とコンストラクタクラスを議論している。)

Haskellの型システムは をプログラム内の各式に割り当てる。一般的に、型は形式u. cx ⇒ tである。uは変数型の集合u1, ..., unである。どのような型であっても、cxに束縛がない一般的な個々に区別された変数型uitでも束縛がないものでなければならない。その上、内容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.164.4.1を参照)。

u. cx1 ⇒ t1は領域が以下のようなuの代用Sがある場合に限り、型w. cx2 ⇒ t2 より一般的 である。

  • t2S(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宣言)を説明する。これらの宣言はモジュールのトップレベルでのみ現れてよい。

代数データ型宣言

topdecldata [context =>] simpletype [= constrs] [deriving]
simpletypetycon tyvar1tyvark(k ≥ 0)
constrsconstr1 | … | constrn(n ≥ 1)
constrcon [!] atype1 … [!] atypek(arity con = k, k ≥ 0)
|(btype | ! atype) conop (btype | ! atype)(infix conop)
|con { fielddecl1 , … , fielddecln }(n ≥ 0)
fielddeclvars :: (type | ! atype)
derivingderiving (dclass | (dclass1, … , dclassn))(n ≥ 0)
dclassqtycls

constrの優先順位は式と同じである。通常のコンストラクタの適用が中置コンストラクタの適用より高い優先順位を持つ(そのためa : Foo aa : (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は互いに異なるものでなければならず、cxtijに出現してもよい。 また、他の型変数がcxやそれより右側に出現すると静的なエラーとなる。 新しい型定数Tは引数の変数uiのカインドκiがセクション4.6で説明されるカインド推論によって決定される形式κ1 →… → κk →∗のカインドを持つ。 これはTが0からk引数のどこでも型の表現に使われるかもしれないということを意味する。

例えば、以下の宣言は

data Eq a => Set a = NilSet | ConsSet a (Set a)

カインド∗→∗の型コンストラクタSetを導入し、型ありのコンストラクタNilSetConsSetは以下のものである。

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が形式!titiのいずれかである形式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上のパターンマッチングは正格なフラグによる影響を受けられない。

型シノニムの宣言

topdecltype simpletype = type
simpletypetycon tyvar1tyvark(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を参照)。

データ型の改名

topdeclnewtype [context =>] simpletype = newconstr [deriving]
newconstrcon atype
|con { var :: type }
simpletypetycon tyvar1tyvark(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

型クラスとオーバーロード

クラス宣言

topdeclclass [scontext =>] tycls tyvar [where cdecls]
scontextsimpleclass
|( simpleclass1 ,, simpleclassn )(n ≥ 0)
simpleclassqtycls tyvar
cdecls{ cdecl1 ;; cdecln }(n ≥ 0)
cdeclgendecl
|(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である。 tiuを言及しなければいけないし、uより型変数wを言及するかもしれない。 その場合、viの型はuwの両方に多相的である。 cxiwのみ束縛するだろう。 特に、cxiuを束縛しなくともよい。 例えば、
    class Foo a where
    op :: Num b => a -> b -> a
    

    ここでのop型はa, b.(Foo a, Num b) ⇒ aba.である。
  • cdeclsは(他の値ではなく)そのクラスのメソッドに対する 結合性宣言 を含んでもよい。 しかしながら、クラスメソッドはトップレベルの値を宣言することから、他の選択肢としてクラスメソッドの結合性宣言はクラス宣言の外側であるトップレベルに現れてもよい。
  • 最後に、cdeclsviのいずれかの デフォルトクラスメソッド を含められる(セクション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部を伴わず明示的に与えられなければならない。

インスタンス宣言

topdeclinstance [scontext =>] qtycls inst [where idecls]
instgtycon
|( gtycon tyvar1tyvark )(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 ...

宣言dCのクラスメソッドのみの束縛を含められる。スコープにないクラスメソッドへの束縛を与えることは不正であるが、スコープ内にあるものの名前は重要ではない。特に、それは修飾子付きの名前でもよいであろう。(このルールはセクション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つの状態も満たされなければならない。

    1. Cのスーパークラスの内容cx[(T u1 … uk)∕u] によって表現された束縛が満たされなければならない。言い換えると、TCのスーパークラスの各インスタンスでなければならず、全スーパークラスのインスタンスの内容はcx'によって暗示されなければいけない。
    2. 正しく型付けされたd内のクラスメソッド宣言に要求されるインスタンス型内の型変数上の束縛も満たされなければいけない。

    実際に異常なケースを除いては、インスタンス宣言から上記2つの制約を満たす最も一般的なインスタンス文脈cx'を推論することが可能である。だが、それでも明示的なインスタンスの内容を書くことは強制される。

次の例はスーパークラスインスタンスによって強いられた制限事項を説明する。

class Foo a => Bar a where ...  

instance (Eq a, Show a) => Foo [a] where ...  

instance Num a => Bar [a] where ...

この例はHaskellにおいて正常である。FooBarのスーパークラスであるため、2つ目のインスタンス宣言は[a]が仮定Num aの下でFooのインスタンスである時に正常である。1つめのインスタンス宣言はこの仮定の下で[a]Fooのインスタンスであると実際に告げる。なぜなら、EqShowNumのスーパークラスだからだ。

もし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で言及したように、datanewtypeの宣言は任意のderivingの形式を含んでいる。 もしその形式が含まれていたら、そのデータ型に対して 導出されたインスタンス宣言 が指定されたクラスのそれぞれについて自動的に生成される。 これらのインスタンスはユーザー定義されたインスタンスと同じ制限事項に従うべきである。 型TへクラスCを導出している時、Cの全スーパークラスのインスタンスはTのために存在しなければならず、明示的なinstance宣言を経由またはderiving句にスーパークラスを含むことを経由するかのいずれかである。

導出されたインスタンスはユーザー定義のデータ型へ便利なよく使われるオペレーションを提供する。 例えば、クラスEqの中のデータ型に導出されたインスタンスが==/=オペレーションを定義すると、それらを定義する必要からプログラマーを解放する。

導出されたインスタンスが許されるPreludeのクラスはEqOrdEnumBoundedShowReadのみであり、図6.1で全て図示される。 これらの句それぞれに生成される導出されたインスタンスの様相の精密な詳細は11章に提供されており、そこにはそのような導出されたインスタンスが可能である時の仕様書を含んでいる。 標準ライブラリによって定義された句も導出可能であろう。

もしderivingの形式で名付けられたクラス上でinstance宣言を導出することが可能でないなら、静的エラーが結果となる。 例えば、すべてのデータ型が正しくEnumのクラスメソッドをサポートできるわけではない。 それはまた導出されたクラスに明示的なinstance宣言を与えるため静的エラーになる。

もしderiving形式がdataまたはnewtype宣言から省略されたなら、そのときそのデータ型への いかなる インスタンス宣言も導出されない。 すなわち、deriving形式を省くことは空の導出の形式: deriving()を含んでいることと同等である。

曖昧な型とオーバーロードされた数値演算子の既定値

topdecldefault (type1 ,, typen)(n ≥ 0)

Haskellスタイルのオーバーロード固有の問題は 曖昧な型 の可能性があるということである。 例えば、11章で定義されたreadshow関数を使い、もし単なるIntBoolReadShowのメンバーなら、その時の次の式

let x = read "..." in show x  -- invalid

は曖昧である。なぜならshowreadの型は、下の2つの式の

show :: ∀ a. Show  a  ⇒  a  →  String
read :: ∀ a. Read  a  ⇒  String  →  a

両方のケースでaIntまたはBoolのどちらでもインスタンス化で満たすことが可能だからだ。 そのような式は不適切な型付けだと考えられ、静的エラーである。

u. cx ⇒ t内で、もしtではなくexの中に存在するuに型変数uがあるなら、式e曖昧な型 を持つと言う。 そのような型は不正である。

例えば、先程のshowreadを伴う式はその型がa. Show a, Read a ⇒ Stringであるから曖昧な型を持つ。

曖昧な型はユーザーからの入力によってのみ回避できる。 その方法のひとつはセクション3.16で説明された 式の型シグネチャ の使用を通じてである。 例として、先程与えられた曖昧な式において、以下のように書くことで、

let x = read "..." in show (x::Bool)

型から曖昧さを取り除く。

型シグネチャにより固定された型を与える以外にも、曖昧な式はある変数と同じ型にしなければならない場合がしばしばある。 これは関数asTypeOf:x asTypeOf y(9章)の用途がxの値を持つということであるが、xyは同じ型を持つように強制される。 例えば、

approxSqrt x = encodeFloat 1 (exponent x ‘div‘ 2) ‘asTypeOf‘ x

(encodeFloatexponentの説明についてはセクション6.4.6を参照)

Numクラスの曖昧さはとてもありふれているので、Haskellはこれを解決する別の方法を提供している。 それはデフォルト宣言である。 n ≥ 0で、各tiNum tiが保持するdefault (t1 , … , tn)。 曖昧な型が発見された状態で、もし以下の条件を満たすなら、曖昧な型変数vはデフォルト可能である。

  • vC vの形の制約の中でのみ出現し(ただしCはクラスである)、かつ
  • 少なくともこれらのクラスの一つが数値クラス(Num、またはNumのサブクラス)であり、
  • これらのクラスの全てがPreludeまたは標準ライブラリの中で定義されている(図6.2-6.3は数値クラスを示し、図6.1はPrelude内で定義されたクラスを示す)。

各デフォルト可能な変数は全ての曖昧な変数のクラスのインスタンスであるデフォルトリストの初めの型によって置き換えられる。 そのような型が見つからなければ、静的エラーである。

ひとつのデフォルト宣言のみがモジュールごとに許可され、その効果はそのモジュールに制限される。 もしデフォルト宣言がモジュール内で与えられないなら、その時は次のようなものであると仮定する。

default (Integer, Double)

空のデフォルト宣言default ()はモジュール内の全てのデフォルトをオフにする。

ネストされた宣言

次の宣言はモジュールのトップレベルを含むどんな宣言リストでも使用される。

型シグネチャ

gendeclvars :: [context =>] type
varsvar1 ,, 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のように、より一般的な型シグネチャを供給することを許可する。

結合性宣言

gendeclfixity [integer] ops
fixityinfixl | infixr | infix
ops op1 , … , opn (n ≥ 1)
op varop | conop

結合性宣言は一つ以上の演算子の結合性と束縛の優先順位を与える。 結合性宣言の中のintegerは0から9の範囲でなければならない。 結合性宣言は型シグネチャが現れるところや、型シグネチャのように各自の演算子のプロパティを宣言する場所ならどこでも現れることができる。 また型シグネチャのように、結合性宣言は演算子の宣言と同じ宣言の列でのみ出現することができ、そして演算子に対しては高々1つの結合性宣言のみ与えてよい。 (クラスメソッドは小さな例外であり、それらの結合性宣言はクラス宣言の中またはトップレベルに出現できる。)

non-とleft-、right-結合(それぞれinfixinfixlinfixr)の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
funlhsvar apat { apat }
|pat varop pat
| ( funlhs ) apat { apat }
rhs= exp [where decls]
| gdrhs [where decls]
gdrhsguards = exp [gdrhs]
guards | guard1, …, guardn(n ≥ 1)
guardpat <- infixexp (pattern guard)
|let decls (local declaration)
|infixexp (boolean guard)

この文法に置いて我々は次の2つのケースを区別する: パターンの束縛 がおきているとき(そのとき左手側はpatである)と、それ以外の 関数束縛 と呼ばれる束縛がおきているときである。 どちらの束縛もモジュールのトップレベルでまたはwhereletの構成物の範囲で現れることができる。

関数束縛

関数束縛は関数の値に変数を束縛する。 変数xへ束縛する関数の一般的な形式は以下のものである。

x	p11 … p1k	match1x	pn1 … pnk	matchn

上の各pijはパターンで、各matchiは次の一般的形式

= ei where { declsi }

または、

| gsi1  =  ei1 
…
 | gsimi  =  eimi
            where { declsi }

であり、n ≥ 1, 1 ≤ in, 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 = \ x1xk -> 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に依存する。

  1. b1は自由識別子で、型シグネチャを持たずb2によって束縛されるようなものを含む。または、
  2. b1b2に依存する束縛に依存する。

宣言のグループ は相互依存の束縛の最小限のひと組である。 Hindley-Milner型推論は依存の順序に各宣言グループに適用される。 where/letの構成物の中の宣言の順序は無意味である。

一般化

Hindley-Milner型システムは次の2段階でlet式に型を割り当てる。

  1. 宣言グループは依存している順に考慮される。 各グループにおいて、全称量化を伴わない型がそのグループに束縛される各変数へ推論される。 その時、これらの型に現れる全ての型変数は型環境に束縛された変数を連帯されるにも限らず全称量化される。 このことを一般化と呼ばれる。
  2. 最後に、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 ...

g1g2の定義の型は両方aa → Stringであり、 累算された制約はOrd a(>の使用から発生する)と、Show a(showの使用から発生する)である。 この制約の収集に現れる型変数は制約された型変数と呼ばれる。

一般化の手順は型a. (Ord a, Show a) ⇒ aa → Stringというg1g2両方に帰属する。 >showg1の定義にあるのに、g2g1と同じ方法にオーバーロードされることに注目してほしい。

プログラマがある宣言グループの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. ab([a],b)というgに帰属する。 aが型環境に現れているため、bのみが全称量化されることが可能である。 このことをgの型が型変数aに単相的であると言う。

そのような単相性の結果としてgの全ての適用の初めの引数が単一の型でなければならない。 例えば、次の式は”...”に有効であろう。

(g True, g False)

(ついでに言うとxBool型を持つよう強制させる。) しかし次のものは不正である。

(g True, g 'c')

一般的にもしau. cx ⇒ t.の中で自由なら、型u. cx ⇒ tは型変数a単相的であると言われる。

Haskellによって与えられる明示的な型シグネチャは単相的な型変数を含む型を表現するのに十分な力がないことには注意すべきである。 例えば、次のものは書くことができない。

f x = let  
        g :: a -> b -> ([a],b)  
        g y z = ([x,y], z)  
      in ...

なぜなら、あれはgabの両方に多相的であることを要求されるからだ(セクション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を参照。)

  • ルール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)、型シグネチャが与えられるかどうかにかかわりなく、推論された型はそれらの制約された型変数の中で常に単相的である。 この場合において、nsの両者はaに単相的である。

    同じ制約はパターン束縛関数に適用する。 例えば、次の中の

    (f,g) = ((+),(-))
    

    fgの両者は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)を持つことを探し、その型変数alen2上の型推論を行う時に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]。 例えば上で定義した中で、パラメータabarの型の関数コンストラクタ(->)の引数のように現れ、これゆえにカインドを持たなければならない。 それはDSの両方がカインド∗→∗を、かつクラスCの全インスタンスがカインドを持たなければならないということを結果として生ずる。

推論されたカインドのいくつかの部分は対応する定義によっておそらく完全に決定されないであろうことも起こりうる。 そのような場合、の既定値は推測される。 例えば、次の例の各々のaパラメータに任意のカインドκを推測できるだろう。

data App f a = A (f a)  
data Tree a  = Leaf | Fork (Tree a) (Tree a)

これは任意のカインドκに対して、それぞれAppTreeにカインド(κ →∗) → κ →∗κ →∗を与えるであろう。 そして、多相的なカインドを許すように拡張を要求するであろう。 代わりに、デフォルト束縛κ = ∗を用いれば、これら2つのコンストラクタへ与えられる実際のカインドはおのおの、(∗→∗) →∗→∗∗→∗になる。

既定値は特別な型コンストラクタ定数またはクラスが後ろの依存グループまたはプログラムの他の場所で使われる方法の検討なしで各依存グループに与えられる。 例として、上のソースコードに次に続く定義を追加することは(例えば(∗→∗) →∗に変化することによって)Treeへ推論されたカインドに影響を及ぼさない。 そして代わりに静的エラーを生成する。 なぜなら[], ∗→∗,のカインドはTreeの引数へ期待されるカインドと適合しないからだ。

type FunnyTree = Tree []     -- invalid

これは各コンストラクタとクラスがスコープ内にある時はいつでも同じカインドで一貫して使われていることを保証するため重要である。