型付き穴
型付き穴は,式として使用される,(“_”, “_foo”, “_bar”のような)先頭にアンダースコアが付いた特別なプレースホルダを許すGHCの機能である.コンパイル時に,これらの穴は以下の3つを記述するエラーメッセージを生成する.
穴の位置に予想される型
任意の自由型変数の素性の情報
以下のリスト:
穴を埋めるのに役立つ可能性のあるローカル束縛
スコープにある束縛の中で,穴の型に合い,実際のコードで穴を埋めるのに役立つかもしれないもの
型付き穴はGHCで常に有効である.
型付き穴の目的は,型システムの変更ではなくHaskellのコードを書くのを助けることである.型付の穴は,これ以外の方法で得にくい可能性のある追加の情報を型検査器から得るために使うことができる.通常,GHCiを使うと,ユーザはすべてのトップレベルの束縛の(推論された)型シグネチャを調べることができる.しかしながら,この方法は,トップレベルで定義されていない項や複雑な式の中の項ではあまり便利ではない.穴を使用すると,ユーザは書き込もうとしている項の型を確認することができる.
例えば,GHCを使って次のモジュールをコンパイルしてみよう.
code: th1.hs
f :: a -> a
f x = _
これは次のようなエラーを吐いて失敗する.
code: th2
hole.hs:2:7:
Found hole `_' with type: a
Where: `a' is a rigid type variable bound by
the type signature for f :: a -> a at hole.hs:1:6
In the expression: _
In an equation for `f': f x = _
Relevant bindings include
x :: a (bound at hole.hs:2:3)
f :: a -> a (bound at hole.hs:2:1)
Valid hole fits include x :: a (bound at hole.hs:2:3)
この詳細は以下のとおりである.
他の型エラーと同様に,Found holeエラーは通常,コンパイルを終了する.結局の所,あなたがやったことはプログラムからいくらかのコードを省略することである.それでも,-fdefer-typed-holesフラグを使用することで,穴を含むコードを実行してテストすることができる.このフラグは,実行時まで型付き穴のエラーの発生を抑制し,それらをコンパイル時の警告に変換する.これらの警告は-Wno-typed-holesフラグによって完全に抑制することができる.
Variable out of scopeエラーも同じふるまいをする.これはデフォルトでコンパイルを終了する. このようなエラーは,-fdefer-out-of-scope-variablesフラグによって延期することができる.このフラグは,実行時までスコープ外変数によって生成されたエラーを延期し,それらをコンパイル時警告に変換する.これらの警告は, -Wno-deferred-out-of-scope-variablesフラグで完全に抑制することができる.
その結果,穴または変数はundefinedのように動作するが,追加の利点として,コンパイル時に警告を表示し,実行時に評価された場合に同じメッセージを表示する.この動作は,-fdefer-type-errorsフラグの動作に従う.このフラグは-fdefer-typed-holesと-fdefer-out-of-scope-variablesを含む.実行時への型エラーの延期を参照せよ. アンダースコアで始まっていなくても,束縛されてないすべての識別子は型付き穴として扱われる.唯一の違いはエラーメッセージである.
code: th3.hs
cons z = z : True : _x : y
これは以下のエラーを発生させる.
code: th4
Foo.hs:3:21: error:
Found hole: _x :: Bool
Or perhaps ‘_x’ is mis-spelled, or not in scope
In the first argument of ‘(:)’, namely ‘_x’
In the second argument of ‘(:)’, namely ‘_x : y’
In the second argument of ‘(:)’, namely ‘True : _x : y’
Relevant bindings include
z :: Bool (bound at Foo.hs:3:6)
cons :: Bool -> Bool (bound at Foo.hs:3:1) Valid hole fits include
z :: Bool (bound at mpt.hs:2:6)
otherwise :: Bool
(imported from ‘Prelude’ at mpt.hs:1:8-10
(and originally defined in ‘GHC.Base’))
False :: Bool
(imported from ‘Prelude’ at mpt.hs:1:8-10
(and originally defined in ‘GHC.Types’))
True :: Bool
(imported from ‘Prelude’ at mpt.hs:1:8-10
(and originally defined in ‘GHC.Types’))
maxBound :: forall a. Bounded a => a
with maxBound @Bool
(imported from ‘Prelude’ at mpt.hs:1:8-10
(and originally defined in ‘GHC.Enum’))
minBound :: forall a. Bounded a => a
with minBound @Bool
(imported from ‘Prelude’ at mpt.hs:1:8-10
(and originally defined in ‘GHC.Enum’))
Foo.hs:3:26: error:
Variable not in scope: y :: Bool 明示的な穴(つまり、アンダースコアで始まるもの)に対しては,スコープ外変数よりも多い情報が(訳注:エラーメッセージの形で)与えられる.というのも後者は大抵の場合意図していないタイポであり,追加の情報は気が散るだけで余計だからである.詳細な情報が必要な場合は,先頭のアンダースコアを使用して,穴を使用するという意図を明示せよ.
同じ関数内でも,同じ名前の束縛されてない識別子が決して単一化されることはないが,個別に表示される.例えば,
code: th5.hs
cons = _x : _x
これは以下のエラーを発生させる.
code:th6
unbound.hs:1:8:
Found hole '_x' with type: a
Where: `a' is a rigid type variable bound by
the inferred type of cons :: a at unbound.hs:1:1 In the first argument of (:)', namely _x'
In the expression: _x : _x
In an equation for `cons': cons = _x : _x
Relevant bindings include cons :: a (bound at unbound.hs:1:1) unbound.hs:1:13:
Where: ‘a’ is a rigid type variable bound by
the inferred type of cons :: a at unbound.hs:3:1-12
Or perhaps ‘_x’ is mis-spelled, or not in scope
In the second argument of ‘(:)’, namely ‘_x’
In the expression: _x : _x
In an equation for ‘cons’: cons = _x : _x
Relevant bindings include cons :: a (bound at unbound.hs:3:1) Valid hole fits include
with cons @a
(defined at mpt.hs:3:1)
mempty :: forall a. Monoid a => a
(imported from ‘Prelude’ at mpt.hs:1:8-10
(and originally defined in ‘GHC.Base’))
_xの2の異なる出現について報告された2つの異なる型に注目してみよう.
型付き穴を用いるのに言語拡張は必要とされない.語彙素(lexme)_はかつてHaskellでは違法だったが、今ではより有益なエラーメッセージが表示されるようになった. 語彙素_xは完全に合法な変数であり,そのスコープ内であればその動作が変わらない.例えば,
code: th7.hs
f _x = _x + 1
はまったくエラーを引き起こさない.(アンダースコアで始まっているか否かにかかわらず))スコープにない変数のみがエラーとして扱われる.(昔から常にそうであったが,)今ではより有益なエラーメッセージが表示される.
式で使用される束縛されていないデータコンストラクタは上記と全く同じように振る舞う.ただし,パターンで使用されている束縛されていないデータコンストラクタは(訳注:エラーを実行時まで)延期できず,代わりにコンパイルが中断される.(実装の専門用語を使って説明するなら,それらは型検査器ではなく,Renamerによって報告されるということである.)
Valid Hole Fit(訳注:型付き穴に当てはまる妥当な実装のこと)のリストは,スコープ内のどの束縛が穴に収まるかを検査することによって見つけられる.例えば,GHCで次のモジュールをコンパイルすると:
code: th8.hs
import Data.List (inits)
g = _ "hello, world"
以下のエラーを生む:
code: th9
• In the expression: _
In the expression: _ "hello, world"
In an equation for ‘g’: g = _ "hello, world"
• Relevant bindings include g :: String (bound at mpt.hs:6:1) Valid hole fits include
(imported from ‘Prelude’ at mpt.hs:3:8-9
(and originally defined in ‘base-4.11.0.0:Data.OldList’))
(imported from ‘Prelude’ at mpt.hs:3:8-9
(and originally defined in ‘base-4.11.0.0:Data.OldList’))
inits :: forall a. a -> a with inits @Char
(imported from ‘Data.List’ at mpt.hs:4:19-23
(and originally defined in ‘base-4.11.0.0:Data.OldList’))
repeat :: forall a. a -> a with repeat @String
(imported from ‘Prelude’ at mpt.hs:3:8-9
(and originally defined in ‘GHC.List’))
fail :: forall (m :: * -> *). Monad m => forall a. String -> m a
with fail @[] @String
(imported from ‘Prelude’ at mpt.hs:3:8-9
(and originally defined in ‘GHC.Base’))
return :: forall (m :: * -> *). Monad m => forall a. a -> m a
with return @[] @String
(imported from ‘Prelude’ at mpt.hs:3:8-9
(and originally defined in ‘GHC.Base’))
pure :: forall (f :: * -> *). Applicative f => forall a. a -> f a
with pure @[] @String
(imported from ‘Prelude’ at mpt.hs:3:8-9
(and originally defined in ‘GHC.Base’))
read :: forall a. Read a => String -> a
(imported from ‘Prelude’ at mpt.hs:3:8-9
(and originally defined in ‘Text.Read’))
mempty :: forall a. Monoid a => a
(imported from ‘Prelude’ at mpt.hs:3:8-9
(and originally defined in ‘GHC.Base’))
型付き穴について表示されるコンテキスト情報の量を制御するためのフラグがいくつかある.
-fshow-hole-constraints
型付き穴を報告するとき,スコープ内にある制約もプリントする.例えば,
code: th10.hs
f :: Eq a => a -> Bool
f x = _
この場合,次のメッセージが表示される.
code: th11
show_constraints.hs:4:7: error:
• Found hole: _ :: Bool
• In the expression: _
In an equation for ‘f’: f x = _
• Relevant bindings include
x :: a (bound at show_constraints.hs:4:3)
f :: a -> Bool (bound at show_constraints.hs:4:1)
Constraints include Eq a (from show_constraints.hs:3:1-22)
Valid hole fits include
otherwise :: Bool
False :: Bool
True :: Bool
maxBound :: forall a. Bounded a => a
with maxBound @Bool
minBound :: forall a. Bounded a => a
with minBound @Bool
Valid Hole Fit
GHCは時々,型付き穴のValid Hole Fitを提案する.これはいくつかのフラグによって(訳注:詳細を)設定することができる.
-fno-show-valid-hole-fits
デフォルト:オフ
このフラグはValid Hole Fitの表示を完全にオフにすることができる.
-fmax-valid-hole-fits=⟨n⟩
デフォルト:6
Valid Hole Fitのリストは,穴ごとに最大6つのHole Fitを表示するよう制限されている.表示されるHole Fitsの数は,このフラグで設定できる.-fno-max-valid-hole-fitsで制限を無効にすると,見つかったすべてのHole Fitが表示される.
-fshow-type-of-hole-fits
デフォルト:オン
デフォルトでは,Hole FitはHole Fitの型を表示する.このフラグの逆のフラグが存在して,それを使ってこれをオフにすることができる.
-fshow-type-app-of-hole-fits
デフォルト:オン
デフォルトでは,Hole FitはこのHole Fitを穴の型に合わせるために必要な型適用を表示する.例えば,(_ :: Int -> [Int])という穴では,memptyはmempty @(Int -> [Int])によってHole Fitする.このフラグの逆が存在して,それを使ってこれをオフにすることができる.
-fshow-type-app-vars-of-hole-fits
デフォルト:オン
デフォルトでは,Hole FitはこのHole Fitを穴の型に合わせるために必要な型適用を表示する.例えば,(_ :: Int -> [Int])という穴では,mempty :: Monoid a => aは,mempty @(Int -> [Int])によってHole Fitする.このフラグでは,Valid Hole Fitのメッセージのwhere句で,mempty @(Int -> [Int])の代わりにa ~ (Int -> [Int])を表示するかどうかを切り替える.
-fshow-provenance-of-hole-fits
デフォルト:オン
デフォルトでは,各Hole Fit には,そのHole Fitの出所の情報,つまり束縛された場所または定義された場所,そして,インポートされた場合はもともと定義されていたモジュール,が表示される.このフラグの逆が存在して,それを使ってこれをオフにすることができる.
-funclutter-valid-hole-fits
デフォルト:オフ
このフラグは,候補の出所や型適用を表示しないことで,Valid Hole Fitの冗長性を減らすためのものである.
Refinement Hole Fit
-frefinement-level-hole-fits=⟨n⟩フラグでnが0より大きい値に設定されている場合,GHCはValid Refinement Hole Fitのリストを提示する.Valid Refinement Hole Fitというのは,完全になるために追加で最大n段階のrefinementが必要なvalid hole fitである.ここで,1段階というのは,hole fitの中の,埋める必要のある追加の穴のことである(訳注:要するに再帰の深さがnだということ).
例として,以下のコード内の穴を考えよう.
code: th12.hs
f = _
Refinementレベルが設定されていない場合,Valid Hole Fitの提案のみが提示される.
code: th13
Valid hole fits include
with head @Integer
with last @Integer
maximum :: forall (t :: * -> *).
Foldable t =>
forall a. Ord a => t a -> a
with maximum @[] @Integer
minimum :: forall (t :: * -> *).
Foldable t =>
forall a. Ord a => t a -> a
with minimum @[] @Integer
product :: forall (t :: * -> *.
Foldable t =>
forall a. Num a => t a -> a
with product @[] @Integer
sum :: forall (t :: * -> *).
Foldable t =>
forall a. Num a => t a -> a
with sum @[] @Integer
しかし,例えば-frefinement-level-hole-fits=⟨n⟩を1に設定すると,追加でRefinement Hole Fitのリストが提示される.この場合では,以下のようになる.
code: th14
Valid refinement hole fits include
foldl1 (_ :: Integer -> Integer -> Integer)
with foldl1 @[] @Integer
where foldl1 :: forall (t :: * -> *).
Foldable t =>
forall a. (a -> a -> a) -> t a -> a
foldr1 (_ :: Integer -> Integer -> Integer)
with foldr1 @[] @Integer
where foldr1 :: forall (t :: * -> *).
Foldable t =>
forall a. (a -> a -> a) -> t a -> a
const (_ :: Integer)
where const :: forall a b. a -> b -> a
with ($) @'GHC.Types.LiftedRep @Integer @Integer where ($) :: forall a b. (a -> b) -> a -> b
fail (_ :: String)
where fail :: forall (m :: * -> *).
Monad m =>
forall a. String -> m a
return (_ :: Integer)
with return @((->) Integer) @Integer where return :: forall (m :: * -> *). Monad m => forall a. a -> m a
(Some refinement hole fits suppressed;
use -fmax-refinement-hole-fits=N or -fno-max-refinement-hole-fits)
これは,穴が,例えばfoldl1 _に置き換えられることを示している.穴を決定するものではない一方で,ユーザーに(訳注: 穴を埋めるコード片として)どんな選択肢があるのかをユーザーが理解するための一助となる.
-frefinement-level-hole-fits=⟨n⟩
デフォルト:オフ
Valid Refinement Hole Fitのリストは,様々な量の追加の穴を有するHole Fitを考慮することによって生成される.Refinementでの穴の量は,このフラグによって設定できる.フラグが0に設定されているか,全く設定されていない場合は,Valid Refinement Hole Fitは提案されない.
-fabstract-refinement-hole-fits
デフォルト:オフ
Valid Refinement Hole Fitの有効なリストはRefinementレベルが2以上のときに長くなることが多く,head _ _やfst _ _などの穴は有効なRefinementだが,1つ以上の穴があるため関連性が低いと考えられる.というのも,それらの穴の型も種も提案された識別子によって一切制約されていないからである.デフォルトでは,そのような穴は報告されない.このフラグをオンにすることで,そのような穴もValid Refinement Hole Fitのリストに含まれるようになる.
-fmax-refinement-hole-fits=⟨n⟩
デフォルト:6
Valid Refinement Hole Fitのリストは,穴ごとに最大6つのHole Fitを表示するよう制限されている.表示されるHole Fitの数はこのフラグで設定できる.-fno-max-refinement-hole-fitsで制限をオフにすると,見つかったすべてのHole Fitが表示される.
-fshow-hole-matches-of-hole-fits
デフォルト:オン
Refinement Hole Fitに当てはまる追加の穴の型が出力に表示される.例えば,foldl1 (_ :: a -> a -> aは,穴_ :: [a] -> aのRefinementである.このフラグをオフにすると,出力にはfoldl1 _のみが表示される.これは,ScopedTypeVariables拡張なしに,穴の直接的な代替として使用できる.
Valid Hole Fitのソート
Valid Hole Fitをソートする方法は,現在2つある.ソートは-fsort-valid-hole-fitsフラグで切り替えることができる.
-fno-sort-valid-hole-fits
デフォルト:オフ
デフォルトでは,Valid Hole Fitは,最も関連性の高いHole FitがValid Hole Fitのリストの一番上に表示されるようにソートされている.これはこのフラグで切り替えることができる.
-fsort-by-size-hole-fits
デフォルト:オン
穴の型と一致するためには関数の型での量化された型変数がどの程度の大きさでなければならないかによってソートする.
-fsort-by-subsumption-hole-fits
デフォルト:オフ
もう一つのソート.Hole Fitが他のHoleFitを含むかどうかの検査によってソートする.例えば,Hole Fit aがHole Fit bのHole Fitとして使用できる場合,出力はaの前にbが現れる.これはデフォルトのソートよりも細かい(hsjoihs「『精密』とかにしたい」Lugendre「悩んで無難な訳にした」)が,Valid Hole Fitの各ペアに対して包含検査を行う必要があるため,遥かに遅くなる.