jhcユーザーズマニュアル日本語訳 (2013/03/16更新)

Posted on January 12, 2013 / Tags: haskell, jhc, specification, translation

Table of contents


アラフラで採用する予定のjhc、おおざっぱな使い方はわかっているのでゲソが細かいところは知らんでゲソ。 気になるのはGHCとの違いでゲソが、内部解析のための足掛かりになるかもしれないので、使い方はしっかりマスターしておきたいでゲッソ!

しかし前々回の記事で紹介した Jhc User’s Manual は当然のごとく英語…自然言語さえ苦手なワシには読めんでゲソ。 そこで根性で日本語訳してみたでゲッソ! 1 ざんねんながらワシには専門知識が圧倒的に欠如しているので、用語のミスや根本的な間違いなどを見つけたら是非教えて欲しいでゲソ。 この記事は翻訳の修正をするたびに随時更新するつもりでゲソ。

この翻訳には po4a を使っていて、gettextの翻訳データは po/ja.po - jhc-arafura in Metasepi - Gitorious にあるので、po4aとgitを使える人はpull requestを送ってくれるのも歓迎でゲッソ!

2013/03/16追記: Metasepi向けのjhcの開発は Ajhc で行なうことになったでゲソ。 Ajhcで追加された機能は Ajhc User’s Manual (日本語訳) を参照のこと、でゲッソ!


Jhcユーザーズマニュアル - John Meacham

使い方

プロジェクトをビルドする

Jhcはソースコードの依存関係を独自に解析してくれるため、 コマンドラインから’main’関数を含んだファイルを指定するだけでソースコード全体をコンパイルできる。 例えばソースコード’HelloWorld.hs’をコンパイルしたければ、次のようにコンパイルすれば’hello’という名前の実行バイナリが生成される。

; jhc HelloWorld.hs -o hello

Jhcはサーチパスからモジュールを探す。 そのサーチパスは既定値ではカレントディレクトリである。 モジュールは名前にもとづいて検索される。 例えばData.Fooモジュールは’Data/Foo.hs’もしくは’Data.Foo.hs’のパスから探されることになる。 このサーチパスは’-i’コマンドラインオプションを使って指定できる。 また、’JHC_PATH’環境変数を使って指定することもできる。

ライブラリを使う

jhcライブラリは、例えば’base-1.0.hl’のような、’hl’という拡張子を持つ単一のファイルである。 何かHaskellライブラリを使うためには、jhcの検索するディレクトリにこのファイルを置くだけで良い。 例えば $HOME/lib/jhc などだ。 JHC_LIBRARY_PATH環境変数を定義することで、別の位置をライブラリのサーチパスとして指定することができる。 また、-Lコマンドラインオプションを使ってもサーチパスを指定できる。 さらに-L-オプションを使えばサーチパスの設定を既定値に戻すことさえできる。

‘hl’ファイルが適切な位置に置かれていれば、’-p’コマンドラインオプションを使って使用するライブラリを指定することができる。 例えば’mylibrary-1.0.hl’を既にサーチパスに配置済みであれば、以下のように使うことができる。

; jhc -p mylibrary MyProgram.hs -o myprogram

–list-librariesオプションを使えば全ての使用可能なライブラリのリストを得られる。 さらに’-v’オプションを追加することで、各ライブラリに関する詳細な情報を得ることもできる。 これらの情報はYAMLフォーマットを使って生成されたものである。

環境変数

Jhcの挙動はいくつかの環境変数によって制御される。

JHC_OPTS : jhcのコマンドライン直後に挿入されるオプション群

JHC_PATH : モジュールの検索パス

JHC_LIBRARY_PATH : ライブラリの検索パス

JHC_CACHE : jhcがキャッシュとして使用するディレクトリ。キャッシュはjhcのパフォーマンス向上にはかかせない。既定値は~/.jhc/cache。

Haskellライブラリをビルドする

ライブラリは–build-hlオプションで指定されたライブラリファイルの内容にもとづいてビルドされる。 このライブラリファイルのフォーマットはYAMLである。

; jhc --build-hl mylibrary.yaml

ライブラリファイルのフォーマット

ライブラリファイルはYAMLフォーマットで、jhcは自分の理解できるフィールドだけを解釈し、残りは無視する。

Name : ライブラリの名前

Version : ライブラリのバージョン。バージョン番号は’-p’コマンドラインオプションを使う際に区別するために使われる。しかし、それ以外の用途にはjhcは使わない。

Exposed-Modules : ライブラリに含まれ、ライブラリの利用者への公開インターフェイスとなる一連のモジュール。この欄が他のライブラリ内のモジュールを含んでいた場合、再度このライブラリでも公開インターフェイス扱いされることになる。

Hidden-Modules : ライブラリ内部で使用されるかもしれないが、公開インターフェイスにはならない一連のモジュール。jhcはこの情報を元にして最適化を行なう。このリストがライブラリのビルドに網羅的でなければ、jhcはワーニングメッセージを出力する。

Extensions : このモジュールをコンパイルするのに必要な拡張のリスト。jhcはできうるかぎりghc拡張と似た拡張を提供するつもりである。

