diff --git a/ppx_2018.md b/ppx_2018.md new file mode 100644 index 0000000..35b4c34 --- /dev/null +++ b/ppx_2018.md @@ -0,0 +1,194 @@ +# 2018年のPPX + +# PPXとは + +PPXとはOCamlのプリプロセッサ方式の一つです。 +今流行ってます。 +簡単に言うとOCamlの構文解析木(AST)を受け取ってASTを返すプログラムです。 +入力されたASTを変化させることでプリプロセッシングを行います。 + +PPXプリプロセッサはOCamlコンパイラに`-ppx`オプションを与えることで起動できます: + +```shell +$ ocamlc -ppx ppx.exe x.ml +``` + +とすると、 + +* OCamlコンパイラがソースコード`x.ml`を構文解析しASTを作る +* ASTを`-ppx`オプションで指定したPPXプリプロセッサ`ppx.exe`に渡す +* PPXプリプロセッサはASTを変化させて、OCamlコンパイラに返す +* OCamlコンパイラは変化したASTを入力として型検査、コンパイルを行う + +という手順でプリプロセスとコンパイルが行われます。 + +PPXはOCamlのソースコードの文字列自体は取り扱わず、 +その構文解析結果であるASTを入力と出力にします。 +ですから、PPXの出力にさらに別のPPXを適用することができます: + +```shell +$ ocamlc -ppx ppx.exe -ppx ppx2.exe x.ml +``` + +とすると`x.ml`のASTがまず`ppx.exe`に渡され、 +次にその結果が`ppx2.exe`に渡されます。 +このように複数のPPXを組み合わせることで、 +複数の別の機能のあるプリプロセッサをOCamlのソースコードに連続して適用できます。 + +# Attribute と extension point + +OCamlの文法には、PPXがプリプロセス時に利用できるヒントとして、 + +* Attribute: `[@...]`, `[@@...]`, `[@@@...]` +* Extension point: `[%...]%, `[%%...]` + +が追加されています。なんじゃこりゃ。 + +AttributeはASTのノードに情報を足します: + +```ocaml +1 [@hello] +``` + +と書くと`1`という式に`hello`という情報を付加できます。 + +## `ocamlc -dparsetree`は友達 + +... + +... + +... + +よくわからない? +よくわからないですね。 +実際にASTを見てみましょう。 +よけいわからなくなるかもですが。 + +OCamlには`-dparsetree`という素晴らしい「ひみつ(undocumented)」機能があります。 +これを使うとプログラムの構文解析結果をなんとなく見ることができます: + +```ocaml +1 [@hello] +``` + +という内容のファイル`x.ml`を作ってやると + +```shell +$ ocamlc -dparsetree x.ml +[ + structure_item (x.ml[1,0+0]..[1,0+1]) + Pstr_eval + expression (x.ml[1,0+0]..[1,0+1]) + attribute "hello" + [] + Pexp_constant PConst_int (1,None) +] + +``` + +なんか出ました。これが上のソースコードを構文解析した結果のAST(構文木)です。 +慣れていないとなんのことだかわからないかもしれませんが、 +式`expression`が`x.ml`の一行目0文字目から1文字目にあって、 +その内容は整数定数`1`で、attributeとして`"hello"`という文字列が +付加されている、と言われればなんとなくわかるのではないでしょうか。 + +PPXはこのASTに付加されたアトリビュートを利用してプログラム変換を行うことができます。 + +あっ、使わなくてもいいですよ。単に無視すれば、 +`ocaml.warning`や`unboxed`などのOCamlコンパイラが使用するアトリビュートを除いて、 +コンパイルには影響はありません。 + +## 式、型、変数へのアトリビュート `[@...]` + +アトリビュート`[@...]`は直前の式、型、変数に付加されます。 + +``` +1 + 2 [@hello] +``` + +において、`[@hello]`は`2`に対して付加されます。`1 + 2`全体に付加するには、 + +``` +(1 + 2) [@hello] +``` + +とします。 + +## 宣言へのアトリビュート `[@@...]` + +```ocaml +let x = 1 +``` + +のような宣言があった時に、式ではなくて、宣言全体にアトリビュートを付けたい、 +ということがあります。その時に利用するのが`[@@...]`です: + +```ocaml +let x = 1 [@@hello] +``` + +`@`の数で区別ってダサいと思われるかもしれませんが、 +`(let x = 1) [@hello]`とかとするとカッコ付けが大変なので。 + +## ファイル全体につけるアトリビュート `[@@@...]` + +`[@@@...]`はファイル全体につけるアトリビュートです。 + +## アトリビュートの実用例 + +このアトリビュートはPPX以外にも使い道があって、 +例えば警告スイッチを操作することができます。 +例えば次のプログラムはコンパイルが警告8を出します: + +```ocaml +(* ocamlc -c x.ml *) +let f = function + | 1 -> true +``` + +やってみます: + +```shell +$ ocamlc -c x.ml +File "x.ml", line 2, characters 8-30: +Warning 8: this pattern-matching is not exhaustive. +Here is an example of a case that is not matched: +0 +``` + +ここで、この宣言に`[@@ocaml.warning "-8"]`をつけると、 +この警告を消すことができます: + +```ocaml +(* ocamlc -c x.ml *) +let f = function + | 1 -> true + [@@ocaml.warning "-8"] +``` + +```shell +$ ocamlc -c x.ml +(警告でなーい) +``` + +このアトリビュート`[@@..]`はこの宣言だけに影響するので、 +もしプログラムの他の部分に警告8が出るところがあれば、 +その警告はそのままです。 + +プログラム全体で警告8を無視したければ、`[@@ocaml.warning "-8"]`のかわりに +`[@@@ocaml.warning "-8"]`を使います。 + +PPX抜きでのアトリビュートの使用は、他にもC言語関数との間で +unbox化されたレコードを使った最適化を使うための +`[@@unboxed]`アトリビュート +(OCaml reference manualの20.3.2 Tuples and records +http://caml.inria.fr/pub/docs/manual-ocaml/intfc.html +を参照)などがあります。 + +## エクステンションポイント + +アトリビュートは存在するASTノードに情報を付加するものですが、 +ではエクステンションポイントってなんでしょうか。 + +エクステンションポイントは式、型、宣言に置き換える \ No newline at end of file