HaskellをGHC 9.14で動かす——specialisation、stage、recordの変更
GHC 9.14.1の式に対するSPECIALISE、明示的なstage別import、visible forall、record更新と診断を実行例から解説します。
GHC 9.14.1は2025年12月19日に公開されました。 2026年10月24日時点の9.14系列では、9.14.2の2番目のrelease candidateが9月17日に発表されています。 以下では9.14.1の公開済み機能を中心に扱い、9.14.2-rc2の修正は末尾で対象を分けて説明します(9.14.1の公開記録、9.14.2-rc2の公開記録)。
9.14では、SPECIALISEに具体的な値を渡して専用の実装を生成できます。 Template Haskellにはstageを指定するimportが加わり、recordの更新処理ではsetFieldの引数順序が変わります。 型の引数をconstructorへ渡す構文、linearなrecord field、部分的なselectorの診断にも変更があります(GHC 9.14.1の変更履歴)。
型だけでなく、値を固定してspecialiseする
従来のSPECIALISEは、polymorphicなfunctionと、専用化する型を指定するpragmaでした。 たとえばNum a => a -> aをInt -> Intへ専用化すると、数値演算に使うdictionaryを具体的なinstanceへ確定できます。 9.14では、functionにtype argumentや通常の引数を適用した式を指定できます。 型をIntへ固定し、さらに分岐の入力をTrueへ固定する、といった専用化が可能です(GHC proposal 493)。
{-# LANGUAGE ScopedTypeVariables, TypeApplications #-}
scale :: forall a. Num a => Bool -> a -> a
scale twice value = if twice then value * 2 else value + 1
{-# INLINE [0] scale #-}
{-# SPECIALISE scale @Int True #-}
{-# SPECIALISE scale @Int False #-}
掲載例は、真なら2倍、偽なら1を加えるfunctionです。 scale @Int Trueは型と最初の値を固定しますが、計算する整数は固定しません。 生成する専用実装の入力には、最後のIntだけが残ります。 したがって、引数の個数を変えずに型のdictionaryだけを取り除く従来の専用化より、入力を具体化する範囲が広くなっています。
GHC 9.14.1、macOS/arm64、-O2での呼び出し結果は、分岐に対応します。
main :: IO ()
main = print (scale @Int True 3, scale @Int False 3) -- => (6,4)
simplified Coreでは、加算だけを持つ実装と乗算だけを持つ実装に分かれます。 専用実装のbodyには、元のBoolを調べるcaseが残っていません。
scale_$sscale
= \ value -> case value of { I# x -> I# (+# x 1#) }
scale_$sscale1
= \ value -> case value of { I# x -> I# (*# x 2#) }
Core断片の一時変数は、compilerが付けるsuffixを除いた表記です。 I#から取り出した整数に対し、片方は+#、片方は*#を使っています。 型のdictionaryを毎回参照する処理と、Boolの分岐を毎回選ぶ処理を、両方とも専用化の段階で解消した結果です。 元のpolymorphicなscaleも残るため、専用化しない型や値の呼び出しを失う変更ではありません。
専用実装を作る操作と、呼び出しを書き換える規則
SPECIALISEの処理は、新しいbodyを作るだけでは完結しません。 呼び出し元が元のscaleを使い続ければ、専用化したbodyへ処理が移らないためです。 GHCは、元のfunctionへ固定した引数を渡す式を、生成したfunctionへ置き換えるrewrite ruleも作ります。 scale @Int Trueとscale @Int Falseには、値の固定に対応する2本のruleを生成します。
専用化の対象を選ぶのは、実行中の値を検査するdispatcherではありません。 compilerは最適化中、適用済みの型や値をruleへ一致させます。 呼び出し側のBoolが実行時まで分からなければ、単にSPECIALISEを書いたという理由だけで片方の専用実装へ確定しません。 専用化で消せる分岐は、最適化時点で既知の入力へ対応します(expression specialisationの生成規則)。
再帰するfunctionでも、固定した値を保って再帰する呼び出しへruleを適用できます。 たとえば同じmodeを渡し続けるloopなら、専用bodyの中から同じ専用bodyへ戻る形になります。 modeを毎回選び直す処理を残さず、modeに対応した再帰を生成できる構成です。 一方、再帰の途中でmodeを変更する呼び出しには、変更後の式に一致するruleが必要になります。
SPECIALISEへ渡す引数には、具体的なliteralだけでなく、束縛した変数を含む式も使えます。 たとえばtupleの形を固定しながら、中の値を変数として残す指定です。 自由変数を無条件に取り込む構文ではなく、pragma内のforallで束縛する変数と、通常のscopeにある名前を区別します。 生成する専用functionのparameterには、固定しなかった値の情報が残ります。
専用化する式は、型検査を通る必要があります。 元のfunctionへ適用できないtype argumentや値を、pragmaの中だけで許可する規則はありません。 SPECIALISEの先頭は対象のfunctionであり、任意の局所式に新しい名前を与える構文でもありません。 定義する位置もtop level、class宣言、instance宣言に限られ、letやwhereへ配置する仕組みではありません(proposal 493の構文とscope)。
INLINEの段階と、無効な専用化の診断
掲載例はINLINE [0]を使っています。 inlineする段階を指定し、functionの適用という形が最適化の途中まで残るようにした例です。 専用化のruleを使う処理と、元のbodyを呼び出し箇所へ展開する処理は、同じ式へ作用します。 先に元のbodyを展開し尽くした場合には、function名へ一致するruleを使う機会がなくなるため、最適化の段階も関係します。
NOINLINEを付けると、同じspecialisationを利用できない場合があります。 9.14.1は、NOINLINEのfunctionに対するpragmaを無効な専用化として診断します。 診断番号はGHC-38524で、対象のwarningは-Wuseless-specialisationsです。 -Werror=useless-specialisationsは、同じwarningをerrorとして扱います。
scale :: forall a. Num a => Bool -> a -> a
scale twice value = if twice then value * 2 else value + 1
{-# NOINLINE scale #-}
{-# SPECIALISE scale @Int True #-}
-- GHC-38524: -Wuseless-specialisations
診断は、専用化を書いたというsource上の事実と、compilerが実際に使える最適化を区別します。 型と値を具体化しない指定や、元のfunctionと同じ一般性を持つ指定も、無効と判断できる対象です。 追加したpragmaを単に受け付けるだけでは、生成するcodeへ効果があるかは分かりません。 9.14では、無効だと判断できた指定を専用のwarningへ分離しています(無効なspecialisationの追加診断)。
複数の型をcommaで並べる旧形式はdeprecatedになりました。 以前のSPECIALISE f :: Int -> Int, Double -> Doubleのような指定は、pragmaを2個に分ける移行対象です。 新しい診断は-Wdeprecated-pragmasが制御し、既定のwarningに含まれます。 旧形式の削除予定はGHC 9.18であり、9.14で直ちに受け付けなくなった構文ではありません(旧pragma構文の変更時期)。
9.14.1の公開時点では、polymorphic specialisationは既定で無効です。 有効化する場合は-fpolymorphic-specialisationを指定します。 公開記録では、不正なcode生成に関するregressionを理由に、既定化を見送ったと説明しています。 式に値を渡せるSPECIALISEと、polymorphicな形へさらに専用化する最適化は、有効化の条件が異なります(既定値とissue #26329)。
Template Haskellの依存先をstage別に宣言する
Template Haskellでは、compile中にHaskellのcodeを実行し、生成した構文をprogramへ埋め込めます。 通常の実行時に呼ぶfunctionと、compile時のspliceを作るfunctionでは、必要になる段階が異なります。 9.14のExplicitLevelImportsは、importする名前を使うstageをsourceへ記述する機能です。 import spliceは、top-level spliceを評価する段階で必要な依存先を示します(Explicit Level Importsのproposal)。
例では、別moduleのmakeAnswerが整数42の式を作ります。 StageMainは、makeAnswerを通常のimportではなく、spliceを付けて読み込みます。 $(makeAnswer)をcompile中に評価し、得られた式をanswerの定義へ入れます。 実行するprogramが表示する値は42ですが、makeAnswerを呼ぶのはprogramの実行時ではありません。
{-# LANGUAGE TemplateHaskellQuotes #-}
module StageHelper (makeAnswer) where
import Language.Haskell.TH (Exp, Q)
makeAnswer :: Q Exp
makeAnswer = [|42 :: Int|]
{-# LANGUAGE ExplicitLevelImports, TemplateHaskell #-}
module Main (main) where
import splice StageHelper (makeAnswer)
answer :: Int
answer = $(makeAnswer)
main :: IO ()
main = print answer -- => 42
補助moduleを含むcompileと実行には、次のcommandを使います。
ghc --make StageMain.hs -o stage-demo
./stage-demo
StageHelperの式を作る処理、spliceの評価、生成したprogramの実行が、別の段階としてつながっています。 module間の依存を単に実行時の呼び出し関係だけで描くと、compileを完了する前に必要な処理が抜けます。

hostで動く補助codeと、targetで動くprogram
cross-compilationでは、compilerを動かすhostと、完成したprogramを動かすtargetが異なります。 spliceを評価する補助moduleは、compile中にhostで動かなければなりません。 一方、完成したprogramからだけ呼ぶ依存先は、target向けのcodeが必要です。 stageを明示すると、依存先を必要とする理由が、import宣言から区別できるようになります(splice importとcross-compilation)。
通常のimportだけでは、補助functionをspliceから使うかを、moduleのbodyを読むまで確定できない構成がありました。 splice用だと分かれば、hostで実行するobject codeの準備を、通常の依存と分けて進められます。 targetのlibraryをhostで実行しようとして失敗する構成と、host向けに必要な補助libraryを準備する構成では、build graphが異なります。 新しいimportは、型を名前空間へ加える操作に、stageに関する情報を加えています。
quoteを付けたimportは、quotation内へ参照を残す側のstageに対応します。 spliceはcodeを作る処理を実行し、quoteは生成するcodeの構造を記述します。 両方に同じ名前が登場しても、値を今使うことと、後で使う参照を構文へ残すことは異なります。 stageの区別は、単にsourceを上から順に実行するという時刻の区分ではなく、生成するcodeの段階です。
明示的なlevelの検査では、通常のmodule bodyをlevel 0、top-level spliceをlevel -1として扱います。 名前がimportされたlevelと、名前を使用するlevelを照合します。 別のlevelでしか使えない名前は、通常のmodule exportへ無条件に再公開できません。 exportはlevel 0の利用者へ名前を渡す入口なので、splice専用の束縛を通常の値として渡す説明では整合しません(levelとexportの規則)。
stageは同名のimportを自動で選び分けない
異なるmoduleから同じ名前をimportした場合、使用箇所のstageだけで名前の曖昧さを解消する規則ではありません。 通常のA.xとsplice用のB.xを両方取り込み、裸のxを書けば、名前解決が曖昧になる構成があります。 stageを使って都合のよい方を選ぶのではなく、qualifiedな名前で対象を確定させます。 名前解決の後にstageの条件を検査するため、stageの区別が新しいoverloadの解決規則へ変わるわけではありません(Explicit Level Importsの名前解決)。
type classのinstanceも、利用できるlevelの影響を受けます。 同じclassと型に対し、通常のcode用とsplice用に別のinstanceをimportしても、stageごとの別世界として自由に共存させる仕組みではありません。 instanceの選択は、importしたinstance全体に関する整合性を保ちます。 選んだinstanceを使用するlevelの検査と、instance同士の重複検査は、別の条件として残ります。
9.14のExplicitLevelImportsは、Template Haskellの構文木を別の形式へ置き換える機能ではありません。 既存のquotationやspliceが行う生成処理へ、依存を宣言する方法を追加します。 したがって、生成する式の型が不正なら、stageを正しく宣言していてもcompileは成功しません。 stageに適した名前の利用と、生成したprogramの型の成立を、同じ1つの検査へまとめる変更ではありません。
constructorへrequired type argumentを渡す
RequiredTypeArgumentsでは、forall a ->で呼び出し時に明示するtype argumentを宣言できます。 従来のforall a.では、type inferenceで省略するか、@Intのようなtype applicationで補えます。 forall a ->の引数は、通常の呼び出しの構文へ現れます。 9.14では、visible forallをGADTのdata constructorにも使えるようになりました(required type argumentとGADT)。
{-# LANGUAGE GADTs, RequiredTypeArguments, TypeAbstractions #-}
data Typed a where
Typed :: forall a -> a -> Typed a
unTyped :: Typed a -> a
unTyped (Typed _ value) = value
掲載例のTyped Int 42は、最初に型Int、次に値42を渡します。 Typed Bool Trueなら、型Boolと値Trueです。 unTypedは、constructorへ渡した値を元の型で返します。
main :: IO ()
main = print (unTyped (Typed Int 42), unTyped (Typed Bool True)) -- => (42,True)
constructorの結果型は、先頭で渡した型に応じてTyped IntまたはTyped Boolになります。
型の引数が通常の値を渡す位置へ書かれていても、実行時に型objectを保存する意味ではありません。 required type argumentはcompile中に消去され、型を使うためのruntime reflectionを自動で作りません。 Typed Int 42を「Intという値と42という値を2個保存する」と説明すると、型消去後の表現を取り違えます。 明示した型は、後続の値と結果型の関係をcompile中に確定するための入力です。
requiredな型の位置でも、_を使ってtype inferenceへ任せられます。 ただし、型の引数がsource上に存在するという構文の条件は残ります。 型の引数を完全に省略した呼び出しと、型の位置を_で表した呼び出しは同じ形ではありません。 値42からIntを推論できる場面でも、visibleな引数を持つ宣言の適用規則に従います(visibleとinvisibleな型の引数)。
呼び出す構文と、patternで型を束縛する構文
TypeApplicationsは、既存のpolymorphicなfunctionへ型を明示して適用する拡張です。 TypeAbstractionsは、definitionやpatternの側で型の束縛を表す拡張です。 9.14では、ScopedTypeVariablesとTypeApplicationsの組み合わせだけでは、patternにtype applicationを書けなくなりました。 型のpatternを使うsourceでは、TypeAbstractionsを明示する必要があります(型のpatternに必要な拡張)。
ScopedTypeVariablesとTypeApplicationsだけでidentity @a value = valueを宣言すると、GHC-78249を出します。 @aはillegal invisible type patternとなり、TypeAbstractionsの追加が必要です。
{-# LANGUAGE ScopedTypeVariables, TypeApplications #-}
identity :: forall a. a -> a
identity @a value = value
-- GHC-78249: Illegal invisible type pattern
同じ@でも、呼び出す側と束縛する側を別の拡張で制御する境界が、compile結果へ現れています。
visible forallは、任意の値へ依存する型をすべて表せる機能へ完成したという意味ではありません。 型の引数を明示する構文と、値を型の計算へ反映する仕組みは異なります。 GHCのuser guideも、required type argumentを、proposal全体の部分的な実装として説明しています。 9.14ではdata constructorを含む利用位置が増え、任意のdependent typeは対象に含みません(RequiredTypeArgumentsの実装範囲)。
record更新では、新しい値を先に渡す
OverloadedRecordUpdateを使うと、record更新をsetFieldとgetFieldの呼び出しへ変換できます。 9.14では、setFieldへ渡す引数順序が変わりました。 以前はrecord、新しい値の順で、9.14では新しい値、recordの順です。 RebindableSyntaxで独自のsetFieldを定義するsourceは、signatureと実装の両方が変更対象です(overloaded record updateの定義)。
setField :: forall field record value.
Access field record value => value -> record -> record
たとえばoriginal { count = 9 }は、field名をtype argumentへ渡し、値9と元のrecordを渡す形になります。 新しい値を先に受けるため、部分適用したsetField @"count" 9を、recordを更新するfunctionとして扱えます。 古い実装へ引数だけを同じ順で渡し続ければ、recordの位置に9が入り、値の位置へrecordが入ります。 型が異なる一般的な例では、実行時に更新結果を間違える前に、signatureの不一致として拒否されます。
整数だけを持つCounterにも、countという仮想的なfieldを公開できます。 内部のconstructorへrecord labelを宣言する代わりに、Access "count" Counter Intで読み書きを定義しています。 更新は元のCounterを変更せず、新しい値を返します。 更新前の値は1を保持し、更新後の値9を持つCounterとは独立しています。
{-# LANGUAGE AllowAmbiguousTypes, DataKinds, FlexibleInstances #-}
{-# LANGUAGE FunctionalDependencies, KindSignatures, MultiParamTypeClasses #-}
{-# LANGUAGE OverloadedRecordDot, OverloadedRecordUpdate, RebindableSyntax #-}
{-# LANGUAGE TypeApplications #-}
import GHC.TypeLits (Symbol)
import Prelude
class Access (label :: Symbol) record value | label record -> value where
getField :: record -> value
setField :: value -> record -> record
newtype Counter = Counter Int deriving Show
instance Access "count" Counter Int where
getField (Counter value) = value
setField value _ = Counter value
main :: IO ()
main = do
let original = Counter 1
updated = original {count = 9}
print (original.count, updated.count) -- => (1,9)
OverloadedRecordDotによる参照と、OverloadedRecordUpdateによる更新は、使うoperationが異なります。 参照はgetField、更新はsetFieldへ変換します。 fieldへアクセスする記法を追加しただけでは、更新を担当するfunctionのsignatureまで自動で用意されません。 9.14時点のoverloaded updateは、利用者がgetFieldとsetFieldを用意するexperimentalな拡張です。
入れ子のcompany { owner.name = value }では、途中のownerを取得し、内側のnameを更新してから、外側へ更新結果を戻します。 内側の更新だけでなく、外側のrecordへ値を組み戻す呼び出しにも、同じ引数順序が適用されます。 独自のsetFieldを持つlibraryでは、単純なfield更新と入れ子の更新が同じ変換規則を使うことになります。 field名を文字列としてruntimeで探索する機能とは異なり、名前はtype applicationとして渡ります(入れ子のrecord更新の変換)。
部分的なselectorは-Wallで見つかる
一部のconstructorにしか存在しないfieldのselectorは、すべての値へ適用できるfunctionではありません。 Circleだけがradiusを持ち、Pointには持たせない型なら、radius Pointは値を取り出せません。 9.14では、-Wincomplete-record-selectorsが-Wallへ加わりました。 以前と同じ-Wall -Werrorでも、部分的なselectorを使うsourceがbuildに失敗する範囲が増えます(部分的なrecord selectorの診断)。
data Shape = Circle { radius :: Int } | Point
readRadius :: Shape -> Int
readRadius = radius
-Wall -Werror=incomplete-record-selectorsでは、GHC-17335をerrorとして報告します。 診断に現れる失敗対象のconstructorはPointです。 field名の綴りが誤っているのではなく、受け取るShapeの集合に、fieldを持たない値が含まれています。 readRadiusの宣言を読むだけでは見えないpartialityを、selectorの使用箇所で報告する診断です。
Maybe Intを返すfunctionへ変えれば、Circle nをJust n、PointをNothingへ対応付けられます。 必ず半径を持つ型だけを受け取るAPIへ分ける方法もあります。 両方とも、従来失敗していた入力を同じ契約のまま隠す修正ではなく、入力または返り値へ失敗条件を表す変更です。 warningをerrorにしない設定ではcompileを続けられますが、selectorの定義域を広げる効果はありません。
record selectorを単にpattern matchingへ置換しても、Pointの分岐を省略すればpartialityは残ります。 必要なのは、field名を使わない構文へ変えることだけではなく、すべての入力を処理する返り値の設計です。 CircleとPointの両方を処理する定義なら、半径を取得できない条件がfunctionのbodyに現れます。 9.14のwarningは、recordの記法の背後へ隠れていた失敗経路を、既存のwarning groupへ加えています。
linearなrecordに、何度でも使うfieldを混ぜる
LinearTypesでは、入力を1回消費するfunctionをa %1 -> bと書きます。 recordを1回消費してpattern matchingすると、constructorの各引数を使う回数にも条件が伝わります。 9.14では、record fieldへmultiplicityを明示し、linearなfieldとunrestrictedなfieldを同じrecordへ置けます。 すべてのfieldへ一律に同じ使用回数を課す代わりに、constructorのparameterごとの契約を表す追加です(linear record fieldの規則)。
{-# LANGUAGE DataKinds, LinearTypes #-}
import GHC.Types (Multiplicity (Many))
data Record = Record {count %'Many :: Int, marker :: Char}
duplicateCount :: Record %1 -> (Int, Int, Char)
duplicateCount (Record n c) = (n, n, c)
main :: IO ()
main = print (duplicateCount (Record 7 'x')) -- => (7,7,'x')
duplicateCountは、受け取ったrecordを1回だけpattern matchingします。 取り出したnは結果のtupleへ2回使い、cは1回使います。 countをManyとしているため、整数を2回使う実装を受け付けます。
通常のfunction arrowである->は、multiplicityがManyのarrowに対応します。 %1 ->は、引数をlinearに消費する関係です。 recordのconstructorへmultiplicityを書いた場合は、recordを作る際に渡す値と、分解して取り出す値の扱いへ反映します。 外側のRecord %1 -> ...だけを見て、fieldを含めた全値を同じ回数に固定する説明では、countの条件を落とします。
multiplicityは、record selectorのarrowを自動で変えない
fieldのannotationは、record selectorの型へ同じ形で反映されるわけではありません。 countやmarkerのselectorは、通常のRecord -> ...というarrowを持ちます。 constructorをlinearに分解する操作と、既存のselectorをfunctionとして呼ぶ操作には、別の型が与えられています。 fieldのannotationは、selector経由の所有関係をlinearに変えません(constructorとselectorのmultiplicity)。
newtypeのfieldには、linearである条件が残ります。 一般のdataへ許可したunrestricted fieldを、newtypeにも無条件で書けるという規則ではありません。 newtypeは元の表現と接続する型なので、通常のdata constructorと同じ選択肢を持つとは扱いません。 9.14の追加構文を共通化して生成するtoolでも、dataとnewtypeで許される宣言を分ける必要があります。
型を推論する際に、compilerが任意のfunctionへlinear arrowを自動で付ける機能でもありません。 linearなfunctionやmultiplicity-polymorphicなfunctionのsignatureは明示します。 signatureなしのfunctionから、使う回数だけを見て公開APIのarrowを自動で変える説明では、既存のtype inferenceの規則と合いません。 掲載例は、外側のarrowとfieldのmultiplicityを両方sourceへ置いています。
kindの指定とnamespaceを、必要な拡張へ揃える
DataKindsを付けずにpromotedな型をkindの位置へ使う処理は、9.14でerrorになります。 従来の-Wdata-kinds-tcでwarningとして残していた入力を、拡張なしでは受け付けなくする変更です。 たとえば長さを型へ含めるVecで、parameterのkindをNatへ指定する宣言が該当します。 KindSignaturesによってkindを書く構文を許可したことと、Natをkindとして利用する条件は別です(DataKindsなしのkind指定)。
{-# LANGUAGE KindSignatures #-}
import GHC.TypeNats (Nat)
data Vec (n :: Nat) a = Empty
DataKindsがない宣言には、GHC-68567を出します。 Natの展開先はillegal kindとなり、kindへ使うにはDataKindsが必要です。 型のparameterへnを書くことだけが問題なのではなく、Natをkindの位置へ置くための拡張が不足しています。 9.14では、warningの抑制だけで同じ宣言を存続させる段階から、必要な拡張を明示する段階へ変わります。
ExplicitNamespacesでは、importとexportの名前にdataを付けられるようになりました。 型の名前とdata constructorの名前が同じ綴りになる場合にも、取り込むnamespaceを明示できます。 従来のpattern namespace specifierはdeprecatedとなり、-Wpattern-namespace-specifierで報告します。 constructorやpattern synonymを指す名前と、型を指す名前を、同じ綴りだけで曖昧に扱わないための構文です(namespace指定の変更)。
subordinate import listの中で不正なtype指定を使っていた入力も拒否されます。 親の型へ属する名前を列挙する位置と、独立した型の名前をimportする位置では、許される構文が違います。 namespaceを明示する拡張を有効にしただけで、すべての名前の列挙位置へ同じspecifierを挿入できるわけではありません。 compilerやsource生成toolがimportを書き出す場合は、名前の所属と構文の位置が接続する条件になります。
coerceのtype inferenceと、同じmoduleのtype family
coerceの呼び出しでは、Coercible constraintからtype variableを決定する推論が改善されています。 型の関係をconstraintに書いてあっても、変数を曖昧だと判断していた入力を扱うための変更です。 値の表現が同じ型同士を接続するcoerceへ、新しいruntime変換を加える変更ではありません。 受け付けるsourceの型を確定する過程が変わり、既存のCoercible関係が推論へ反映される範囲を改善しています(coerceの推論改善)。
kind checkerも、同じmoduleにあるtype family instanceを、別の宣言のkindを調べるために使う処理を改善しました。 type familyの適用結果がkindの計算へ入る場合、instanceをまだ利用できない状態で検査すると、本来確定できるkindを扱えません。 宣言が同じfileにあるという位置関係だけでなく、型の計算に必要な情報の依存が関係します。 9.14は、複数の既存issueにまたがっていた同じmodule内の処理を修正しています(kind checkerの対象issue群)。
DeepSubsumptionのhigher-rank typeに関する修正も含まれます。 functionの引数や結果にpolymorphicな型が現れる場合、単純な第1階の型の一致だけでは処理できません。 9.14ではhigher-rankな型を扱う複数の経路を修正していますが、拡張を付けずにすべてのhigher-rankな書き方を許す変更ではありません。 拡張の選択、型のsubsumption、診断するsourceの関係を保った修正です。
quantified constraintの解決では、一部の入力でsolverが無限に処理を続ける問題が修正されました。 constraintを解決する過程で別のquantified constraintを再び要求すると、同じ要求を増やし続ける経路ができます。 型エラーの内容だけではなく、compileが完了するかに影響する修正です。 修正対象は型検査のconstraint solverで、runtimeの評価が終了する性質とは独立しています(quantified constraintの修正)。
warningとcoverageが扱うsourceの範囲
-Wrule-lhs-equalitiesは、rewrite ruleの左辺でquantifyしたequality constraintを報告します。 以前のcompilerが受け付けていた形式でも、生成されたruleが実際には発火できない構成が対象です。 9.14では該当するruleを落とし、warningによって無効な最適化の指定を見える形にします。 programの型検査を通った事実と、最適化のruleが呼び出しへ一致する事実を、同じ結果として扱わない診断です(RULESのequalityに関する変更)。
UNPACKを付けたconstructor fieldをunpackできない場合の診断には、専用の制御名が加わりました。 -Wunusable-unpack-pragmasで有効にし、-Wno-unusable-unpack-pragmasで無効にできます。 -Werror=unusable-unpack-pragmasなら、期待した格納方式へ変換できなかったbuildを失敗にできます。 従来の無条件なwarningを、ほかのwarningと同じ粒度で制御できるようにした変更です。
code coverageを有効にする-fhpcでは、record fieldを参照する記法の扱いが変わります。 RecordWildCardsやNamedFieldPunsによってfieldを取り出した場合も、生成されたselectorを使う参照と同じようにcoverageへ反映します。 radius valueと明示しなかったというsourceの違いだけで、fieldの利用を未実行として扱う問題へ対応します。 同じfield参照を、sourceの表記によらずcoverageの記録対象へ揃える変更です(field参照とcoverage)。
warningの有効化とerror化は別の指定です。 -Wallは対象のwarning群を選び、-Werrorは有効なwarningをbuildの失敗へ変えます。 部分的なselectorの診断を-Wallへ追加すると、sourceを変更していないlibraryも失敗し始めます。 選択したwarning groupの内容が増えるためです。 warning名を個別に扱う設定なら、変更された診断だけをerror化したり、既存のほかの診断を維持したりできます(warning groupとseverityの指定)。
GHCiは複数のhome unitを読み込める
GHCのhome unitは、利用中のprojectでsourceから扱う単位です。 すでにinstallされたpackageを参照するだけの状態と、sourceを変更しながら同じsessionで読み直す状態は異なります。 9.14のGHCiは、複数のhome unitを扱えます。 複数packageから成るprojectで、片方を固定したinstall済みの依存としてしか扱えなかった境界を広げる変更です(GHCiのmultiple home units)。
同名のmoduleでも、所属先のunitは異なり得ます。 複数unitを扱うcompiler APIでは、moduleの綴りだけをkeyにして全projectを1つの表へまとめると、区別する情報を失います。 home packageとunit graphを管理するAPIの変更は、複数のhome unitを扱うGHCiや開発toolにも関わります。 REPLへsourceを読み込む機能と、sourceの所属単位を追うcompiler内部の管理が接続しています。
:infoは、意味上必要な@ binderを宣言の表示へ残すようになりました。 表示を短くするために型の束縛を落とすと、見えた宣言を再利用した際に、元と同じkindや束縛順序にならない場合があります。 型を表示する入口では、読みやすさのための省略と、意味を変える省略を分ける必要があります。 9.14は、@ binderを含む型を扱う際の表示情報を修正しています(GHCiのinfo表示)。
scaleの型と式の評価は、GHCiでも表示できます。
ghci> :info scale
scale :: Num a => Bool -> a -> a
ghci> scale @Int False 3
4
GHCiで式を評価する処理は、-O2でnative executable向けのCoreを最適化する処理とは異なります。 SPECIALISEで分岐が消える条件は最適化とrewrite ruleにあり、GHCiの返り値が4である条件とは分けて扱います。
debuggerはdoのstatementへ停止点を置く
GHCiのdebuggerでは、doの各statementがbreakpointの対象になります。 複数の式を組み合わせたsourceでも、実行中のstatementを追う位置が増えます。 functionへの入口に加えて、内部のstatementごとに実行を停止できます。 実行するIOの順序を変更する機能ではなく、停止点の配置をsourceのstatementへ対応させています(GHCi debuggerの変更)。
新しい:stepoutは、停止中のfunctionやbindingから外へ進むcommandです。 内部の式を1個ずつ追う操作とは異なり、呼び出しの境界から外へ戻るところを選びます。 9.14ではtechnology previewとして追加されているため、GHCiのほかの全commandと同じ実装成熟度を示す記録ではありません。 :steplocalにも性能改善が入り、外部debuggerとの統合へ向けたAPIの整理も含まれています。
bytecodeのbreakpoint indexは32 bitになりました。 大きいmoduleへ多数の停止点を付ける場合にも、少ないindex幅を前提とした上限へ早く到達しないための変更です。 multi-threadを扱うdebuggerへ向けた内部refactoringも入っていますが、単にindex幅を増やしたことでthread間の実行関係が完成するわけではありません。
GHCi自身のerrorやwarningにもnumericな診断番号が付きます。 型検査の診断を文字列の全文で分類するtoolでは、表記や文言の変化が識別方法へ影響します。 番号を含む出力を使えば、説明文の綴りと診断の種類を別に扱えます。 compiler本体の診断と、対話的なcommandの診断を、同じmessage本文だけで一括解析する必要を減らす変更です。
native codeとWebAssemblyで異なる追加
x86向けnative code generatorは、128-bitの整数SIMD operationを追加しました。 shuffleFloatX4#とshuffleDoubleX2#は、-mavxを要求しなくなっています。 浮動小数点vectorのshuffleに使う命令の条件と、整数vectorの演算の追加は、対象のoperationが異なります。 命令の変更対象はx86のnative code generatorで、arm64向けの命令生成は含みません(native code generatorの変更)。
LoongArch向けには、native code generatorの初期対応が入りました。 あるarchitectureのcodeを生成できる段階と、すべてのOS、ABI、package構成で配布物が揃う段階は異なります。 9.14の記録は初期対応であり、既存の全libraryが同じ条件で動くという互換性の保証へ拡大できません。 利用するtarget向けのcompilerとruntimeを持つことが、生成先を選ぶ際の前提になります。
WebAssembly向けのGHCでは、interpreterを通じた評価が追加されています。 GHCiの対話的な評価と、Template Haskellの評価を、Wasm側の構成でも扱う変更です。 foreign import javascriptも関わるため、browser内のJavaScriptやDOMへ接続する評価を表せます。 完成したWasm moduleを起動するだけでなく、codeを読み込みながら評価する工程へ対応範囲を広げています(Wasm interpreterの追加)。
GHCは、単一の配布物がすべてのtargetへcodeを生成するcompilerではありません。 通常のnative向けghcへoptionを追加するだけで、同じ配布物からwasm32-wasiを生成する構成にはなりません。 Wasm向けのGHCでは、wasm32-wasi-ghcなどのtargetを含むcommandを使います。 interpreterの追加は、通常のmacOS向けcompilerにWasm出力を切り替えるoptionを加えた変更ではありません。 Wasm用のtoolchainが独立しています(GHCのWasm向け配布とcommand)。
Wasm側の修正には、dynamic linkerのGOT.func、GHCi初期化時のsetKeepCAFs()、JSFFIの初期化処理が含まれます。 GOT.funcの先行宣言を扱う処理は、後から解決するfunction参照の位置へ関わります。 JSFFIの初期化用constructorが利用者のmainと衝突しない修正もあり、programの入口と補助処理の入口を区別しています。 同じ「Wasm対応」でも、名前の解決、初期化、対話評価に関する変更が別に含まれています。
runtime linker、共有するthunk、heapの予約
runtime linkerには--optimistic-linkingが追加されました。 未解決のsymbolがあってもlink処理を継続する指定で、既定では有効になりません。 symbolの実体を自動で生成したり、参照を別のfunctionへ置き換えたりする機能ではありません。 未解決の参照を残して進める動作と、解決済みのprogramを作れた動作には、成立している条件の差があります(optimistic linkingの追加)。
RTS optionは、programの通常の引数とは別に処理します。 link時に許可した設定に従い、実行時の+RTSから-RTSの間をruntime自身が読み取ります。 GHCiやcompilerのoptionと、完成したprogramのruntime optionを混ぜると、指定を読むprocessが変わります。 9.14のlinker optionを使う場合にも、sourceの最適化flagではなく、runtime linkerへ作用する指定として位置付けます(RTS optionの受け渡し)。
black holeは、別のthreadが評価中のthunkを共有する際の状態に関わります。 複数threadが同じ遅延計算へ到達すると、評価を担当するthreadと、完了を待つthreadをruntimeが調整します。 9.14には、eager black holeの変更を記録する処理と、評価を担当しようと競合するthreadのassertionの修正が入りました。 利用者のsourceに明示的な共有変数がなくても、共有したthunkの評価がruntimeの同期へ接続するためです(black holeの修正issue #26495)。
threadPausedには、black holeで停止していたthreadへの通知を失う問題の修正があります。 評価中のthunkが完了しても、待機していたthreadを再開する通知を失えば、処理は進みません。 値の計算結果を間違える問題とは異なり、計算を待つ側が再開できるかに影響します。 9.14の修正は、共有する計算の完了と、待っていたthreadへの通知を接続する経路を対象にしています(lost wakeupのissue #26324)。
POSIXのheap予約には、OSが低いmemory addressを繰り返し返した場合の無限loopの修正が含まれます。 OSがheap予約へ返す領域と、runtimeが利用できるaddressの条件が合わない場合は、予約要求をやり直します。 やり直しのたびに同じ条件へ戻る構成では、予約処理が完了しません。 programが要求した値の計算を始める前の、領域を確保する処理へ関わる修正です(heap予約のissue #26151)。
Windowsのruntime linkerは、DLLのpath文字列を複製してからcacheへ保存します。 一時的な文字列の領域を参照したままcacheへ保存すると、領域の寿命が切れた後にもcacheがpointerを持ち続けます。 後から同じDLLを探す処理がcacheへ到達すると、保存した文字列を安全に読む条件が失われます。 9.14では、cacheの寿命へ合う文字列を保持して、解放後の領域を参照する経路を修正しています(DLL pathのissue #26613)。
non-moving GCでは、WHITEHOLE closureを走査する処理も修正されました。 objectを移動しないcollectorでも、closureの評価状態が変わる途中の表現を正しく扱う必要があります。 移動しない性質から、並行して変わる状態の扱いまで不要になるわけではありません。 9.14には、collectorが走査する状態と、thunkの評価を調整する状態に関する修正が同じruntimeの変更として含まれています(non-moving GCのissue #26204)。
同梱libraryと、compiler APIを使うtoolの移行
GHC 9.14.1にはbase 4.22.0.0が同梱されています。 同梱されたbaseのversionは、package databaseから取得できます。
ghc-pkg field base version
# => version: 4.22.0.0
baseはUnicode 17.0.0へ更新され、文字の分類などが参照するUnicodeの版も変わります。 compilerの構文だけを更新したつもりでも、同梱libraryが持つ文字データは従来版のままではありません(baseの更新と同梱version)。
GHC.Extsからは、安定しないheapの表現に関する詳細が取り除かれました。 runtimeの内部状態を調べるtoolでは、公開名が残り続けることと、内部表現が安定した契約であることを区別する必要があります。 9.14のlibrary構成では、heapを調べるためのghc-heapにも専用の取得functionが追加されています。 closureの情報やpointer argumentを読む操作を、部分的なrecord selectorへの依存から分離するAPIです。
追加された名前にはgetClosureInfoTbl_maybeとgetClosureInfoTblがあります。 pointer argumentにはgetClosurePtrArgsとgetClosurePtrArgs_maybeが加わりました。 名前のmaybeは、取得する情報が存在しない経路を返り値で表す入口です。 内部のclosure全種類に同じfieldがあると仮定してselectorを呼ぶ構成より、取得できない条件をAPIへ含められます(ghc-heapの追加API)。
ghc-primは、primitive operationや型を公開するlegacy interfaceという位置付けになりました。 primitive operationと型は、ghc-experimentalからも公開されます。 primitive名を使うcodeは、compilerのversionへ密接に結び付くため、通常のbaseの安定したAPIと同じ移行範囲にはなりません。 9.14では、compiler内部の機能を公開するpackageの境界を整理しています。
annotateStack#は、call stackへ任意のdataを押し込み、後からstack traceをdecodeする処理で取り出すためのprimopです。 単に現在のfunction名を文字列として保存する機能より、利用者が持つdataをstack上の文脈へ関連付ける入口になります。 追加したdataを後から読む処理まで、stackをdecodeする側の経路として必要です。 primopを呼ぶだけで、すべての例外表示へ任意のdataが自動で整形されるというAPIではありません(annotateStack#のissue #26218)。
GHC.RTS.FlagsとGHC.Statsに対応するexperimentalなmoduleも追加されています。 公開名はGHC.RTS.Flags.ExperimentalとGHC.Stats.Experimentalです。 base側にも従来のmoduleは存在しますが、packageを分割する計画に伴い、experimental側へ移す対象として説明されています。 module名が変わる移行と、runtime統計の値を読む処理の意味が変わる移行を、同じ変更としては扱っていません(ghc-experimentalの公開module)。
HomePackageTableの更新はIOへ移る
compilerをlibraryとして使うtoolでは、HomePackageTableの変更が直接影響します。 9.14の表はIORefを使う構成になり、作成や要素の追加を含む多くのoperationがIOへ移りました。 以前のpureな値の組み立てとして呼んでいた処理を、単にimport元だけ変えて使う移行にはなりません。 tableの状態を更新する作用を、呼び出すfunctionの型と実行順序へ反映する必要があります(HPTとHUGのAPI変更)。
emptyHomePackageTableやaddHomeModInfoToHptも、IOで扱う名前として説明されています。 表へ要素を足す処理が作用になれば、表を共有するsessionが持つ状態を、pureなsnapshotと同じようには扱えません。 呼び出す順序、参照する表、更新後に読む処理の位置が、tool側の実装へ現れます。 変更の理由には、従来の表の構成で起きていたmemory leakを防ぐことが挙げられています。
UnitEnvGraphと関連operationのimport元は、GHC.Unit.Home.Graphへ移りました。 hugEltsは削除され、unitのkeyが必要ならallUnitsを使う構成です。 要素を処理する用途では、traverseやunitEnv_foldWithKeyを使う経路になります。 削除した名前を1つの新しい名前へ機械的に置換するだけではなく、keyを読む用途と、値を処理する用途で対応先が分かれます。
診断を包むUnknownDiagnosticには、hintの型を表すtype argumentと、hintを包む値の引数が増えました。 error本文だけを包むtoolの実装では、inner diagnosticが持つhintの情報も扱う必要があります。 syntax treeでは、parallel statementのblockを表すlistがNonEmptyへ変わる変更もあります。 空を許していた容器から空を作れない容器へ型が変わるため、ASTを構築するtoolでは、生成する要素数の条件も更新対象です。
Template Haskellでは、SpecialisePに代わるSpecialiseEPが追加されました。 expressionを先頭に持つpragmaへ対応する変更であり、従来のfunction名だけを包む形より、表す構文が広くなります。 helperもpragSpecEDとpragSpecInlEDへ移ります。 sourceで新しいSPECIALISEを書ける変更と、sourceを生成・解析するlibraryの表現の変更が、同じ機能追加の両側へ反映されています(template-haskellの変更)。
同梱versionでは、template-haskellが2.24.0.0、ghc-primが0.13.1です。 ghc-experimentalとghc-internalは9.1401.0になっています。 CabalとCabal-syntaxは3.16.0.0で、同梱するlibraryのversionと、利用者が別にinstallしたcabal commandのversionは同じ対象ではありません。 同梱packageのversionは、compilerの配布物に対応します。 dependencyを解決するCabalのversionも、別の入力としてbuildへ関わります(同梱package一覧)。
9.14.2-rc2で追加修正される経路
9.14.2-rc2は、9.14.1に新しい言語機能を一括して追加する別系列のreleaseではありません。 9.14.2へ向けた2番目のcandidateで、specialiser、demand analyser、runtimeなどの修正を含みます。 2026年9月17日の公開記録では、不正な結果、crash、停止しない処理へつながる複数のmiscompilationの修正を挙げています。 対象は式の専用化とruntimeの実行経路で、9.14.1に対する型検査の成功だけでは当該不具合を除けません(9.14.2-rc2の修正記録)。
specialiserとdemand analyserの不具合は、同じHaskellの式から生成するcodeの意味へ関わります。 sourceの型が正しくても、最適化したbodyが元の評価と同じ結果を返さなければ、programの動作は壊れます。 型検査へ新しい制約を追加する修正と、不正な最適化を修正する変更は、失敗する段階が異なります。 candidateの公開記録では、複数のissueを生成結果の誤りに関する修正としてまとめています。
parallel GCには、必要なmemory barrierが不足していた問題の修正があります。 AArch64のような弱いmemory orderingのarchitectureでは、別threadが読み取れる書き込みの順序が、runtimeの前提と一致しない場合があります。 collectorが正しい状態を読むための同期が不足すれば、heapの破損やcrashにつながります。 修正は、collector間のmemory orderingへ必要なbarrierを追加します。 適用先は型検査の規則ではなく、runtimeの同期処理です(memory barrierのissue #27477)。
9.14.1の新構文を使うlibraryでは、必要な拡張、warningのgroup、compiler内部のAPIを依存の境界として持ちます。 9.14.2へ向けた修正は、同じsourceを最適化して動かす際のcode生成やruntimeの経路へ重なります。 したがって、同じ9.14系列でも、sourceの移行で変える宣言と、compilerを更新して受け取る修正は別の作業です。 式の専用化、stage別の依存、recordの型と更新規則をsourceへ反映したうえで、利用したcompilerの正確な版が生成物の条件になります。