この節では、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
が十六進や八進のエスケープを受け付けるのに、整数についてそうしないのは整合性に欠けるというのが考えられる。
isAlpha
Haskell 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は現在これを行えない。