Options : ライブラリビルド時に使うその他のコマンドラインオプション

Build-Depends : ビルドに必要になるライブラリ。’-p’コマンドラインオプションと同じフォーマットで指定する。

Hs-Source-Dirs : Haskellソースコードを探すディレクトリ。‘-i’コマンドラインオプションと異なり、この欄にはライブラリ詳細を記載した.yamlファイルからの相対パスを記載する。’-i’オプションを使う際にはカレントワーキングディレクトリからの相対パスを記載すること。

Include-Dirs : プリプロセッサが’-I’オプションで検索するディレクトリ。このディレクトリもyamlファイルが置かれたディレクトリからの相対パスとして指定すること。

C-Sources : このライブラリを使うプログラムにリンクされるべきC言語ソースコードファイル群。

Include-Sources : このライブラリを使ったプログラムのビルドに必要なC言語のヘッダを指定する。これらのヘッダはビルド時にincludeされることはあっても、実行ファイルにリンクされることはない。

ライブラリファイルの例としてlib/jhc/jhc.yamlとlib/base/base.yamlを参考のこと。

依存情報

jhcは依存情報を出力できる。 この依存情報はソースコードとライブラリがコンパイル時にその他の要素にどのように依存しているかを表現している。 依存情報は–deps name.yamlオプションをjhcに入力するとname.yamlファイルに出力される。 この依存情報もまたYAMLフォーマットで、各項目な以下のような意味を持っている。

deps.yamlを扱うサンプルとして’utils/deps_to_make.prl’を参照のこと。

オプション

Usage: jhc [OPTION...] Main.hs
  -V                --version                 バージョン表示
                    --version-context         バージョン履歴表示
                    --help                    ヘルプメッセージ表示
                    --info                    コンパイラ設定表示
                    --purge-cache             コンパイルキャッッシュの削除
  -v                --verbose                 stderrに冗長なメッセージ表示
  -z                                          冗長な統計情報を表示
  -d [no-]flag                                コンパイルパイプラインの特定の情報をダンプ
  -f [no-]flag                                コンパイルフラグの有効/無効
  -X ExtensionName                            言語拡張の有効化
  -o FILE           --output=FILE             出力ファイル名を指定
  -i DIR            --include=DIR             ソースファイル検索パス
  -I DIR                                      プリプロセッサのインクルードパスを追加
  -D NAME=VALUE                               プリプロセッサに渡すdefineを追加
                    --optc=option             Cコンパイラに渡すその他のオプション
  -c                                          モジュールを単体でコンパイル
  -C                                          C言語コードへコンパイル
  -E                                          ソースコードをプリプロセッサにかけてstdoutへ出力
  -k                --keepgoing               エラーを無視してコンパイルを続行
                    --cross                   クロスコンパイルを有効に、-mオプションでターゲットを指定すること
                    --stop=parse/typecheck/c  parse/typecheck/cの直後で停止する
                    --width=COLUMNS           デバッグ出力のスクリーン幅を変更
                    --main=Main.main          mainエントリポイントを指定
  -m arch           --arch=arch               クロスコンパイルターゲットを指定
                    --entry=<expr>            mainエントリポイントを式を使って指定
                    --show-ho=file.ho         hoファイルの概要をダンプ
                    --noauto                  haskell98パッケージを自動的に読み込まない
  -p package                                  指定したライブラリを使用する
  -L path                                     ライブラリを指定したパスから検索する
                    --build-hl=desc.yaml      指定したライブラリファイルからHaskellライブラリをビルド
                    --annotate-source=<dir>   指定したディレクトリにプリプロセス済みソースコードを出力
                    --deps=<file.yaml>        依存情報を指定したファイルに書き込む
                    --interactive             インタラクティブ実行                                                     (デバッグ用途)
                    --ignore-cache            コンパイルキャッシュを無視する
                    --readonly-cache          コンパイルキャッシュに追加情報を書き込まない
                    --no-cache                コンパイルキャッシュを使わず、書き込みも行なわない
                    --cache-dir=JHC_CACHE     指定したディレクトリをコンパイルキャッシュとして使う
                    --stale=Module            指定したモジュールがコンパイルキャッシュにあっても古い情報として扱う
                    --list-libraries          インストール済みライブラリを表示
                    --tdir=dir/               中間生成物を出力するディレクトリを指定する

-dオプションの引数: '-d help'オプションで詳細
    all-types, aspats, atom, bindgroups, boxy-steps, c, class, class-summary, core, core-afterlift
    core-beforelift, core-initial, core-mangled, core-mini, core-pass, core-steps, datatable
    datatable-builtin, dcons, decls, defs, derived, e-alias, e-info, e-size, e-verbose, exports, grin
    grin-datalog, grin-final, grin-graph, grin-initial, grin-normalized, grin-posteval, grin-preeval
    imports, ini, instance, kind, kind-steps, optimization-stats, parsed, preprocessed, program
    progress, renamed, rules, rules-spec, scc-modules, sigenv, srcsigs, stats, steps, tags, the
    types, verbose, veryverbose

