この節では、Haskell 98およびHaskell 2010の実装についてのGlasgow Haskellの問題点を列挙する。クラッシュ、スペースリークなどの良くない現象については「うまくいかないとき」の節(第11章. 何かがうまくいかないとき)も参照せよ。
この制限一覧は、(大体)Haskellレポートの順で並んでいる。
デフォルトではGHCは(大部分)Haskell 2010のコンパイラのように振る舞おうとするが、-XHaskell98や-XHaskell2010フラグを使うことで特定の言語バージョンのように振る舞うようにすることもできる。標準からの逸脱は以下に記述する。特に述べていない場合、逸脱はHaskell 98、Haskell 2010、デフォルトの各モードに関するものである。
GHCでは、修飾された識別子に関して、ある種の字句規則がHaskellレポートのものとわずかに異なっている。module.reservedopという形のもの、例えばM.\があったとき、Mと.\という二つの字句要素ではなく、一つの修飾された演算子と解釈される。
Haskell 98モードおよびデフォルトモード(Haskell 2010モードはそうでない)において、GHCは、do式でのレイアウト規則について、多少寛容である。具体的には、「入れ子の内側の文脈は、外側の文脈よりも多くインデントされなければならない」という規則を緩和して、外側の文脈がdoの場合、内側の文脈が外側の文脈と同じ水準でも良いとする。
例えば、GHCは以下のコードを受け付ける。
main = do args <- getArgs
if null args then return [] else do
ps <- mapM process args
mapM print ps
この振る舞いはNondecreasingIndentation拡張によって制御される。
Haskell 98は(Haskell 2010は違う)パース中に式の中で結合性の解決を行なうことを要求するが、GHCはこれを行なわない。例えば、Haskell レポートによれば、以下のものは合法的なHaskellである。
let x = 42 in x == 42 == Trueそして、これは以下のようにパースされるとする。
(let x = 42 in x == 42) == True
これは、レポートによれば、let式は「できるだけ右へ拡張される」からである。二番目の等号を越えて拡張しようとするとパースエラーが発生する(==は非結合的なので)ため、let式はそこで終わる。GHCは単純に式全部を飲み込む。パースは次のようになる。
(let x = 42 in x == 42 == True)デフォルトのモードでは、GHCはある種のプログラムの定義され度合いを本来よりも僅かに大きくする。例として以下を考えよ。
f :: [a] -> b -> b
f [] = error "urk"
f (x:xs) = \v -> v
main = print (f [] `seq` True)
これはerrorを呼ぶべきだが、実際にはTrueを表示する。理由: GHCはfを以下のようにη展開する。
f :: [a] -> b -> b
f [] v = error "urk"
f (x:xs) v = v
大部分のプログラムについては、これは僅かだが有意に効率を改善する。これが害になるようなプログラムは少ない。この偽の「最適化」を無効にするには-fpedantic-bottomsを使う。
データ型の文脈は、次の版の言語標準では取り除かれることが決まったので、デフォルトモードにおいてGHCはこれを受け付けない。この振る舞いはDatatypeContexts拡張で制御できる。7.4.2. データ型文脈を見よ。
4.7.9. 相互再帰的なモジュールをコンパイルするにはにある通り、相互再帰的なモジュールのループがある場合、それをhs-bootファイルを使って断つことをGHCは要求する。これはバグと言うより不幸な出来事である。Haskellレポートは次のように述べている(5.7節、和訳)。「使うHaskell処理系によっては、相互再帰的なモジュールを分割コンパイルするとき、インポートされるモジュールに追加の情報を持たせ、そのモジュールがコンパイルされる前に参照できるようにすることが要求されるかもしれない。相互再帰に対処するため、エクスポートされる値全てについて、明示的な型シグネチャが必要とされるかもしれない。分割コンパイルの精密な詳細はこのレポートでは定義しない。」
Numクラスは、スーパークラスとしてShowおよびEqを持たない。
以下のようにすることで、Haskell98/Haskell2010とGHCの両方で動くコードを作ることができる。
Numのインスタンスを作るときは必ず、ShowとEqのインスタンスにもする。
Num tという制約を持つ関数またはインスタンスまたはクラスを与える場合は必ず、Show tとEq t制約も与える。
BitsクラスはスーパークラスとしてNumを持たない。従ってbit、testBit、popCountのデフォルトメソッドを持たない。
以下のようにすることで、Haskell2010とGHCの両方で動くコードを作ることができる。
型をBitsのインスタンスにするときは常にNumのインスタンスにもする。
Bits t制約を持つ関数、インスタンス、クラスを与えるときは常に、Num t制約も持たせる。
Bitsのインスタンスでは、常にbit、testBit、popCountの各メソッドを定義する。
以下のインスタンスが追加で定義される。
instance Functor ((->) r) instance Monad ((->) r) instance Functor ((,) a) instance Functor (Either a) instance Monad (Either e)
以下のコード断片は致命的エラーを出すべきだが、実際にはそうならない。
main = print (array (1,1) [(1,2), (1,3)])
GHCのarrayの実装では、配列要素の値はリスト中に最後に現れた(index,value)の対から採られ、重複は検査されない。これは純粋単純に効率上の理由からである。
Preludeサポートに関することタプルの大きさは現在100までに制限されている。ただし、タプルに関する標準インスタンス(Eq、Ord、Bounded、Ix Read、Show)は16要素のタプルまでについてしか使えない。
この制限は簡単に覆せるので、これで困ることがあったら教えてほしい。
Read整数型に関するReadクラスのGHCでの実装は、十六進と八進のリテラルにも対応している。(Haskell 98レポート中のコードは対応していない)。従って、GHCでは以下のものが動作する。
read "0xf00" :: Int
こうなっている理由としては、readLitCharが十六進や八進のエスケープを受け付けるのに、整数についてそうしないのは整合性に欠けるというのが考えられる。
isAlphaHaskell 98でのisAlphaの定義は以下のようになっている。
isAlpha c = isUpper c || isLower c
GHCでの実装では、Unicodeのアルファベット文字のうち、小文字でも大文字でもない文字についても、isAlphaはこれをアルファベットだとみなすが、この点でHaskell 98から逸脱している。
hGetContents遅延I/Oにおいてエラーが発生した場合、Haskell 98の仕様ではそれを捨てることになっている(Haskell 98レポートの21.2.2節を見よ)が、それに反して例外が投げられる。投げられるのは、原因となったIO操作がIOモナド中で実行された場合に投げられるであろう通常のIO例外であり、System.IO.Error.catchかControl.Exception.catchで捕捉できる。
hs_exit()の後にhs_init()を呼ぶことが許されないFFI仕様の要求するところによると、実装は、hs_exit()によって停止した後に再び初期化を行うのに対応していなければならないが、GHCは現在これを行えない。
この節では、Haskell 98で未定義、または実装固有とされている諸問題について、GHCの立場を解説する。
Char
ISO-10646標準に従って、GHCでのmaxBound :: Charは0x10FFFFである。
GHCでは、Int型の精度はホストアーキテクチャでのアドレスの大きさと同じである。言い替えると、32ビットの機械では32ビットだし、64ビットの機械では64ビットである。
Intの数値演算はオーバーフローの検査を行わない。従って、Intの全ての演算は2nを法として行われる。ただしnはInt型のビット数である。
fromInteger関数(従ってfromIntegralも)は、Intに変換する時は特別は場合である。fromIntegral x :: Intの値は、まず(abs x)の下位nビットを採り、xの符号を掛ける(nビットの2の補数演算で)ことで得られる。この振る舞いは、0xffffffff :: Intと書いた場合に、結果のIntにビットパターンが保存されるように選ばれた。
例えば-3のような負のリテラルは、Haskellレポートによれば(注意深く読めば)、Prelude.negate (Prelude.fromInteger 3)を意味する。従って-2147483648はnegate (fromInteger 2147483648)である。fromIntegerは表現の下位32ビットを採るので、fromInteger (2147483648::Integer)を型をIntとして計算すると-2147483648::Intになる。次にnegate演算はオーバーフローするが、これは検査されないので、negate (-2147483648::Int)は単に-2147483648になる。まとめると、minBound::Intをリテラルとして書いた場合、予期した通りの意味になる。(しかしこれは一般には保証されていない。)
fromIntegral関数は、大きさ固定の整数型(Int8、Int16、Int32、Int64、および対応する符号なしのWord系の型)間で変換するときも、ビットパターンを保存する。ライブラリ説明書中のData.IntとData.Wordモジュールを見よ。
FloatとDoubleの数値の演算は、オーバーフロー、アンダーフロー、その他の悲しい現象について検査されない。(ただし、アーキテクチャによっては、浮動小数オーバーフローや精度の損失を捉え、浮動小数例外として報告する場合がある。この場合おそらくプログラムは終了させられる)
hs_exit()の後にhs_init()を呼ぶことが許されないFFI仕様の要求するところによると、実装は、hs_exit()によって停止した後に再び初期化を行うのに対応していなければならないが、GHCは現在これを行えない。