部分型シグネチャ
PartialTypeSignatures
7.10.1以降
型検査器が穴に対して推論された型を許すようになる.
ワイルドカードと呼ばれる,アンダースコアで始まる特別なプレースホルダー(_, _foo, _bar)を含んでいる型シグネチャのことを部分型シグネチャという.部分型シグネチャと型シグネチャの関係は,型付き穴と式の関係と同じである.コンパイル中,これらのワイルドカード(穴)は,「穴の位置でどのような型が推論されたか」と「自由な型変数の素性」を記述するエラーメッセージを生成する.GHCはデフォルトでそのようなエラーメッセージを報告する.
型付き穴の存在はプログラムを不完全なものとし,型付き穴は評価されたときにエラーを生成する.一方,型シグネチャ内の穴に関しては必ずしもそうではない.型検査器は(ほとんどの場合)型シグネチャがあってもなくても束縛を型検査することができる.部分型シグネチャは両極端(訳注:完全に指定された型シグネチャ vs. 型シグネチャがない)の間のギャップを橋渡しする.つまり,型のどの部分を注釈し,どの部分を型検査器が推論するのに任せるのか,をプログラマが選べるようになる.
デフォルトでは,型検査器は部分型シグネチャ内のそれぞれの穴に対してエラーメッセージを報告し,推論された型をプログラマに知らせる.PartialTypeSignatures拡張が有効化されているときは,型検査器は各穴について推論された型を採用し(訳注:推論された型を用いて型検査を行うの意),エラーの代わりに警告を生成する.加えて,これらの警告は-Wno-partial-type-signaturesを用いて握りつぶすことができる.
しかしながら,型の一部が(訳注:PartialTypeSignaturesで)省略されているときは,GHCはその型を推論しなければならないため,GHCは多相的な再帰を用いることができない.型シグネチャが完全に省かれているときも同様の制約が生じる.
構文
(部分)型シグネチャはforall a b .. . (C1, C2, ..) => tauの形をとる.これは以下の3つの要素から構成される.
型変数: a b ..
constraint: (C1, C2, ..)
(mono)type:tau
我々は三種のワイルドカードを区別する.
型ワイルドカード
型シグネチャのmonotype(tau)のところに出てきたワイルドカードは型ワイルドカードである(これがデフォルトのワイルドカードであるので,「型」を省略して単に「ワイルドカード」と言ったりすることもよくある).型ワイルドカードは Bool や Maybe [Bool] といった任意のmonotype(これは(Int -> Bool) のような関数や Maybeといった高階の型を含む)へと具体化することができる.
code: pts1.hs
not' :: Bool -> _
not' x = not x
-- Inferred: Bool -> Bool
maybools :: _
just1 :: _ Int
just1 = Just 1
-- Inferred: Maybe Int
filterInt :: _ -> _ -> Int filterInt = filter -- has type forall a. (a -> Bool) -> a -> a -- Inferred: (Int -> Bool) -> Int -> Int 例えば,not'の型シグネチャにある最初のワイルドカードは,以下のようなエラーメッセージを生成するだろう:
code: pts2
Test.hs:4:17: error:
• Found type wildcard ‘_’ standing for ‘Bool’
To use the inferred type, enable PartialTypeSignatures
• In the type signature:
not' :: Bool -> _
• Relevant bindings include
not' :: Bool -> Bool (bound at Test.hs:5:1)
monotypeに具体化されない場合,ワイルドカードは一般化される.すなわち,新鮮な型変数に置き換わる.例えば,
code: pts3.hs
foo :: _ -> _
foo x = x
-- Inferred: forall t. t -> t
filter' :: _
filter' = filter -- has type forall a. (a -> Bool) -> a -> a -- Inferred: (a -> Bool) -> a -> a 名前付きワイルドカード
NamedWildCards
7.10.1以降
型シグネチャでの(_xのような)ワイルドカードの名前付けを許す.
型ワイルドカードは,アンダースコアの後ろに識別子を付けることで命名することもできる(例えば,_aのように).これらは名前付きワイルドカードと呼ばれている.単一の型シグネチャ内の同一の名前付きワイルドカードは同一の型に単一化される.例えば,
code: pts4.hs
f :: _x -> _x
f ('c', y) = ('d', error "Urk")
-- Inferred: forall t. (Char, t) -> (Char, t)
名前付きワイルドカードにより,引数の型と結果の型が同一となることが強制される.シグネチャがなければ,GHCは forall a b. (Char, a) -> (Char, b)を推論しただろう.名前付きワイルドカードは制約内で言及することもできるが,名前付きワイルドカードが何らかの型と単一化することを保証するために型シグネチャのmonotypeの部分のなかでもそれが登場しなければならない.
code: pts5.hs
somethingShowable :: Show _x => _x -> _
somethingShowable x = show x
-- Inferred type: Show a => a -> String
somethingShowable' :: Show _x => _x -> _
somethingShowable' x = show (not x)-- Inferred type: Bool -> String
追加制約のワイルドカード(下述)を除けば,名前付きワイルドカードのみが制約の中で登場できる.例:Show _x 中の_x
名前付きワイルドカードを型変数と混同することのないように.構文的には似ているが,名前付きワイルドカードはmonotypeと単一化することができるし,一般化されて型変数としてふるまうこともできる.
上記の最初の例では,_xは一般化され(,事実上新鮮な型変数aによって置き換えられ)る.2番目の例では,_xはBool型と単一化され,BoolがShow型クラスのインスタンスであるため,Show Bool制約は単純化して消すことができる.
デフォルトでは,GHCは(Haskell 2010の標準仕様に基づいて)型の中にあるアンダースコアで始まる識別子を型変数として構文解析する.それらを名前付きワイルドカードとして扱うためには,NamedWildCards拡張を有効化する必要がある.以下は拡張の効果を示す例である.
code: pts6.hs
foo :: _a -> _a
foo _ = False
NamedWildCardsを有効化せずにこのプログラムをコンパイルすると,型変数_aが実際の型Boolにマッチしないということを訴える,以下のエラーメッセージが生成される.
code: pts7
Test.hs:5:9: error:
• Couldn't match expected type ‘_a’ with actual type ‘Bool’
‘_a’ is a rigid type variable bound by
the type signature for:
foo :: forall _a. _a -> _a
at Test.hs:4:8
• In the expression: False
In an equation for ‘foo’: foo _ = False
• Relevant bindings include foo :: _a -> _a (bound at Test.hs:5:1)
このプログラムをNamedWildCards (と PartialTypeSignatures)を有効化した状態でコンパイルすると,名前付きワイルドカード_aの推論された型を報告する以下のようなエラーメッセージが生成される.
code: pts8
• Found type wildcard ‘_a’ standing for ‘Bool’
• In the type signature:
foo :: _a -> _a
• Relevant bindings include
foo :: Bool -> Bool (bound at Test.hs:5:1)
追加制約(Extra-Constraints)のワイルドカード
3種類目のワイルドカードは追加制約のワイルドカードである.追加制約のワイルドカードの存在は,任意の数の追加の制約を型チェック中に推論して型シグネチャに追加することが許されるということを示す.以下の例では,追加制約のワイルドカードは,3つの追加の制約を推論するために用いられている.
code: pts9.hs
arbitCs :: _ => a -> String
arbitCs x = show (succ x) ++ show (x == x)
-- Inferred:
-- forall a. (Enum a, Eq a, Show a) => a -> String
-- Error:
Test.hs:5:12: error:
Found constraint wildcard ‘_’ standing for ‘(Show a, Eq a, Enum a)’
To use the inferred type, enable PartialTypeSignatures
In the type signature:
arbitCs :: _ => a -> String
自分で知っている(または注釈したい)制約をプログラマが予め記載しようとすることが,追加制約のワイルドカードによって妨げられないべきである.例えば,
code: pts10.hs
-- Also a correct partial type signature:
arbitCs' :: (Enum a, _) => a -> String
arbitCs' x = arbitCs x
-- Inferred:
-- forall a. (Enum a, Show a, Eq a) => a -> String
-- Error:
-- Test.hs:9:22: error:
-- Found constraint wildcard ‘_’ standing for ‘()’
-- To use the inferred type, enable PartialTypeSignatures
-- In the type signature:
-- arbitCs' :: (Enum a, _) => a -> String
追加制約のワイルドカードによって一切の追加の制約が推論されない,という場合もある.例えば,
code: pts11.hs
noCs :: _ => String
noCs = "noCs"
-- Inferred: String
- Error:
-- Test.hs:13:9: error:
-- Found constraint wildcard ‘_’ standing for ‘()’
-- To use the inferred type, enable PartialTypeSignatures
-- In the type signature:
-- noCs :: _ => String
追加制約のワイルドカードが1つあれば何個でも制約を推論できるため,一つの型シグネチャ内には追加制約のワイルドカードは1つしか書くことができず,また制約のリストの最後に置かれねばならない.
追加制約のワイルドカードに名前を付けることはできない.
部分型シグネチャはどこに書けるのか
部分型シグネチャは束縛シグネチャ・パターンシグネチャ・式シグネチャで許容される.ただし,追加制約のワイルドカードはパターンシグネチャや式シグネチャではサポートされない.次の例では,3つの可能な文脈(訳注:=束縛シグネチャ・パターンシグネチャ・式シグネチャ)それぞれでワイルドカードが用いられている.
code: pts12.hs
{-# LANGUAGE ScopedTypeVariables #-} foo :: _
foo (x :: _) = (x :: _)
-- Inferred: forall w_. w_ -> w_
名前無しワイルドカードや名前付きワイルドカードは型インスタンス宣言やデータインスタンス宣言の左辺に登場することができる.型族の「データと型族のインスタンスの左辺のワイルドカード」を参照せよ. 名前無しワイルドカードは「目に見える型適用」内でも許容される(目に見える型適用を参照せよ).wurbleに対して第二型引数のみを指定したい場合,wurble @_ @Intと書くことができ,ここで第一引数はワイルドカードである. standaloneなderiving宣言では,単一の追加制約のワイルドカードの使用が許される.例えば以下の通りである.
code: pts13.hs
deriving instance _ => Eq (Foo a)
これは,導出された Eq (Foo a) インスタンスを示し,その制約の推論は通常のderiving節がするのとほぼ同じように推論される.standaloneなderiving宣言内では,これ以外のどんなワイルドカードの使用も禁じられている.
それ以外の全ての制約内では,型ワイルドカードは許されず,名前付きワイルドカードは普通の型変数として扱われる.例えば:
code: pts14.hs
class C _ where ... -- Illegal
instance Eq (T _) -- Illegal (currently; would actually make sense)
instance Eq _a => Eq (T _a) -- Perfectly fine, same as Eq a => Eq (T a)
部分型シグネチャはTemplate Haskellのspliceの中でも用いることができる.
宣言splice:部分型シグネチャは完全にサポートされる.
code: pts15.hs
{-# LANGUAGE TemplateHaskell, NamedWildCards #-} $( [d| foo :: _ => _a -> _a -> _
foo x y = x == y|] )
式splice:名前無しワイルドカードと名前付きワイルドカードは式シグネチャ内で用いることができる.通常の式シグネチャと全く同様に,追加制約のワイルドカードはサポートされない.
code: pts16.hs
{-# LANGUAGE TemplateHaskell, NamedWildCards #-} 型付き式splice:(型なし) 式spliceでサポートされているワイルドカードが型付き式spliceでもサポートされる.
パターンsplice:名前無しワイルドカードと名前付きワイルドカードはパターンシグネチャで用いることができる.パターンシグネチャを許容するためにはScopedTypeVariablesが有効になっている必要があることに留意せよ.通常のパターンシグネチャと全く同様に,追加制約のワイルドカードはサポートされない.
code: pts17.hs
{-# LANGUAGE TemplateHaskell, ScopedTypeVariables #-} 型splice:型spliceでは名前無しワイルドカードのみがサポートされている. 名前付きワイルドカードや追加制約のワイルドカードはサポートされていない.
code: pts18.hs
{-# LANGUAGE TemplateHaskell #-} foo x = x