-fオプションの引数: '-f help'オプションで詳細
    bang-patterns, boehm, controlled, cpp, debug, default, defaulting, exists, ffi, forall, full-int
    glasgow-exts, global-optimize, inline-pragmas, jgc, lint, m4, monomorphism-restriction, negate
    prelude, profile, raw, rules, standalone, type-analysis, type-families, unboxed-tuples
    unboxed-values, user-kinds, wrapper

コードオプション

jhcの解釈やコンパイルは’-f’フラグでコントロールできる。 これらのオプションを以下に列挙する。また、その効力は’no-’をフラグ名に付けることで無効化できる。

コードオプション
bang-patterns - バン!パターン(強制正格評価パターン)
cpp HaskellコードをCプリプロセッサにかけてからコンパイル
exists - existsキーワードを存在型の解釈に使う
ffi 他言語関数インタフェース(FFI)を使う
forall - forallキーワードをランクN多相と明白な量化に用いる
m4 Haskellコードをm4プリプロセッサにかけてからコンパイル
prelude Preludeを暗黙の内にimportする
type-families Type Families(型族)を使う
unboxed-tuples アンボックス化タプル文法を許容する
unboxed-values アンボックス化値文法を許容する
user-kinds ユーザ定義の種を使う
型検査
defaulting 型のデフォルト化を行なう
monomorphism-restriction 単相性制限を強制する
デバッグ
lint より多くの型検査を行なう
最適化オプション
global-optimize Eによってプログラム全体を最適化する
inline-pragmas inlineプラグマを使う
rules rulesプラグマを使う
type-analysis メソッド生成直後に型に対する基本的なpoints-to analysisを適用する
コード生成
boehm Boehm GCを使う
debug 実行バイナリ中のデバッグコードを有効に
full-int 32bitマシンでIntとWordを32bitに拡張する
jgc jgcガーベッジコレクタを使う
profile 実行バイナリ中のプロファイルコードを有効に
raw main関数をWHNFとして評価する
standalone 単独実行可能にコンパイル
wrapper main関数を例外ハンドラで包む
設定既定値
default inline-pragmas rules wrapper defaulting type-analysis monomorphism-restriction global-optimize full-int prelude
glasgow-exts forall ffi unboxed-tuples

デバッグ情報のダンプ

‘-d’フラグ付きでjhcを起動することで各種パラメータを出力できる。 次に挙げるのは’-d’フラグに渡すことができるパラメータのリストである。

フロントエンド
defs モジュール内で定義された名前を列挙する(?)
derived 自動導出されたインスタンスを表示する
exports モジュールからエクスポートされている名前を列挙する
imports モジュールがインポートしている名前を列挙する
ini iniファイルのオプションを表示
parsed パース済みコードを出力
preprocessed プリプロセス後のコードを出力
renamed renaming後のコードを出力
scc-modules 強結合したモジュールを依存度順に表示
型検査
all-types 型検査完了後に型テーブルを表示
aspats アズパターンを表示
bindgroups 束縛グループを表示
boxy-steps 型推論の挙動をステップバイステップで表示
class 個々の型クラスの詳細情報を表示
class-summary 型クラス群の概要を表示
dcons データコンストラクタを表示
decls 処理済みの宣言を表示
instance インスタンスを表示
kind モジュールへの種推論の結果を表示
kind-steps 種推論のステップを表示
program プログラム全体の構造
sigenv 型シグニチャ初期状態の表示
srcsigs renaming後の型シグニチャを表示
types 定義された全ての名前を含んでいる型テーブルを表示
中間コード
core core言語コードを表示
core-afterlift hoファイルに書き込む直前のcoreコードを表示
core-beforelift lambda lifting直前のcoreコードを表示
core-initial E.FromHs転換直前のcoreコードを表示
core-mangled grinにかける前の型なしcoreコードを表示
core-mini 個々の関数の最適化の詳細を表示
core-pass コード変形の経過を表示
core-steps コンパイルパスそれぞれのステップについて表示
datatable コンストラクタのデータテーブルを表示
datatable-builtin ビルドイン型のデータテーブルを表示
e-alias 展開済みエイリアスを表示
e-info 束縛された値のタグ情報を表示
e-size コンパイルパスそれぞれでのEのサイズを表示
e-verbose Eコードに冗長なメッセージを表示させる
optimization-stats 最適化パスのスタティクスを表示
rules ユーザ定義の書き換え規則を表示する
rules-spec 特殊化書き換え規則を表示する
Grinコード
grin grinコードを表示する
grin-datalog データベースに適したフォーマットでgrinの情報を印字する
grin-final C言語への変換直前のgrinコードを表示
grin-graph 最終的なgrinコードをdotファイルをoutputname_grin.dotに出力する
grin-initial core言語から変換された直後のgrinコード
grin-normalized 最初の正規化を行なった直後のgrinコード
grin-posteval eval/applyインライン化直後のgrinコード
grin-preeval eval/applyインライン化直前のgrinコード
steps インタープリタの実行を表示
tags タグと型を列挙する
バックエンドコード
c コンパイル時に生成したC言語ソースファイルを削除しない
内部
atom 終了時にatomテーブルをダンプする
一般
progress 通常のプログレス表示を行なう
stats その他の情報を表示する
verbose 冗長なプログレス表示
veryverbose スタティクスを含む冗長なプログレス表示

