型シグネチャの拡張
全称量化の明示 (forall)
ExplicitForAll
6.12.1以降
暗黙のうちに全称量化されている場所でのforallキーワードの使用を許可する.
Haskellの型シグネチャは暗黙のうちに量化されている.ExplicitForAllが有効な場合,forallキーワードにより,これが何を意味するのかを正確に示すことができる.例えば,
code: te1.hs
g :: b -> b
これは以下を意味する.
code: te2.hs
g :: forall b. (b -> b)
後者は型変数にスコープの概念(訳注: 直訳に合わないのでテキトーに訳した)を持ち込むことができる点を除いて,2つは同じように扱われる(字句的スコープを持つ型変数を参照せよ). 注意
ExplicitForAllが有効な場合,forallはキーワードになり,forallを型変数として使うことはできない.
型シグネチャの場合と同様に ,インスタンス宣言で明示的なforallを使うこともできる.
code:te3.hs
instance forall a. Eq a => Eq a where ... -Wunused-forallsフラグが有効になっている場合,明示的に型変数を記述する際に,未使用のforall宣言があれば,警告が出る.例えば,
code: te4.hs
g :: forall a b. (b -> b)
と書くと未使用の型変数aについて警告が出る.
型シグネチャのコンテキスト
FlexibleContextsの拡張は,型シグネチャにおける型クラス制約は,(class type-variable)か(class (type-variable type1 type2 .. typen))のいずれかの形式でなければならないというHaskell 98の制限を取り除く.FlexibleContextsを使えば,以下の型シグネチャはまったく問題ない.
code: te5.hs
g :: Ord (T a ()) => ...
あいまいな型とあいまいさ検査
AllowAmbiguousTypes
7.8.1以降
使用できない束縛になると思われる型シグネチャを許可する.
ユーザが書いたおのおのの型シグネチャは,あいまいさ検査の対象となる.あいまいさの検査は,決して呼び出せない関数を拒否する.例えば,
code: te6.hs
f :: C a => Int
すべての呼び出しがあいまいな制約を引き起こすので,fへの合法な呼び出しがあり得ないということである.実際,あいまいさ検査の目的は,呼び出すことができない可能性のある関数を報告することだけである.(訳注:ということは,)あいまいさのエラーを呼び出し場所まで遅らせるという犠牲を払えば,型シグネチャのあいまいさの検査を完全に取り除くことができる.実際に,AllowAmbiguousTypes拡張はあいまいさ検査を無効にする.
あいまいさは(敏感すぎる|わかりにくい)ことがある.関数従属を使う次の例を考えてみよう.
code: te7.hs
class D a b | a -> b where ..
h :: D Int b => Int
Intは呼び出し側でbを決めそうだし,そのようなシグネチャは拒否されるべきではない(訳注:関数従属の単射性から,Intから一意にbの型が決まるため).さらに,依存関係が隠されている可能性もある.次の例を考えてみよう.
code: te8.hs
class X a b where ...
class D a b | a -> b where ...
instance D a b => X a b where... h :: X a b => a -> a
ここではhの型bはあいまいに見るが,以下は正当な呼び出しになる.
code: te9.hs
上記のコード(te9.hs)は(X [Bool] beta)制約を引き起こす.そしてそのインスタンスを使用することから(D Bool beta)制約が必要となり,Dの関数従属性からbetaが決まる
(訳注:逐語訳の日本語が微妙なので文構造を変更した).
これらすべての特別な場合の背後には,単純な指針となる原則がある.以下の例を考えてみよう.
code:te10.hs
f :: type
f = ...blah...
g :: type
g = f
gの定義は確かに型検査に通るだろうと思いたくなるだろう!だって,fはgとまったく同じ型で,g = fなのだから.しかし,実際には fの型が具体化され,具体化された制約はgのシグネチャによって束縛される制約に対して解決される.そのため,あいまいな型の場合,解決は失敗する.たとえば,前述の定義f :: C a => Intを考える.
code:te11.hs
f :: C a => Int
f = ...blah...
g :: C a => Int
g = f
gの定義内では,(訳注:fの制約を)(C alpha)に具体化して,(訳注:gの型シグネチャかの)(C a)から(C alpha)を推論しようとして,(訳注:型推論に)失敗する.
そのため実際には、これをあいまいさの定義として使用する.型tyが曖昧である必要十分条件を,((undefined :: ty) :: ty)が型検査に通らないこととして定義する.推論された型についても非常によく似たテストを使用して,それらにもあいまいさがないことを確認する.
あいまいさ検査を無効にすることについて:関数が「指針となる原則」によればあいまいとなる型を持っていても,その関数は呼び出し可能である可能性がある.例えば,
code:te12.hs
class D a b where ...
instance D Bool b where ...
strange :: D a b => a -> a
strange = ...blah...
foo = strange True
ここではstrangeの型はあいまいだが、fooの呼び出しは問題ない.なぜなら,(D Bool beta)という制約が発生し,これが(D Bool b)インスタンスによって解決可能だからである.
(訳注:ここではbの型は型推論から具体的には決まらないが,インスタンスの決定できるので,呼び出し側での型検査に失敗しない.)
呼び出し場所でのあいまいさを取り除くもう1つの方法は,TypeApplications拡張を使用して型を指定することだ.例えば,
code:te13.hs
class D a b where
h :: b
instance D Int Int where ...
main = print (h @Int @Int)
ここでaはDの定義内ではあいまいだが,後で型適用を使ってIntと指定されている.
AllowAmbiguousTypesを使用すると,あいまいさの検査を無効にすることができる.しかし,あいまいさの検査が無効でも,GHCは決して呼び出せないような以下のような関数については文句を言うだろう.
code:te14.hs
f :: (Int ~ Bool) => a -> a
歴史的メモ
GHCは以前は型シグネチャに対してより制限的で原則立っていない条件を課していた.型 forall tv1..tvn (c1, ...,cn) => typeに対し,GHCは以下の条件をかつて要求していた.
全称量化されたそれぞれの型変数tviは,typeから「到達可能」でなければならない.
すべての制約ciが少なくとも1つの全称量化された型変数tviに言及していなければならない.
このアドホックな制約は,新しいあいまいさ検査に完全に含まれている.
明示的に種付けされた量化
KindSignatures
6.8.1以降
型変数の種シグネチャを明示するのを許可する.
Haskellはそれぞれの型変数の種を推論する.関数に型シグネチャを付けるのが良いのと同じように,(機械によってチェックされる)ドキュメンテーションとして,種を明示的に与えることができるのが良いという状況がある.場合によっては,そうすることが不可欠である.たとえば,John Hughesの論文「Haskellの制限付きデータ型」(Haskell Workshop 1999)では,彼は以下のようなデータ型を定義する必要があった. code:te15.hs
| Unused (cxt a -> ())
Unusedコンストラクタの唯一の用途は,型変数cxtに正しい種を強制することであった.
GHCでは,代わりに,型変数が明示的に束縛されている場合はいつでも,KindSignatures拡張を使用すれば,型変数の種を直接指定できるようになった.
この拡張は、次の場所で種シグネチャを有効にする.
data宣言
code:te16.hs
data Set (cxt :: Type -> Type) a = Set a type宣言
code:te17.hs
type T (f :: Type -> Type) = f Int
class宣言
code:te18.hs
class (Eq a) => C (f :: Type -> Type) a where ...
型シグネチャ内のforall
code:te19.hs
f :: forall (cxt :: Type -> Type). Set cxt Int
括弧は必須である.
同じ拡張の一部として,型に種注釈を付けることもできる.したがって,
code:te20.hs
f :: (Int :: Type) -> Int
g :: forall a. a -> (a :: Type)
などと書ける.
構文は次のとおりである.
code:te21
atype ::= '(' ctype '::' kind ')'
(訳注:原文では最後のシングルクォートが欠けているので,補った.)
括弧は必須である.