プラグマ

プラグマは特別なコンパイラへの指示で、コンパイラの挙動に特定の影響を与える。 一般的には、コンパイラは自由に独自のプラグを定義できる。 しかしjhcでは他のコンパイラでも使われているものを実装しようと努めている。 プラグマはソースコード中で {-# PRAGMANAME … #-} のような形式で使われる。

関数の属性

これらのプラグマは関数定義のあるファイルと同じファイル中に記述すべき。 インスタンスやクラスの関数に適用するためには、 これらのプラグマをインスタンスやクラスの宣言の節に配置しなければならない。

プラグマ
NOINLINE core変形中のインライン化を抑制する。当該関数はgrin変形中にインライン化される かもしれない
INLINE 可能であれば関数をインライン化する
SUPERINLINE 関数本体を単に複製することになったとしても、とにかくインライン化を行なう
VCONSTRUCTOR 関数を仮想コンストラクタとして扱う。CPR解析とworker/wrapper変換は関数をコンストラクタとして扱うことがある。これは’NOINLINE’で実装される。

クラスプラグマ

プラグマ
NOETA デフォルトではjhcは最適化のために全てのクラスメソッドをイータ変換する。このプラグマはこの挙動を無効化する

書き換え規則と特殊化

プラグマ
RULES 書き換え規則。GHCの書き換え規則と同様の構文で同様の振舞いをする。ただし、’phase’を使うことはできない。
CATALYST 特殊な書き換え規則で、他のRULESの使用中でのみ発火する。そのためCATALYSTは最良でない中間ステージを要求する最適化を許す。
SPECIALIZE 指定された型で特殊化された関数を生成する
SUPERSPECIALIZE SPECIALIZEと同じ効力がある。しかし一般化された関数にて特殊化された呼び出しかどうかランタイムでの検査を行なう。

型プラグマ

プラグマ
CTYPE FFIでdataもしくはnewtypeを使うたえに、外部の型を指定する。
この型はnewtypeかforeign宣言された型の単項コンストラクタでなければならない。
例えば
~~~
data {-# CTYPE “unsigned short” #-} CUShort = CUShort Word16
~~~

ヘッダプラグマ

これらのプラグマはファイルの’先頭’でのみ有効である。 つまりこれらは最初の’module’宣言の前に置かれなければならず、 ファイルの先頭4096バイト以内に配置されねばらなず、 そしてASCII文字セットのみしか使ってはならない。

OPTIONS_JHC : その他のオプションを指定する。これらのオプションはコマンドラインオプションとしても利用できるが、単一のファイルにのみ影響を与えたい場合に重宝する。

LANGUAGE : 言語拡張のオプションを指定する

拡張

モジュールのサーチパス

他のHaskellコンパイラと同じように、jhcでのモジュールはその名前にもとづいて検索される。 ’Data.Foo’モジュールを検索するとjhcは’Data/Foo.hs’ファイルも検索するが、 ’Data.Foo.hs’も同じく検索対象になる。

FFIへの拡張

複数の返値をともなうforeign import

foreign C importは複数の値を返すことがある。 このような場合、アンボックス化タプルを返値として使う。 最初の返値は関数から直接返された値であり、 残りは関数の引数リストの終端へのポインタが返ってくる。 純粋な(IOではない)関数でのみ複数の値を返値として使うことができる。

-- frexpはC言語のプロトタイプ宣言として
-- double frexp(double x, int *exp);
-- を持つ。そのため、IOとして通常のimportをすることができ、
-- 他の純粋な関数へ渡すにはStorableを使うことになる。

foreign import ccall "math.h frexp" c_frexp :: Double -> Ptr CInt -> IO
Double

-- この拡張を使うと以下のように宣言できる。
foreign import ccall "math.h frexp" c_frexp2 :: Double -> (# Double, CInt #)

-- 二番目の返値は最後の'exp'パラメータによって追加されて、アロケートされた
-- メモリから読み出される。関数に渡されるメモリの内容は未定義である。

呼び出し規約’capi’

呼び出し規約’capi’は’ccall’を使ってたimportのかわりに使われる。 この規約を使うことで、foreignする対象はアドレス空間に実体がなくても良い。 マクロやビルドイン関数もしくはその他コンパイラ独自の機能であっても良い。 jhcはルーチンが決してポインタとしては使用されないこと、依存するヘッダをインクルードすること保証する。 これは’ccall’と異なる。ccallはヘッダがスコープに入っているか、正しい名前のリンカのためのシンボルがエクスポートされているか、なんの保証もしない。

パッケージに関連した依存

p:foo.c や p:foo.h と書かれたforeign importはそのファイルがパッケージ内部実装の一部として解釈されることを意味している。 jhcはそれらのファイルが同じ名前を持っていても他のパッケージと衝突しないことを保証する。 それらのファイルはライブラリファイルのc-filesとc-headersの項目に列挙されるべきである。

import/exportされる名前空間

jhcはimportやexportする際の名前空間を持つ。 これらはimport/export宣言によって制限されたり修正されたりする。

* 'type' - 名前が型シノニムであり、'type'や'newtype','data',種宣言のコンストラクタで定義されている
* 'class' - 名前がクラスであることを指定
* 'data'  - 名前がデータコンストラクタであることを指定
* 'kind'  - 名前がユーザ定義の種であることを指定

型クラスと型シノニムは独立した名前空間にあるので、 同じ名前の型シノニムと型クラスが同じスコープに存在しても問題ない。

ユーザ定義された種

-fuser-kindsオプションが有効な場合、jhcはユーザ定義の種を使用可能にする。 その文法は:

data kind Nat = Z | S Nat

ここでは新しい種’Nat’と、その種に存在する2つの型’Zと’S’を定義している。 これらの型は値を持たないアンボックス化された値として表現された種の宣言によって定義されている。 そのためこれらはランタイムでの表現を持たない。(当然、⊥でさえない)

独立deriving

jhcはある環境下にて独立derivingをサポートする。

ランクN多相性

jhcは高ランク多相性をサポートする。 文脈が高ランク多相型を明確に指定しても、jhcは高ランクの型推論を行なわない。 例えば、型注釈と多相として定義されたデータコンストラクタへの引数を与えれば、正しい多相型が手に入る。

存在型

jhcはファーストスラスの存在型をサポートする。 それは’exists’キーワードで使用できる。 また存在データ型はghcと似た方法でサポートしている。

アンボックス化された値

jhcでのアンボックス化された値はGHCと同様に指定できる。 ただし、# をその識別子としては使用しない。 各種アンボックス化されたコンストラクタのための構文として # はまだ使われているが、 通常のHaskellのルールをがHaskellの識別子に使われる。 コンベンションとして’_’の接尾辞の付いた型はアンボックス化された状態にあることを示している。 アンボックス化タプル以外の全てのアンボックス化された値は、-funboxed-valueフラグによって有効になる。 GHCとの互換性のため、MagicHash拡張でもアンボックス化型を有効にできる。

アンボックス化タプル

jhcはGHCと同じ文法でアンボックス化タプルをサポートする。(# 2, 4 #) は2つの数値を持つアンボックス化タプルである。 アンボックス化タプルは-funboxed-tuplesオプションによって有効にできる。 アンボックス化タプルは種多相であり、ボックス化とアンボックス化の両方おん値を保持することができる。 (しかし他のアンボックス化タプルを保持することはできない)

アンボックス化文字列

アンボックス化文字列は-funboxed-valuesフラグによって有効になる。 通常の文字列と異なり、その名前は’#’で終わる。 アンボックス化文字列は’BitsPtr_’型である。

アンボックス化文字

アンボックス化文字は通常の文字リテラルの後に # を付けて表現する。 アンボックス化文字はChar_型であり、さらにJhc.Prim.Bitsに定義のあるBits32_のnewtypeでもある。

アンボックス化数値

アンボックス化数値は-funboxed-valuesフラグによって有効になる。 それらは 3# や 4# のように’#’を付けて表現する。 もし型が環境によって完全に指定されてて、かつそれが適切なアンボックス化数値型であるなら、jhcは型推論を限定的にサポートする。 そうでない場合にはInt__をデフォルトとして使用する。 型が完全に指定されているかどうかにかかわらず、ランクN型として同じルールが適用される。 アンボックス化数値は列挙可能である。 そのため 0# はアンボックス化されたFalse値として使うことができる。 そして型推論も行なわれる。

アンボックス化された値の取り扱い

アンボックス化値を取り扱うために、適切なプリミティブの演算子をスコープ内に用意する必要がある。 プリミティブのimportのために特殊なFFI宣言を使うことができる。 全てのC–プリミティブはユーティリティ関数と同様にimportできる。 プリミティブimportはのしくみは’スマート’で、newtype宣言を通りぬけ、ボックス化/アンボックス化値が必要か面倒をみてくれる。 だからあなたがプリミティブをCharにimportしたなら、 それが’Char’コンストラクタをボックス化されていると認識し、 Char_がBits32_のnewtypeであることも認識してくれる。 そして遂には正しいBits32_プリミティブを選択してくれるのだ。 importされたプリミティブは通常のHaskell宣言である。 だからモジュールからexport/importできるし、通常の高階関数としても扱える。

Foreignプリミティブ

FFIの仕様であるforeign importに加えてjhcは’primitive’ importをサポートしていて、 あなたはこれを用いてコンパイラと直接通信することができる。 一般的にこれらは基盤ライブラリの実装以外の用途に使われるべきではない。 一般にこれらは少ないエラーチェックしか行なわない。 jhc内では、すべてのHaskellのエントリはforeign宣言によって形づくられている。

それらは以下の形を取る。

foreign import primitive "specification" haskell_name :: type

“specification”には以下を選択できる。

seq : 最初の引数をWHNFとして評価し、二番目の引数を返す

zero,one : 0か1の値をプリミティブ型に使う

const.C_CONSTANT : constに続くテキストをコンパイル結果のC言語ソースコードに直接埋め込む

peek.TYPE : TYPE型の値の読み込みプリミティブ

poke.TYPE : TYPE型の値の書き込みプリミティブ

sizeOf.TYPE, alignmentOf.TYPE, minBound.TYPE, maxBound.TYPE, umaxBound.TYPE
内部型TYPEの各種属性

error.MESSAGE : results in an error with constant message MESSAGE.

constPeekByte : 指定したバイト数、定数値を読み込み、Jhc.Stringとして使う

box : アンボックス化値を取り、それをボックス化する。ボックス化された形はimportされた型によって決まる

unbox : ボックス化値を取り、アンボックス化する。ボックスの形はimportされた型によいって決まる

increment, decrement : 整数のプリミティブ値を増減させる

fincrement, fdecrement : 浮動小数点のプリミティブ値を増減させる

exitFailure__ : プログラムを即時abortさせる

C– Primitive : C–プリミティブはこの作法でimportする

差異

Haskell 98との違い

言語差分

ライブラリへの変更

GHCのbaseにおおざっぱに沿ったbaseライブラリに加えて、 jhcはいくつかの拡張とマイナーな修正を標準ライブラリに施している。 これらは大部分は後方互換性とクラスシステムのためにデザインされている。

ライブラリの追加

jhcはその他多くの追加ライブラリを提供している。 ここではHaskell98もしくはFFIの仕様で定義されているモジュールに影響する変更だけを挙げるにとどめる。

GHCとの注目すべき差異

jhcはHaskell 98には準拠しているがGHCとの差異がある。

仕様の欠陥

これらの仕様の欠陥は将来修正される予定である。

クロスコンパイル

基本

他の多くのコンパイラと異なり、jhcは生来のクロスコンパイラである。 これが意味することは、jhcによるコンパイルによって、どんなターゲットシステムのためのコードも生成できるということである。 これにより、jhcを利用したクロスコンパイルが非常に簡単になる。 基本的には、クロスコンパイルを行うためにはjhcに ‘–cross’ フラグと、 ターゲットマシンを指定するオプション ’-m’を渡せば良い。 以下がその例である。

; jhc --cross -mwin32 test/HelloWorld.hs

targets.iniファイルに以下のような表記を追加すれば、ターゲットのリストを拡張できる。

targets.ini

このファイルは利用可能なターゲットを列挙する。 フォーマットは以下のエントリから成り立っている。

[targetname]
key1=value
key2=value
key3+=value
merge=targetname2

mergeは特別なキーで他のターゲットから現在のターゲットへ内容をマージしていることを意味する。 設定ファイルは先頭から順に読み込まれ、 与えられたキーに対応する最後の値が実際に使われる。

次に示すのはWindowsクロスコンパイルの例である:

[win32]
cc=i386-mingw32-gcc
cflags+=-mwindows -mno-cygwin
executable_extension=.exe
merge=i686

上記は一般のi686設定を使って少々の他のオプションをコンパイラに設定している。 特別なターゲットである[default]は常に他のターゲットより先に読み込まれる。 もしコマンドラインから’–cross’が指定されたら暗黙にこの設定を読み込む。 さもなければjhcは現アーキティクチャをコンパイル対象としてみなして、 defaultに追加して適切なターゲットを選択する。

jhcは複数のtargets.iniファイルを順に読み込もう試みる。 それらのファイルは以下である。

$PREFIX/etc/jhc-$VERSION/targets.ini : このtargets.iniファイルはjhcによって読み込まれ、既定のオプションを含む。

$PREFIX/etc/jhc-$VERSION/targets-local.ini : jhcは存在すればこのファイルを読み込む。このファイルはローカルのコンパイラのようなシステムグローバルのカスタム設定を含む。

$HOME/.jhc/targets.ini : ユーザローカルの設定

$HOME/etc/jhc/targets.ini : このファイルは隠し属性のディレクトリを嫌う人向けのものである

オプションについて有効とみなされるのは最後の値である。 ユーザ定義のローカル設定はシステムグローバルのローカル設定を上書きする。 またシステムグローバルのローカル設定は組み込みのオプションを上書きする。

利用可能なオプション

オプション 意味
cc Cコンパイラを指定。一般に手元でのコンパイルにはgccを指定し、クロスコンパイルにはARCHHOST-gccを指定する。
byteorder リトル/ビッグエンディアンのために le もしくは be を指定する
gc GCを使う。static もしくは boehm を指定する。
cflags Cコンパイラに渡すオプション
cflags_debug デバッグが有効な場合のにみCコンパイラに渡すオプション
cflags_nodebug デバッグが無効な場合のにみCコンパイラに渡すオプション
profile プロファイルコードを実行バイナリに出力する
autoload 自動読み込みされるHaskellライブラリを指定。コンマで区切る。
executable_extension 実行バイナリファイルの拡張子を指定する (すなわちWindowsでは.EXEとなる)
merge 他の設定ターゲットの内容を現ターゲットにマージする特別なオプション
bits このアーキティクチャでポインタが保有するビット数
bits_max 整数型の最大値が保有するビット数。これは’intmax_t’のC言語型と同じビット数でなければならない。
arch gccに渡すアーキティクチャ名

内部設計

ランタイムシステム

jhcは強いミニマリストであり、あらかじめコンパイルされたランタイムを持たない。 ランタイムはコンパイル時に必要になった際に生成される。 けれどもバックエンドは、GCの選択のように、ランタイム特定のデータ表現を持つ。 C言語ベースのバックエンドのための一般レイアウトを次に示す。 しかしGC種別のようなコンパイラオプションやプログラム全体解析は、どの機能が使われて、最適化されたレイアウトが使用できるかに影響を及ぼす。

アンボックス化値はターゲットの言語の値に直接翻訳される。 アンボックス化Intは引数として直接’int’に翻訳される。 またアンボックス化ポインタは生のポインタになる。 アンボックス化値は特別な解釈を持たず、 GCは面倒をみてくれない 。 もしターゲットの言語が複数返値のような機能をサポートしていない場合、シミュレーションする必要がある。 grinコードが複数返値が補強されたC–もしくはCへの同型としてのみアンボックス化値を取り扱うのは悪い考えではない。

ボックス化された値は標準の表現を持っている。 他の実装と異なり、ボックス化されることがオブジェクトがヒープに配置されることを意味しない。 それはスタックやヒープに確保されるかもしれない。 さらにはスマートポインタの中に組み込まれているかもしれない。 ボックス化されていることはオブジェクトがスマートポインタによって表現されているか、 実際には伝統的なポインタであるかもしれないのだ。

jhcにおけるボックス化された値はC言語型のsptr_tという’スマートポインタ’である。 スマートポインタは通常のポインタと同じサイズを持つが、 1つのペアのタグビットで表わされる別の役割もおっている。 それはptypeと呼ばれる。

スマートポインタは一般に以下の形をしている:

-------------------------
|    payload        | GL|
-------------------------

  G - セットされると、GCは値をポインタとして取り扱わない
  L - 遅延。このビットがセットされると値はWHNFではないことを示す

sptr_tは以下の形の内どれか一つの状態にある:

-------------------------
|    whnf raw value | 10|
-------------------------

-------------------------
|    whnf location  | 00|
-------------------------

WHNFは’Weak Head Normal Form’を表わしている。 さらにその値は中断された関数ではなく、それゆえサンクへのポインタでもない。 それは直接検査されるかもしれず、また評価される必要がないかもしれない。 wptr_tはsptr_tの別名で、上記の形式を取っていることを保証するためにある。 これは安全性を向上させるために使われ、 値がWHNFであり高価な’eval’をスキップできるかどうかを静的に知ることができる。

生の値とWHNF配置の違いは、 前者は未解釈のビットを保持しているが、 後者はヒープやスタックへのポインターであるためにGCが回収する必要があるということだ。 WHNF配置によって指し示されたメモリのフォーマットは実際の型表現に依存していていちがいに決まってはいない。

部分適用された関数は通常のWHNF値である。 ’eval’されたか更新された全適用された関数はサンクと呼ばれ、WHNFのポインタから指示されない。 これらの表現は以下である。

-------------------------
|   lazy location   | 01|
-------------------------

遅延配置はサンクもしくはWHNFへのリダイレクトトを指し示す。 遅延配置は必ずアロケートされたメモリ領域を差し、 その領域の先頭は制限されたスマートポインタである。 この制限されたスマートポインタはC言語の’fptr_t’型で表現される。 fptr_tは遅延配置の先頭エントリのためだけに存在し、 それらはオブジェクトとして流通することはない。

fptr_tはWHNF値もしくはコードへのポインタである可能性がある。 もしfptr_tが(上図2種類の内の一つのような)WHNF値ならそれはリダイレクションと呼ばれる。 遅延配置はそれがWHNFによるものだと厳密に取り扱わなければならない。 これは評価済みサンクへのリダイレクトとして使われる。

fptr_tはまた’コードポインタ’である可能性があり、 この場合遅延配置はサンクと呼ばれる。 コードポインタは実行可能な機械語へのポインタで、それはクロージャを評価してwptr_tを返す。 返却されたwptr_tは通常、サンクをリダイレクションに変更するために、コードポインタを書きつぶす。 指示されたコードはこのリダイレクションを実行する責任がある。

-------------------------
|    code pointer   | 11|
-------------------------
|     data ...          |

デバッグ時には特殊なコードポインタBLACK_HOLEが時々fptr_tに格納されている。 これはある種のランタイムエラーを検出する。

他の実装と異なりfptr_tは遅延配置の別の形では ない ことに注意すること。 あなたはリダイレクションをチェーンしてはいけない。 リダイレクションは常にWHNF値へのリダイレクションでなければならない。

sptr_t - タグ付けされたスマートポインタ。WHNF値か遅延配置を保持する。
wptr_t - タグ付けされたスマートポインタ。生か配置情報のWNHF値を保持する。
fptr_t - タグ付けされたスマートポインタ。リダイレクションを示すWHNF値かサンクを示すコードポインタを保持する。

jhcコア型システム

jhcのコアは純粋なpure type systemにもとづいている。 pure type system (PTS)は型システムのパラメータ化されたセットである。 jhcのPTSについて以下で解説する。

種類 = (*, !, **, #, (#), ##, □)
公理 = (*:**, #:##, !:**, **:□, ##:□)

-- 種類kind(種)
*   ボックス化値の種
!   ボックス化された正格値の種
#   アンボックス化された種
(#) アンボックス化タプルの種
-- 種類superkind
**  全てのボックス化値のsuperkind
##  全てのアンボックス化値のsuperkind
-- 種類box
□   superkindの存在位置

ユーザ定義の種の存在に加えて、supersort ##が存在する。

次に示すルール表は抽象の種類を表現している。 (A,B,C)の形をもつルールは種類Aから種類Bへの関数で結果が種類Cであることを意味している。 この文脈での 関数 は項と型レベルの抽象を含んでいる。

アンボックス化タプルを引数に取り正格であるかもしれないが、 関数はいつもボックス化されていることに注意すること。 コードを直接指示するポインタによって表現され、関数が正格であるなら、 関数の評価が中断された値であることはありえない。

これらの型システムのルールはλ抽象に適用される。 これらのルールによって、与えられた型を取れないデータコンストラクタが存在してしまうかもしれないが、それは許容できる。 例としてはアンボックス化タプルがある。 部分適用された関数であったとしてもそれらのコンストラクタが型エラーであることを正しく推論するのは困難ではない。

ショートカットとしてここでは *# を * と # の組み合わせをとして使う。
例えば、 (*#,*#,*) は (*,*,*) (#,*,*) (*,#,*) (#,#,*) のセットを意味している。

ルール =
   (*#!,*#!,*)  -- 値から値への関数で、ボックス化されていて非正格
   (*#!,(#),*)  -- 値からアンボックス化タプルへの関数で、ボックス化されていて非正格
   ((#),*#!,!)  -- アンボックス化タプルから値への関数で、ボックス化されていて正格
   ((#),(#),!)  -- アンボックス化タプルからアンボックス化タプルへの関数で、ボックス化されていて正格
   (**,*,*)     -- アンボックス化型から値への関数を保持している可能性がある
   (**,#,*)
   (**,!,*)
   (**,**,**)  -- 型から型への関数を持てる
   (**,##,##)  -- MutArray_ :: * -> #
   (##,##,##)  -- Complex_ :: # -> #

ボックス化値の定義は以下である。

_|_ :: t iff t::*

このPTSは関数的であるが単射ではない。

このPTSは次のレベルに階層化されている。

□               - 種類box
**,##,           - 種類superkind
*,#,(#),!        - 種類kind(種)
Int,Bits32_,Char - 種類type(型)
3,True,"bob"     - 種類value(値)

ボックス化kind(種)について

ボックス化kind(* や !)は同型のランタイム表現を持つ型を表現する。 このため、関数はこれらのkindの型で多相的に記述することができる。 (**,?,?)という形のルールがあるため、ボックス化されたkindの型を引数として取ることができる。

アンボックス化されたkind # は独自のランタイム表現を持つ型に存在する。 アンボックス化型を多相的に使う関数を書くことはできない。

種類box、アンボックス化タプルとその同類について

種類boxはコード中に現われないけれど、アンボックス化タプルのようなある種の型について理論的な視点から有用である。 アンボックス化タプルはボックス化/アンボックス化どちらの引数も取れる。 これを表現することは種類boxなしにそれは不可能だ。 なぜならsuperkind多相でなければならないから。 種類boxは次のような表現を許す。 (これはアンボックス化2タプルのケースである)

∀s1:□ ∀s2:□ ∀k1:s1 ∀k2:s2 ∀t1:k1 ∀t2:k2 . (# t1, t2 #)

たとえアンボックス化タプルが部分適用されることが妥当な型付けでも (##,?,?) もしくは (□,?,?) の形のルールは許容しないのだから、 この型は明らかに型検査されない。 アンボックス化タプルがいつも全適用されるという不変条件が強制されるなら種類boxのコード表現もまた不要になる。

superboxは必要か?

種類の公理を見て接続の不足したグラフになっていることに、あなたは気付くかもしれない。

         □            - box
        / \
      **   ##          - superkind
      /\     \
     *  !     #   (#)  - kind(種)

これは単に、それらのsuper-sort(種類)は本来不要であるが、kind (#) のアンボックス化タプルが多相性を欠如していることにに起因する。 このギャップを埋めるために(##)と(□)、□□の種類を導入できる。 これらの種類は決してコードや論文には現われないが、気にしないことにする。

           □□          - 種類superbox
          /  \
         □    (□)      - 種類box
        / \      \
      **   ##     (##)   - 種類superkind
      /\     \    |
     *  !     #   (#)    - 種類kind(種)

  1. 後半の“jhcコア型システム”の章の翻訳は differential_engine (dif_engine) さんに手伝ってもらったでゲッソ。感謝でゲソー↩︎

blog comments powered by Disqus