From afdedf6d693a8b4b185d1adcb4c8578ca72f7ddb Mon Sep 17 00:00:00 2001 From: Jun FURUSE Date: Fri, 27 May 2016 17:01:12 +0800 Subject: [PATCH] added old files --- bb/4.02.md | 426 +++++++++++++++ bb/README.md | 16 + bb/caml_bool.rst | 19 + bb/caml_num.rst | 269 ++++++++++ bb/camlp4.rst | 881 ++++++++++++++++++++++++++++++++ bb/channel.md | 19 + bb/clffaq.md | 742 +++++++++++++++++++++++++++ bb/duck.rst | 168 ++++++ bb/exception.md | 233 +++++++++ bb/fwo.md | 96 ++++ bb/include.md | 149 ++++++ bb/index.rst | 8 + bb/install.md | 357 +++++++++++++ bb/js_of_ocaml.rst | 539 +++++++++++++++++++ bb/lazy_split_at.rst | 172 +++++++ bb/module_coercion.md | 100 ++++ bb/monad_functor.rst | 532 +++++++++++++++++++ bb/mutable.md | 154 ++++++ bb/oasis.rst | 360 +++++++++++++ bb/ocaml-ext-tools.rst | 619 ++++++++++++++++++++++ bb/ocaml-object.rst | 127 +++++ bb/ocaml-tools.rst | 318 ++++++++++++ bb/ocaml_c_interface.rst | 58 +++ bb/ocaml_i18n.md | 328 ++++++++++++ bb/one-data-type-one-module.rst | 180 +++++++ bb/opam_release.md | 110 ++++ bb/pack.md | 56 ++ bb/pattern_match.md | 43 ++ bb/ppx.md | 188 +++++++ bb/random_memo.rst | 104 ++++ bb/recursion.md | 40 ++ bb/side_effect.md | 32 ++ bb/tclass.md | 245 +++++++++ bb/types.mli | 271 ++++++++++ bb/warn.ml | 14 + bb/warning.md | 157 ++++++ bb/why-p4-sucks.rst | 19 + 37 files changed, 8149 insertions(+) create mode 100644 bb/4.02.md create mode 100644 bb/README.md create mode 100644 bb/caml_bool.rst create mode 100644 bb/caml_num.rst create mode 100644 bb/camlp4.rst create mode 100644 bb/channel.md create mode 100644 bb/clffaq.md create mode 100644 bb/duck.rst create mode 100644 bb/exception.md create mode 100644 bb/fwo.md create mode 100644 bb/include.md create mode 100644 bb/index.rst create mode 100644 bb/install.md create mode 100644 bb/js_of_ocaml.rst create mode 100644 bb/lazy_split_at.rst create mode 100644 bb/module_coercion.md create mode 100644 bb/monad_functor.rst create mode 100644 bb/mutable.md create mode 100644 bb/oasis.rst create mode 100644 bb/ocaml-ext-tools.rst create mode 100644 bb/ocaml-object.rst create mode 100644 bb/ocaml-tools.rst create mode 100644 bb/ocaml_c_interface.rst create mode 100644 bb/ocaml_i18n.md create mode 100644 bb/one-data-type-one-module.rst create mode 100644 bb/opam_release.md create mode 100644 bb/pack.md create mode 100644 bb/pattern_match.md create mode 100644 bb/ppx.md create mode 100644 bb/random_memo.rst create mode 100644 bb/recursion.md create mode 100644 bb/side_effect.md create mode 100644 bb/tclass.md create mode 100644 bb/types.mli create mode 100644 bb/warn.ml create mode 100644 bb/warning.md create mode 100644 bb/why-p4-sucks.rst diff --git a/bb/4.02.md b/bb/4.02.md new file mode 100644 index 0000000..b7ae581 --- /dev/null +++ b/bb/4.02.md @@ -0,0 +1,426 @@ +OCaml 4.02 の予習 +================================= + +printf が GADT で強化された +----------------------------- + +まあ簡単に言うと毎回プリントする再にインタープリットしていた format 文字列を +もすこし代数的なデータに落して嬉しいみたいな感じ、なのだが、 + + (* ocaml -dlambda *) + # Printf.sprintf "hello %s !!" "world";; + (apply (field 3 (global Printf!)) + [0: [11: "hello " [2: 0a [11: " !!" 0a]]] "hello %s !!"] "world") + - : string = "hello world !!" + +おお、 `"hello %s !!"` が `"hello "` と `" !!"` とその間の何かにコンパイル時に分解されている。 +だから毎回インタープリットしないから速い。速いとすごい、だから強いってことか。 +ようやく P4 で同じことをやっていた O'Rakuda の `<:qq>` みたくなったということですね。 + +この辺なんか GADT 使って頑張ったーみたいなことが書いてあるんだけど、 +結局相変わらずコンパイラ側の format 文字列型付けハックは残っているのでチートであることは変らない。 +まあ、使う人には、中身はほぼ無意味ですね。 +興味があったら Pervasives.CamlinternalFormatBasics と CamlinternalFormat を読めということらしい。 + +しかし、こんな変更ガガっと入れるのはええけど、ちゃんとテストしてあるんか? +これは不安ですね… + +module alias が軽くなった +--------------------------- + +中の人がぼんやりしてたら、外の人がモジュールをごにょごにょ操作するしまくる +OCaml プログラミングスタイルというのを確立してしまった感がある。特に Jane Street +のコードや俺のコードはモジュールをあっちこっちにエイリアスしたりインクルードしたり +大変。それはそれで便利なんだが、実は重かった。例えば、次のような stdlib のモジュールを +何も考えずに全部エイリアスするコード、 + + module Arg = Arg + module Array = Array + module ArrayLabels = ArrayLabels + module Buffer = Buffer + module Callback = Callback + module Char = Char + module Complex = Complex + module Digest = Digest + module Filename = Filename + module Format = Format + module Gc = Gc + module Genlex = Genlex + module Hashtbl = Hashtbl + module Int32 = Int32 + module Int64 = Int64 + module Lazy = Lazy + module Lexing = Lexing + module List = List + module ListLabels = ListLabels + module Map = Map + module Marshal = Marshal + module MoreLabels = MoreLabels + module Nativeint = Nativeint + module Obj = Obj + module Oo = Oo + module Parsing = Parsing + module Pervasives = Pervasives + module Printexc = Printexc + module Printf = Printf + module Queue = Queue + module Random = Random + module Scanf = Scanf + module Set = Set + module Sort = Sort + module Stack = Stack + module StdLabels = StdLabels + module Stream = Stream + module String = String + module StringLabels = StringLabels + module Sys = Sys + module Weak = Weak + +これを 4.01.0 でコンパイルすると結果はこんなサイズ: + + -rwxr-xr-x 1 161780 m.cmi + -rwxr-xr-x 1 2486 m.cmo + -rwxr-xr-x 1 68919 m.cmx + -rw-r--r-- 1 943 m.ml + -rw-r--r-- 1 3100 m.o + +で、これがなんか 4.02.0 では軽くなったんだと: + + -rwxr-xr-x 1 2571 m.cmi <---- !!! + -rwxr-xr-x 1 2405 m.cmo + -rwxr-xr-x 1 2499 m.cmx + -rw-r--r-- 1 943 m.ml + -rw-r--r-- 1 1521 m.o <---- !!! + +おお、はじめは `.cmo` だけ見てたので意義が判らなかったが、 signature (`.cmi`) +と native code (`.cmx` と `.o`) は凄く小くなっている。どうも `strings` で +`.cmi` や `.cmx` を見ると、4.01.0 以前では `module M = N` の `N` の中身を +真面目に展開していた、その展開を止めてエイリアスにした、そういうことらしい。 + +string は immutable の方向へ +-------------------------------- + +string の他に、 bytes という型が入った。通常は string と bytes は同じ型で単一化できる: + + let x : bytes = "hello" + let y : string = "world" + let xy = [ x; y ] (* 問題ない *) + +だが、`-safe-strings` というコマンドラインスイッチを入れるとこの同一視が無くなり、 +string は immutable な文字列、bytes は mutable な文字列という扱いに変る。 +`"hello"` は string としてしか解釈されなくなるので、上の `let x : bytes = "hello"` の +ところでエラーになる。bytes を作るには、 + + let x = Bytes.of_string "hello" + +と書かなければならなくなる。 + +例によって string はただの char 配列でしかないので UTF-8 とかを扱いたい時はライブラリを +通す必要がある。 + +実装上は実に簡単なのでバグとかは無いと思う。 + +新しい文字列リテラル +---------------------- + +`{id|文字列|id}` という新しい文字列が書けるようになった。例えば `{|abcd"\/|}` と書くと、 +`"abcd\"\\/"` という文字列と見做される。 `{|..|}` の中では `\` は特殊文字と見做されないので +`\` を入れる為に `\\` と重ねなくてよいのだ。これで `\` が瀕出する PCRE も楽に書けるわけ。 +id つきのは `{special|文字列|special}` などと書く。ちゃんと対になっていけないといけない。 +冗長でちょっとウザと思うのだが、文字列の中に `|}` を書きたい場合はこれを使うのだと思われる。 +後多分 id は attribute として使えるはず。 + +これも簡単な部類。ガンガン使っていけるはず。 + +Generative functors +-------------------------- + +今までの OCaml の functor は applicative functor と呼ばれる: + + module Make(A : sig end) = struct + type t = Foo + let x = Foo + end + + module Empty = struct end + + module A = Make(Empty) + module B = Make(Empty) + + let () = assert (A.x = B.x) + +が通る。つまり A.t と B.t は同一視される。キモい、と思われるかもしれないが、 + + let _ : A.t = MA1.x + +ではなく + + let _ : M(Empty).t = MA1.x + +と書けたりするため、役に立たないわけではない。 + +でも、キモい、 `A.t` と `B.t` は区別されるべき、という場合もあり、そういう functor +は generative functor と言うそうな。まあ一回一回型が新しく生成されるわけだから generative なのかな。 +それが書けるようになった。 + + module A = Make() + module B = Make() + +と、`(Empty)` 代りに `()` と書くと `A.t` と `B.t` は別の型になって `A.x = B.x` は型エラーになる。 +`()` は空の structure にしか使えないので、非空の引数を取る functor に対して generative functor +は作れないかというと、そういうことはなく、単にもう一つ `()` を引数に取る functor を作ってやれば +よろしい。 + + module Make2(A : sig type t end)() = struct + type t = Foo + ... + end + +Applicative と generative の二つの functor が欲しいのは判るのだが、 +解決が cryptic で駄目なんじゃないかとおもう。Backward compatibility は大事だし新キーワード +恐怖症も理解できるけど、もうなんか限界が来ているじゃないかと思わせる変更。 + + +Attributes and extension nodes +------------------------------------ + +Attribute ってのは簡単に言うと OCaml のコードの好きな所に好きなことを書けるのである。 +ほいでそれを好きにパースして利用するわけだ。パースされた AST を貴方がプログラムで好きに変換して +OCaml コンパイラに戻す。変換次第でバニラ OCaml では出来無かった機能を追加できるわけ。 + +元々 OCaml でこういう機能追加は CamlP4 っていうかっちょいいプリプロセッサでやっていたんだけれども、 +これには幾つか問題があった: + +* 難しい。とにかく複雑。ドキュメント無いし +* パーステクノロジが違う。OCaml バニラは lex+yacc だけど P4 はストリームパーサー。ただし文法拡張ができる。 +* 文法拡張するので拡張部分の事を知らないプログラムでは拡張を使ったコードを機械的に読み込めない。 +* プリプロセスの手間がかかりどうしても遅い + +で、この attributes では、もう文法拡張は止めましょう。その代り、 AST の好きなところにマークを +付けれるようにしましょう。で、そのマークを元に AST を変更するフィルタを書きましょう、 +ということになった。 + +これで嬉しいのは、 attributes を知らなくても少なくともコードのパースはできるってことで、 +エディタでのインデントとかそういう機能は P4 の文法拡張のことを気にしなくて良くなったってこと。 + +Attribute は `[@...]` というキモい文法でコード中に埋め込むことができる。 +問題は、 `[@...]` だけでなく `[@@...]` や `[@@@...]` とかあることで醜いですねー。 +これは AST のどこに attribute がくっついているかを示すものらしい: + +* `[@...]` は expression とか +* `[@@...]` は structure や signature item レベル +* `[@@@...]` は多分、ソースコードファイル全体 (compilation unit) レベル + +覚え方は `@`(at) は ATtribute の頭文字。 + +例えば + + let x = 1 [@attr] + +は `1` に対して `attr` という attribute がくっついているが、 + + let x = 1 [@@attr] + +は `let x = 1` という structure item に対して `attr` という attribute がくっついている。 + +でこの attribute なんだけど、多分、メジャーな CamlP4 拡張は殆どこれでカバー「できる」ということになっている。だから P4 はさようならになると思われる。 + +既にこの attribute は OCaml コンパイラ内部でも使われていて、`obj.mli` では + + val final_tag : int [@@ocaml.deprecated] + +とか書かれている。 `final_tag` を使うと `ocamlc` が警告を出してくれる。 + +あ、あと、 `[%...]` と `[%%...]` という extension てのもあるんだ。 +これは attribute と似ているんだけど、どっちかというと構文のキーワードを修飾して +特殊な意味を持たせるのに使う。 + + [%io + let x = read in + print x + ] + +とか書いて、これを適切なパーサに通すと + + read >>= fun x -> print x + +とかに変えてくれる、そんな感じ。で、 extension には簡単な書き方が用意されていて、 +上のはこう書いてもよい: + + let%io x = read in + print x + +うーーーーーん、どうなんだろうねえ。これで Haskell の `do` 記法書かせて見栄えどうなるんかね。 +実際、 lwt ライブラリの `bind` である `lwt x = ... in` は + + let%lwt x = ... in + +と書かせたいみたいだけど、醜いよね。普通の `let` と `let%lwt` が混在してだらららーっ +と並んでたら結構鬱になる気がするが… + +[OMonad](https://github.com/danmey/omonad) というので pa_monad っぽいの書けるのねえ: + + let vs = perform ( + [|x; y|] <-- [[|1; 2|]; [||]; [|3; 4; 5|]; [|6; 7|]]; + return (x + y) + ) in + +んー? `[|x; y|] <-- [|...|]` ってことは、 `<--` は普通の二項演算子、 +てことはこの左辺 `[|x; y|]` は pattern の様に見えて、実は AST 上では expression なのか… +filter で expression から pattern に変換しているのだな… OMonad のコードでも確認した。 +これはキモい。 + +うーん、 OCaml バニラ構文に閉じ籠るのはいいんだけど、じゃあバニラ構文今で足りてるのかというと、 +ということで、 P4 無くなったのはいいけど P4 の柔軟性無くなったのはキツいんじゃないですか。 + +Open extensible types +----------------------------- + +まあコンストラクタが増やせる variant のようなもの。 + + type t = .. + +で宣言する。この `..` があるので普通の variant とは区別される。コンストラクタを足すには + + type t += A + +とか + + type t += B of int | C + +とか書く。このコンストラクタの追加は別のモジュールで行われてもよいし、お互いの追加は +お互いの存在を知っている必要もない。コンストラクタ名が被っていても構わない。 + +この辺はキモいかもしれないが、例外の一般化と思う方が理解しやすいかもしれない。 +例外の型 exn は次の open extensible type と同じ + + type exn = .. + +で、 `exception E of t` は + + type exn += E of t + +と同じ。実際に exn は open extensible type と内部では同じになっている。 +これを見れば、例外宣言はお互いの例外宣言の存在を知っている必要はないし、 +例外名が被っていても構わない(`exception E;; exception E of int` とか普通やらないけど) +ことから理解できるだろう。 + +で実際の挙動はこうなっている + + type t = .. + type t += A of int + type t += B of int + + open Obj + + let parse_tag t = + if t = int_tag then `int + else if t = string_tag then `string + else if t = lazy_tag then `lazy_ + else if t = closure_tag then `closure + else if t = object_tag then `object_ + else if t = infix_tag then `infix + else if t = forward_tag then `forward + else if t = no_scan_tag then `noscan + else if t = abstract_tag then `abstract + else if t = custom_tag then `custom + else if t = final_tag then `final + else if t = out_of_heap_tag then `out_of_heap + else if t = unaligned_tag then `unaligned + else `unknown t + + let tag_name = function + | `int -> "int" + | `string -> "string" + | `lazy_ -> "lazy" + | `closure -> "closure" + | `object_ -> "object" + | `infix -> "infix" + | `forward -> "forward" + | `noscan -> "noscan" + | `abstract -> "abstract" + | `custom -> "custom" + | `final -> "final" + | `out_of_heap -> "out_of_heap" + | `unaligned -> "unaligned" + | `unknown x -> string_of_int x + + + let dump o = + let open Format in + let rec dump ppf o = + let t = parse_tag @@ tag o in + match t with + | `int -> fprintf ppf "%d" @@ obj o + | `double -> fprintf ppf "%.4f" @@ obj o + | `string -> fprintf ppf "%S" @@ obj o + | _ -> + fprintf ppf "[%s @[" @@ tag_name t; + let s = size o in + for i = 0 to s - 1 do + dump ppf (field o i); + fprintf ppf "@ " + done; + fprintf ppf "@]]" + in + eprintf "%a@." dump o + + let () = + let o = repr (B 3) in + dump o + +このプログラムの出力により `B 3` は + + [0 [object "B" 86 ] 3 ] + +というデータになっていることがわかる。つまり、第一要素に何かコンストラクタ名を含む +オブジェクトタグのついたブロックを含む組だ。普通のバリアントと違ってタグは整数ではなくて +このオブジェクトタグのついたブロックを使う。別モジュールでやはり `B` というコンストラクタを +足した場合でもこの `[object "B" 86 ]` タグが作られるが、この二つのタグを区別できるように +構造比較ではなくてアドレスを判別に使う。これは今までの例外の実装と同じだね。 +だから output_value や Marshal でデータを書き出し読み込んだ場合もとのコンストラクタとは +同一視できなくなる。これも例外と同じ。 + +4.01 では例外には object tag は使われていなかったんだけど、これはどういうことなんかいな。 +例えば上の関数で Failure "hello" はこうなってたんだけど: + + [0 [0 "Failure" ] "hello" ] + +object に付いてくる hash で何か高速化狙ってるのかな? ちょっと不安を感じる… + +例外キャッチを match with に埋め込めるようになった +----------------------------------------------- + +ただの糖衣構文。キモい。 + + let f lst = + try + match List.assoc "x" lst with + | None -> 0 + | Some x -> x + with + | Not_found -> 0 + +を + + let f lst = + match List.assoc "x" lst with + | None -> 0 + | Some x -> x + | exception Not_found -> 0 + +と書けるようになった。そこまでして短かく書きたければ、 +そもそも例外を発生させないライブラリ作りをするべきでは。キモい。 + +まあ単なる糖衣のはずなので使って問題ないはず。 + +LablTk が本体から消える +---------------------------- + +まあ、これ私が大分遊んだやつなんだけど、今更 Tcl/Tk とかねぇ。GUI は外に出しましょう! + +Camlp4 が本体から消える +------------------------------ + +これは p4 が終わりということではなくて、別のレポジトリで管理されるということなんだけれども、 +もちろん、 p4 は終わりという意味です。 diff --git a/bb/README.md b/bb/README.md new file mode 100644 index 0000000..97554ac --- /dev/null +++ b/bb/README.md @@ -0,0 +1,16 @@ +Survival Guide in Desert with OCaml +=========================================== + +この文章は OCaml の入門書ではありません。 + +OCaml の入門書は英語でも日本語でも既にいろいろと存在しています。 +それらに書かれている事と同じ事を繰り返すのは、どうも時間の無駄かと思います。 + +この文章はその一歩先、独りで OCaml に乗って街を離れ、 +死なずにオアシスに辿り着くための砂漠の地図を提供するつもりです。 + +既存のドキュメントに書かれている事は特に事情がない限り書かないこととします。 +例えば、 OCaml コンパイラ一式をソースコードからインストールする場合には、 +`INSTALL` というファイルを読めとは書きますが、その中の一歩一歩を辿ることは +しません。なぜならそれは元の情報を劣化させて不正確にしたものを伝えること +だからです。 diff --git a/bb/caml_bool.rst b/bb/caml_bool.rst new file mode 100644 index 0000000..b3a3aea --- /dev/null +++ b/bb/caml_bool.rst @@ -0,0 +1,19 @@ +============================= +OCaml の真偽値型 ``bool`` +============================= + +OCaml の真偽値の型は ``bool`` その *コンストラクタ* は ``true`` と ``false`` である。 ``true`` と ``false`` は小文字で始まっているが、変数ではなく、 ``Some`` や ``None`` と同じヴァリアントコンストラクタである。よってパターンの中に書ける:: + + (* true の数を数える *) + function + | (true, true) -> 2 + | (true, false) | (false, true) -> 1 + | (false, false) -> 0 + +``bool`` の文字列への出力 +------------------------------- + +``Printf`` のフォーマット文字列中で ``%b`` を使うことで ``bool`` を出力させることができる:: + + # Printf.sprintf "%b" true;; + - : string = "true" diff --git a/bb/caml_num.rst b/bb/caml_num.rst new file mode 100644 index 0000000..0923d9b --- /dev/null +++ b/bb/caml_num.rst @@ -0,0 +1,269 @@ +============================= +OCaml の数値型 +============================= + +OCaml 数値型で標準で使えるものは、 +``int``, ``float`` , ``int32``, ``int64``, ``native_int``, ``nat``, ``big_int``, +``Complex.t`` +の 8つ: + +``int`` + + 標準の整数。 32bit 環境では 31bit, 64bit 環境では 63bit 符号付き整数。 + +``float`` + + 倍精度(64bit)浮動小数点数。 IEEE 754 で言うところの double であって、float ではない。 + +``nativeint`` + + OS でサポートする基本整数幅を持つ。 + 32bit 環境では 32bit, 64bit 環境では 64bit 符号付き整数。 + +``int32`` + + 32bit 符号付き整数。 + +``int64`` + + 64bit 符号付き整数。 + +``nat`` + + num ライブラリで定義されている無限制精度自然数。 + +``big_int`` + + num ライブラリで定義されている無限制精度符号付数。 + +``Complex.t`` + + 複素数。 単に float 二つ組で表現されている。 + +``int`` +================================== + +OCaml で「整数」と言うと ``int`` を指す。 +32bit 環境では 31bit, 64bit 環境では 63bit の符号付き整数。 +「失われた 1bit」は GC が int とポインタを識別するために使われる。 +この 1bit のおかげで ``int`` は box化されず、そのまま扱うことができるので +「1bit」が失われていないが、box化が必要な ``nativeint`` と比べると高速に動作する。 + +定数の書式 +---------------------------- + +通常の10進表記の他に、2進、8進、16進での表記が可能:: + + # 10;; + - : int = 10 + # 0b10;; + - : int = 2 + # 0o10;; + - : int = 8 + # 0x10;; + - : int = 16 + +基本的な演算子 +---------------------------- + +Pervasives に基本的は演算子 ``(+)``, ``(-)``, ``( * )``, ``(/)`` などが +記載されているが、ほとんどがプリミティブ +(``external`` 宣言による C関数呼び出し)である。 + +``int`` 掛け算の二項演算子 ``*`` を関数(CR jfuruse:?)として使う場合、 +``(*)`` とスペース無しで書くとコメント様の文面と受け取られエラーになる。 +必ず括弧と ``*`` の間に空白を入れること: ``( * )`` 。 + +環境によるサイズの違い +---------------------------- + +``int`` は環境によってサイズが異なることに注意してほしい。その環境での ``int`` の +上限と下限は ``Pervasives`` モジュール内の変数 ``max_int``, ``min_int`` に格納されている。 +64bit アーキテクチャでの ``int`` を 32bit アーキテクチャの OCaml プログラムに渡して +``int`` と解釈させると問題が発生する。(CR jfuruse: ほんと?) + +string_of_int x |> ... |> int_of_string の例 + +marshal の例 + +オーバーフロー、アンダーフロー +---------------------------------- + +``max_int``, ``min_int`` を超える演算結果はオーバフローおよびアンダーフローを起こすが、 +起こした事実は特にレポートされない。オバーフロー、アンダフローを起こしたか知りたい場合は +自分で何かしら検査コードを書く必要がある:: + + # max_int + 1 = min_int;; + - : bool = true (* オーバーフローが起こっている *) + + +``float`` +================================== + +倍精度(64bit)浮動小数点数。 IEEE754 や C で言うところの double である。 +単精度(32bit)浮動小数点数(いわゆる IEEE754 や C で言うところの float) は +OCaml には標準では用意されていない。 + +定数の書式 +---------------------------------- + +OCaml では定数(どころか他の全てもそうだが)にはオーバーローディングがない。 +そのため、 ``float`` はもし小数点以下が 0 だったとしても、 ``int`` との +混乱を避けるため、小数点を付けなければならない:: + + # 1.23;; + - : float = 1.23 + # 1.0;; + - : float = 1. + # 42.;; + - : float = 42. + +最後の ``42.`` のように少数点以下を書かない記法は他の言語ではエラーになる場合があるので注意が必要だ。 + +基本的な演算子 +---------------------------- + +``int`` と同様、Pervasives に基本的は演算子 +``(+.)``, ``(-.)``, ``( *.)``, ``(/.)`` などが宣言されている。 +演算子の最後に ``.`` が付いていることに注意。 OCaml ではオーバーローディングが無いため、 +``int`` の演算子 ``(+)``, ``(-)``, ``( * )``, ``(/)`` と同じ名前を +``float`` 用の演算子名として用いることができない。そのための苦肉の策である。 + +後述の ``int`` 以外の演算子でも普通の四則演算子を使う も参照のこと。 +CR jfuruse: link + +``nan`` +---------------------------------- + +``float`` の演算は IEEE754 に準拠するため、無限値や ``nan`` などが存在する。 +浮動小数点数がこれらの特殊な常態かどうかを調べるためには +``Pervasives.classify_float`` を使う。 + +正確な文字列表現 +---------------------------------- + +``float`` の内容を文字列に変換するには ``string_of_float`` を使うが、 +この関数は小数点以下12桁までしか文字列に変換しない:: + + (* pervasives.ml *) + let string_of_float f = valid_float_lexem (format_float "%.12g" f);; + +OCaml では ``float`` を完全に正確に文字列表現に変換することは難しいが、 +桁数を上げることで誤差を少なくすることは可能である。例えば Sexplib では +20桁まで出力を行なっている:: + + (* conv.ml of Sexplib *) + let default_string_of_float = ref (fun n -> sprintf "%.20G" n) + +正確に ``float`` を外部に出力・記録したい場合は、その 64bit 表現をそのまま +とり出さなければならない。 ``Pervasives.output_value`` や C言語による +補助関数の実装などが必要である(Endianness に注意すること)。 + + +``nativeint`` +============================= + +``int`` は OS がサポートする基本整数型の幅から 1bit 少ない範囲の整数しか +取ることが出きなかった。OS のシステムコールなどはフルに整数幅を使うことが多いため、 +OCaml でシステムプログラミングを行う際には ``int`` では不便なことが多い。 +(関連 OS 機能が整数の範囲としてフルサイズを使わないと確信できるときは ``int`` を +使ってももちろん構わない。) + +``nativeint`` は OS の基本整数型と同じ幅を持つ符号付き整数型であり、 +``int`` とは違い「失われた1bit」は無い。そのためシステムプログラミングに向いた整数型 +といえる。一方、 ``int`` が 1bit を犠牲とすることで unbox化された表現を持ち +高速な演算が可能であるのに対し、 ``nativeint`` はフルサイズで box化されたデータ表現となり、 +GC の対象となるため、 ``nativeint`` の計算は ``int`` と比べるとメモリ領域を多く使用するし、遅くなってしまう。 + +CR jfuruse: どれぐらいおそいか + +定数の書式 +---------------------------------- + +``nativeint`` の定数は整数の後に ``n`` をつける。すなはち:: + + # 1n;; + - : nativeint = 1n + # 0x1234n;; + - : nativeint = 4660n + (* 0oxxxn (8進), 0bxxxn (2進) も可能 *) + +``Printf`` 系フォーマット文字列では %nd %nx などやはり ``n`` を使う: + + # Printf.sprintf "%nd" 42n;; + - : string = "42" + # Printf.sprintf "%06nx" 123n;; + - : string = "00007b" + +基本的な演算子 +---------------------------- + +``nativeint`` の関数は Nativeint モジュールに定義されているが、 +四則演算が ``add``, ``sub``, ``mul``, ``div`` と二項演算子ではなく +普通の関数となっているなど、このままでは、不便である。 + +後述の ``int`` 以外の演算子でも普通の四則演算子を使う も参照のこと。 + + +``int32`` と ``int64`` +================================== + +``int32`` と ``int64`` は ``nativeint`` と同じく「失われた1bit」の +無い、 box化された符号付き整数型だが、幅はアーキテクチャに関係なく +``int32`` は 32bit、 ``int64`` は 64bit固定である。 + + +定数の書式 +---------------------------------- + +``int32`` は整数の後に ``l``, ``int64`` は後に ``oL`` と書く。 + + + # 1l;; + - : int32 = 1l + # 0x1234L;; + - : int64 = 4660L + (* 0oxxxl (8進), 0bxxxL (2進) も可能 *) + +``Printf`` 系フォーマット文字列では %ld %Lx などやはり ``l`` や ``L`` を使う: + + # Printf.sprintf "%ld" 42l;; + - : string = "42" + # Printf.sprintf "%06Lx" 123L;; + - : string = "00007b" + +基本的な演算子 +---------------------------- + +``int32`` と ``int64`` のための関数はそれぞれ Int32 と Int64 モジュールに +定義されている。四則演算が ``add``, ``sub``, ``mul``, ``div`` と二項演算子ではなく +普通の関数となっているなど、このままでは、不便である。 + +後述の ``int`` 以外の演算子でも普通の四則演算子を使う も参照のこと。 + + +``nat`` と ``big_int`` +==================================== + +``Complex.t`` +============================== + +OCaml の標準ライブラリには何故か複素数のためのモジュール ``Complex`` があり、 +``Complex.t`` として複素数の型が定義されている。実装は非常に素直:: + + type t = { re: float; im: float } + +実数(``re``)と虚数(``im``)部分を ``float`` で表した二つ組。 +この ``t`` に対して幾つかの基本的な +演算が用意されている。が、文字列への変換も文字列からの変換も存在しない。 +何のためにあるのかよくわからないモジュールである。 +OCaml の標準ライブラリには、中の人が自分が便利だからと +勢いだけで足してしまったこういうモジュールがいくつか有る。 +中の人の特権…とでも言おうか。 + +``int`` 以外の演算子でも普通の四則演算子を使う +================================================= + + +符号なし整数: ocaml-uint +================================================= diff --git a/bb/camlp4.rst b/bb/camlp4.rst new file mode 100644 index 0000000..68a2557 --- /dev/null +++ b/bb/camlp4.rst @@ -0,0 +1,881 @@ +CamlP4 も悲観的入門 + +.. contents:: + :local: + +=============================== +CamlP4 も悲観的入門 +=============================== + +必要なもの + +* OCaml 普通に書けるけど、そろそろ何かもうちょっと楽に書きたいなぁ、こう書ければ嬉しいなあと思い始めたアナタ。 +* 当然 LL(n) とか Parsec とか、原理はともかく、使ったことあるよね! +* OCaml ソースコード +* OCaml コンパイラ一式 + +この文章は OCaml 4.00.0 辺りを使って書かれている。あなたの使っている OCaml では例がそのまま +動かないかもしれない。 + +======================= +肝に命じること +======================= + +**CamlP4 はオーパーツもしくはロストテクノロジー。** 全部を理解しようとしてソース本体を読み始めると魂を取られ帰ってこれなくなる。真髄は理解できないものとして、便利に使う、使えなさそうならさっさと諦める、という姿勢が重要。 + +============================================ +OCaml の文法拡張フレームワークとしてのP4 +============================================ + +P4 は OCaml の文法を拡張することが出来るだけでなく、独自言語のパーサーやプリティプリンタを記述することもできる。できるが時間の無駄だからやめろ。Lex(OCamlLex/ULex)+Yacc(Menhir) があるじゃないか。 + +というわけで OCaml の文法を拡張する道具としての P4 についてしか述べない。 + +=============================================== +できないことを知ろう +=============================================== + +P4 で出来ないこと。夢想しても逆立ちしても出来ないこと。 + +* Lexer を変更してオレオレリテラル書きたい! => 諦めろ。 えっでも書けるようなドキュメントが => 諦めろ。pa_xxx.cmo を積み重ねる方式の P4 での文法拡張では常にバニラ Lexer が使用される +* 型チェックした後の情報から…  => 諦めろ。P4 は型付け前、パース段階で AST をいじるフレームワークなので、型付け情報は扱えない。でも… => じゃあ P4 内部で自分で型推論器実装してみればなんとかなるはずなので勝手にやってくれ + +======================================= +どう動作するのか +======================================= + +* P4 は改造構文を含む OCaml のソースコードをパースルールを使用してパースする +* パースしながら改造構文の無い vanilla OCaml(以下 *バニラ*)のソースコードツリー(以下 *AST*)を生成する +* コードをパース終了した後にも AST を変形させることができる (*Filter*) +* 最終的にバニラコードを出力する + +君の仕事は + +* P4 が提供するバニラのパースルールを基に +* 文法拡張のためのパースルールを追加し +* 文法拡張部分や Filter のための AST 生成器を書き +* それを P4 ランタイムに登録する + +ことになる + +================================================= +3.09以前型と 3.10以降系 +================================================= + +多分あなたが触っている P4 は 3.10以降系。これは OCaml のバージョンを見ればわかる。 +これは 3.09以前系と結構中身が違う。困ったことに CamlP4 のチュートリアルは 3.09以前系の物が +インターネットに結構転がっており、これを読みながら 3.10以降系を使ってみて惨めにハマるという +若者が跡を絶たない。何か読む際にははっきりと 3.10以降系と明示されたものを読むこと。 + +ちなみに、この文章は 3.10以降系の P4 について書かれてある。 + +================================================= +Original Syntax, Revised Syntax なんと複雑な! +================================================= + +P4 にはバニラ OCaml の文法である *Original syntax* とは別に *Revised syntax* という別の OCaml 文法が実装されており、これが P4 のオーパーツ化の始まりとなっている。注意して欲しいのは + +**Revised syntax は君の OCaml プログラミングを revise する物ではない** + +ということだ。実際のところ Revised syntax は Original syntax と比べて人間の目には冗長に見える。Original syntax を知っている人には違いを覚えるのも大変である。知らない人は Original syntax も覚えなければいけないのでもっと大変である。 + +Revised syntax が有利になるのは P4 での拡張を *Quotation* (*Quote* 後述) を使って書く場合だけ。 +Revised は P4 のための DSL として作られており、枠構造が明確になっており Quote が書きやすい場合がある。 + +では Revised syntax を習得するべきか。 **否。Revised syntax は無視してよろしい。というか無視しろ。** +この文法は P4 でしか役に立たないので覚えるのは時間の無駄である。Original syntax では Quote が書けなくなるじゃないですか…という人には、 +Quote を使わなくても P4 は書ける、と答えよう。 +Revised syntax でうんうん唸るなら、 Original syntax を使って、書けない、書きにくい Quote が現れたらそこはベタに AST コンストラクタを書く(*生書き*)。 +もちろん Quote が書けてそちらの方が簡便な場合は Quote を使おう。Revised syntax は何となく読めればよい。それが近道だ。 + + +camlp4, camlp4o, camlp4of, camlp4oof, camlp4orf, camlp4r, camlp4rf … ナメてんのか! +====================================================================================== + +Quote の外と中の言語を上記の Syntax のどちらで書くか、そして reflective であるかどうか(言語拡張が Quote 内部にも適用されるべきか)の違いにより、 P4 には 2x2x2 = 8 種類のバリアントが想定される、そのため P4 には沢山のコマンドがある。君が使うべきはまず一つ、:: + + camlp4of + +である。Quote の外と中両方共 Original syntax で reflective なものだ。 **それ以外は忘れろ。** + +演習問題 +------------------ + +* 100行程度の x.ml という OCaml バニラソースを用意せよ。無ければ書け。 +* ``camlp4of x.ml`` を実行して出力を確認せよ。 +* ``camlp4 x.ml`` を実行して出力を確認せよ。エラーが出た場合、それは何故か考えよ。 +* **[重要]** ``camlp4of x.ml > xx.ml`` を実行し xx.ml を確認せよ。 +* **[重要]** ``camlp4of -printer Camlp4OCamlPrinter x.ml > xxx.ml`` を実行し xxx.ml を確認せよ。 + +解説: P4 は OCaml コンパイラのプリプロセッサとして動作させることが多い。P4 と ocamlc の間でのソースのやり取りはわざわざ人間に読める OCaml コードを出力する意義は無いのでバイナリで行われる。 出力先がターミナル以外の場合、プリンタを明示しないと P4 がバイナリを吐くのはそのためである。 + +============================================= +もう複雑ではなくなったね! +============================================= + +* Original syntax 一本! +* コマンドは常に camlp4of! +* Quote で迷ったら生書き! + +そう決めたらかなり見通しが良くなったはずだ。次に進もう。 + +============================= +文法拡張のテンプレート +============================= + +真似をしていれば良いのだ +=============================== + +なぜかは聞かず、このテンプレを使う:: + + open Camlp4 + + module Id : Sig.Id = struct + let name = "pa_XXX" (* change *) + let version = "1.0" (* change *) + end + + module Make (Syntax : Sig.Camlp4Syntax) = struct + open Sig + include Syntax + open Ast + + (* 文法拡張部 *) + + end + + let module M = Register.OCamlSyntaxExtension(Id)(Make) in () + +* ``Id`` は拡張の名前とかバージョンとかを書く。ありがたかったことがない +* ``Make`` という functor は OCaml シンタックスパーサーモジュール ``Syntax : Sig.Camlp4Syntax`` + をもらってそのモジュールを基に同じ型( ``Sig.Camlp4Syntax`` )のモジュールを生成して返す。 + この functor を積み重ねる方式により、複数の文法拡張を同時に使用することができる。 + 当然、変な変更を積み上げるとまともに使えない文法になるが気にしてはいけない。 +* 文法拡張部はここでは触れない +* 最後に ``Register.OCamlSyntaxExtension`` を使ってこの ``Make`` を登録する。結果のモジュール ``M`` に特に使い道はない。 + +演習問題 +------------------ + +* **[重要]** 上のテンプレを camlp4temp.ml に保存し ocamlc でコンパイルせよ。コマンドは ``ocamlc -pp camlp4of -I \`ocamlc -where\`/camlp4 -c camlp4temp.ml`` +* **[重要]** 上記コンパイルコマンドを一々打ち込まなくても良いよう、自分の使用しているビルドツールのルールを作成せよ。 + +=================================== +Quotation と Anti-quotation +=================================== + +さて、テンプレに触れたから早速文法拡張に移りたいところだが…その前に Quotation system を見なければならない。少し落ち着け。Quote system とは OCaml 内部で OCaml の syntax tree (*AST*) を OCaml ソースの形で記述できるようにするための言語内 DSL だと思って良い。AST を簡単にいじるために必須なツールだ。 + +Quotation +============== + +P4 では *Quasi-quotation* (*Quote*) が使える。Quote を使えば、言語 AST の内部表現を書く(生書きとでも呼ぼう)代わりに、より人間様に判りやすい言語ソースをそのまま書くことができる。 + +例えば、P4 での空リスト ``[]`` 式の内部表現は:: + + Ast.ExId (_loc, (Ast.IdUid (_loc, "[]"))) + +であるが、Quote を使えば:: + + <:expr<[]>> + +と書くことができる。空リストパターンの内部表現は:: + + Ast.PaId (_loc, (Ast.IdUid (_loc, "[]"))) + +であるが、Quote を使えば:: + + <:patt<[]>> + +で済む。残念ながら Quote 内部のソースコード片をパースさせるコンテクスト(``expr``, ``patt`` などは)は明示しなければならない。 + +演習問題 +----------------- + +* ``let _ = <:expr<[]>>`` というファイルを作り、 camlp4of で出力して、 Quote が内部で何に展開されているか確認せよ。 +* ``let _ = <<[]>>`` というファイルを作り、 camlp4of で出力して、出力を確認せよ。結果は役に立つのでメモしておくこと。使える expander のリストが手に入った! + + +Quote が展開される AST の定義 +================================= + +さて、Quote が AST 内部表現に展開される例を見たが、そこで出てくる ``Ast.ExId`` やら ``Ast.IdUid`` はどこで定義されているか。どのようなコンストラクタがあるか。もっとも簡単な資料は OCaml ソースコードディレクトリ(*$OCAML と略記*)の ``$OCAML/camlp4/Camlp4/Camlp4Ast.partial.ml`` である。これは Revised syntax で記述されており、なおかつこのファイル自体が P4 が作成される際にコンパイルされるわけではないのだが、もっとも判りやすい。ここに定義された型名は Quote ``<:XXX< ... >>`` のコンテクスト名 ``XXX`` として使用できる。 + +Revised syntax でのバリアント定義の読み方だが例えば、:: + + | StExt of loc and string and ctyp and meta_list string + +であれば、Original syntax の :: + + | StExt of loc * string * ctyp * string meta_list + +に相当する。読み替えはそれほど難しくはないはずだ。 + +Camlp4Ast の各コンストラクタは一応コメントされているもののその使用方法はよくわからないことが多い。例えば、:: + + and ctyp = + [ ... + | TyApp of loc and ctyp and ctyp (* t t *) (* list 'a *) + ... + +これはどうやら引数を持つデータ型の適用のためのコンストラクタである(実際そうだ)。コメントも Revised syntax で書かれているので ``list 'a`` とは ``'a list`` のことである。さて、``TyApp`` は二つの ``ctyp`` を取るが、 ``'a list`` の場合どちらが ``'a`` でどちらが ``list`` か。 ``('a, 'b) Hashtbl.t`` の場合は ``('a, 'b)`` をどうエンコードするのか。云々。 ``Camlp4Ast`` には時にドキュメントされていないインバリアントがあり、 Ast として型のあった式を作成しても P4 のバニラ出力時に拒否されてしまうことがある。 + +どうしたらよいか。 **例を camlp4of で展開して確かめるのが最も良い。** 次の演習をやりなさい。 + +演習問題 +------------------ + +* ``<:ctyp< 'a list >>`` +* ``<:ctyp< ('a, 'b) Hashtbl.t >>`` +* ``<:ctyp< int list option >>`` +* **[重要]** これらを camlp4of で展開してどのような AST ツリーになるか確認せよ + +``_loc`` とは「例の場所」 +========================== + +Quote 展開例でしばしば見られる自由変数 ``_loc`` は式の場所を指す。この自由変数はもっと外のパターンで Quote を使っている限り、自動的に束縛されることになっているので Quote を使っている限りは気にすることはない。ただし、 Quote を使わず生書きする場合は少し注意する必要がある。 + +Quote では ``_loc`` を書く必要は無いが、明示的書きたい場合があるその場合は:: + + <:patt@myloc<[]>> + +の様に書く。 + +演習問題 +------------------------ + +* ``<:patt@myloc<[]>>`` の quote 展開を確認せよ + + +テンプレ内部での Quote +============================ + +Quote の展開例で見たように、 quote 展開では ``Camlp4Ast.partial.ml`` に記述されたコンストラクタが ``Ast.`` を付けて使用される。(例えば ``Ast.PaId``) これを P4 文法拡張で使用する際には、テンプレの「文法拡張部」で ``Ast`` という名前のモジュールにアクセスできるようになっていなければならない。 + +実際には functor パラメータ ``Syntax`` に ``Ast`` モジュールがある (すなわち ``Syntax.Ast``)。この ``Syntax.Ast`` を ``Ast`` としてアクセスするためには ``Syntax`` を open するか include する必要がある。実際の P4 文法拡張においては ``Syntax`` モジュールを変更し新しい ``Syntax`` を創りだす場合が多いので ``include Syntax`` を見ることが多い。 + +演習問題 +-------------------- + +* テンプレコードの「文法拡張部」に ``let _ = <:expr<[]>>`` と書いてコンパイルを試みよ。コンパイルコマンドは前の演習問題でビルドスクリプトに記録してあるはずだ。 +* なぜ失敗するか、 camlp4of で quote 展開結果を確認せよ +* 自由変数 ``_loc`` をλ抽象で適当になんとかして再度コンパイルを試みよ + +Anti-quotation +==================== + +*Anti-quotation(Anti-quote)* は Quotation の中に外部の値を導入するための Quote の中の Quote。 +書式は ``$ 式 $`` と書く。例えば ``<:expr< $x$ + 1 >>`` と書けば、``x`` に束縛された ``expr`` 型を持つ +AST からそれにさらに 1 を足すという expr AST を作ることができる。例えば、:: + + fun _loc -> (* Quote 内部で _loc が使われているため *) + let x = <:expr< 42 >> in + <:expr< $x$ + 1 >> + +というコードは ``42 + 1`` に相当する AST を生成する。簡単である。 + +Anti-quotation の ``$XXX: 式$`` 記法 +------------------------------------- + +さて、``42`` という整数式を埋め込む例を上で見たが、ではこんどは、 +この整数を自由に変化させるにはどうするか?関数で ``int`` をもらうべきだ。こうだろうか:: + + let make_add_1 _loc x = <:expr< $x$ + 1 >> + +うーん、これだと ``x`` の型は整数 ``int`` ではなく式 ``expr`` になってしまう。 +``CamlAst`` 以外の型(``int`` や文字列)の値を Anti-quotation で埋め込むにはどうしたらよいのだろう。 +もちろん常に生書きすることはできる:: + + let make_add_1 _loc x = <:expr< $Ast.ExInt (_loc, string_of_int x)$ + 1 >> (* x の型は int *) + +しかしこれは面倒だ。こんな場合のために P4 には ``$XXX: 式$`` という Anti-quotation 記法がある(これが全然ドキュメントされてないのだ…)。この記法を使うと上の式は次のように書き換えることができる:: + + let make_add_1 _loc x = <:expr< $int: string_of_int x$ + 1 >> (* x を string に変換…。ほんとは int をそのまま埋め込みたいんだけど… *) + +``$int: x$`` は ``x`` は ``string`` なんだけどそれをよろしくコンテクストに合う AST に変更しちゃってください、という意味だと思えば良い。もっとかっこよく言うと「ホスト言語(Quote の外側)の値から、埋め込み言語(Quote の内側)の AST への変換子」だ。この変換子は(おそらく)次のものが使用できる:: + + <:expr< $x$ >> (* 普通。x がそのまま使われる *) + <:expr< $id:x$ >> (* x : ident を expr にする *) + <:expr< $lid:x$ >> (* x : string を IdLid の ident expr にする *) + <:expr< $uid:x$ >> (* x : string を IdUid の ident expr にする *) + <:expr< $str:x$ >> (* x : string を 文字列 expr にする *) + <:expr< $int:x$ >> (* x : string を 整数 int として解釈し、expr にする *) + <:expr< $int32:x$ >> (* 略 *) + <:expr< $int64:x$ >> (* 略 *) + <:expr< $nativeint:x$ >> (* 略 *) + <:expr< $flo:x$ >> (* x : string を 浮動小数点 expr にする *) + <:expr< $chr:x$ >> (* x : string を 文字 expr にする *) + +``$XXX: 式$`` の ``XXX`` 部分は ``Camlp4Ast`` のコンストラクタ名 ``ExXXX`` から来ている。 + +演習問題 +------------------ + +* **[重要]** 上記例を camlp4of で展開し(ry + +パターン中での anti-quotation +-------------------------------- + +Anti-quotation はパターンの中でも重要。AST 内部の情報を手軽に変数に束縛することができる。 +例えば:: + + match ast with + | <:expr< $x$ + 1 >> -> x + | _ -> ast + +は ast を受け取り、もし ``○ + 1`` という形であれば ``+ 1`` を剥ぎとり、それ以外は ast 自身を返す操作を行う。 Anti-quotation で ``x`` に AST が束縛されることに注意。 + +パターン中でも ``$XXX: パターン$`` という書式が使える。「埋め込み言語のASTから、ホスト言語の値への変換子」だ。:: + + match ast with + | <:expr< $int: x$ + 1 >> -> <:expr< $int: x + 1$ >> + | _ -> ast + +これはもし ast が例えば ``42 + 1`` という形であった場合、 ``43`` にたたみ込む。 + +パターンマッチでの ``$x$`` と ``$XXX: x$`` の使い分けは時に注意が必要だ。 ``$x$`` ではどんな AST でもマッチしてしまう。もし変数だけマッチさせたければ ``$x$`` ではなく ``$lid: x$`` と書かなければいけない。次の式は意味的に間違い:: + + match ast with + | <:expr< x >> -> prerr_endline "The variable x!!!" + | <:expr< $_$ >> -> prerr_endline "A non-x variable" (* 変数どころか全部マッチしちゃう *) + | _ -> prerr_endline "Something else" + +こう書かねばならない:: + + match ast with + | <:expr< x >> -> prerr_endline "The variable x!!!" + | <:expr< $lid: _$ >> -> prerr_endline "A non-x variable" (* 変数だけマッチ *) + | _ -> prerr_endline "Something else" + +なお、 ``$`` は OCaml では普通に使える symbol character なのだが、camlp4of では +``$`` が Anti-quote のために予約されているため $ は使えなくなってしまう。なので ``$`` を OCaml で +使うのは避けよとは言わないが、注意しておくべし。 + +演習問題 +------------- +まだ書いてない。 + +Original syntax ではうまく書けない Quotation +================================================ + +なぜ Original と Revised syntax という二つの文法があるのか、 +それは Original syntax だと Quotation がうまく書けない場合があるからだ。 +次の ``sig ... end`` のためのコンストラクタを見てみよう:: + + (* sig sg end *) + | MtSig of loc and sig_item + +さて、これを使って ``sig .. end`` にマッチするパターン ``MtSig(_loc, sg)`` なのだが、 +これに相当する Quotation を書こうとすると…書けない。:: + + <:module_type< sig $sg$ end >> + +は:: + + Ast.MtSig (_loc, (Ast.SgSem (_loc, y, (Ast.SgNil _loc)))) + +に展開される。なんですかこの ``SgNil`` は?!? +Revised syntax ならば:: + + value x = <:module_type< sig $sg$ end >>; + +は camlp4rf を使えばちゃんと:: + + let x = Ast.MtSig (_loc, sg) + +に展開されるのに… + +ここに Original syntax にこだわると Quotation で難儀する原因がある。 +**Original syntax の Quote は、いくつかのリストの形をした AST コンストラクタを最小の形で記述することができないのだ。** +これは直せるバグで、もしかするとあなたの OCaml では既に直っているかもしれない。 +が、困ったことに他にも複数こういう場所がある。 + +さて、これがすごく問題かというとそうでもない。 +パターンと同様、式においても Quotation ``<:module_type>`` は +``SgNil`` のある式に展開されるし、 camlp4of が ``sig .. end`` という OCaml ソースを +読み込んだ時もやはりこの ``SgNil`` が最後にくっついてくるからだ。(多分。希望である。) + +これが原因で Original syntax で ``module_type`` の Quote を使ったパターンマッチを書くと +``Ast.MtSig (_, _)`` のケースが押さえることが出来ず non exhaustive になってしまう。 +これが気になる場合はデフォルトケースでエラーにするか、Quote を使わず ``MtSig`` のケースを生書きするか、 +ともかくちょっとした工夫が必要になる。 + +例題: 定義された型を抜き出す +================================= + +OCaml のインターフェースファイル mli の P4 でのパースツリーの型は ``sig_item`` という +型である。(``Camlp4Ast`` 参照) この型の値を受け取り、 mli 内部で定義されている型の名前の +文字列を全て抜き出したい。 ``sig_item -> string list`` という型を持つ +関数 ``extract_defined_type_names`` を作成する。 + +これを実直にやるならば単に ``sig_item`` の型の定義を見ながらパターンマッチを +行なって全てのノードを辿る関数を書くだけ。普通にトラバーサルして末尾再帰:: + + let extract_defined_type_names sg = + let rec ext_sig_item st = function + | SgNil _loc -> + | ... + ... + in + ext_sig_item [] sg + +なのだが、それでは読みづらいし、せっかくなのでパターンに Quote を使ってみよう:: + + (* pa_extract_types.ml *) + open Camlp4 + + module Id : Sig.Id = struct + let name = "pa_XXX" (* change *) + let version = "1.0" (* change *) + end + + module Make (Syntax : Sig.Camlp4Syntax) = struct + open Sig + include Syntax + open Ast + + let extract_defined_type_names sg = + let rec ext_sig_item st = function + | <:sig_item< >> -> st + | <:sig_item< class $_$ >> -> st + | <:sig_item< class type $_$ >> -> st + | <:sig_item< $sg1$ $sg2$ >> -> List.fold_left ext_sig_item st [sg1; sg2] + | <:sig_item< #$_$ >> -> st + | <:sig_item< exception $_$ >> -> st + | <:sig_item< external $_$ >> -> st + | <:sig_item< include $_$ >> -> st + | <:sig_item< module $m$ : $mty$ >> -> ext_module_type st mty + | <:sig_item< module rec $module_binding$ >> -> ext_module_binding st module_binding + | <:sig_item< module type $_$ = $_$ >> -> st + | <:sig_item< open $_$ >> -> st + | <:sig_item< type $ctyp$ >> -> ext_ctyp st ctyp + | <:sig_item< val $_$ >> -> st + | Ast.SgAnt _ -> assert false + and ext_module_type _ _ = assert false (* 未実装 *) + and ext_module_binding _ _ = assert false (* 未実装 *) + and ext_ctyp _ _ = assert false (* 未実装 *) + in + ext_sig_item [] sg + + end + + let module M = Register.OCamlSyntaxExtension(Id)(Make) in () + +``Camlp4Ast`` を見ながらこんなのを書いてみた。各行が ``sig_item`` の各コンストラクタに対応している。Antiquote のケースは良くわからないので Quote を使わずに ``Ast.SgAnt`` と普通に書いてみた。 +ひとつひとつを生書きするよりは読みやすいことがわかるだろう。さてこれを:: + + ocamlc -pp camlp4of -I `ocamlc -where`/camlp4 -c .ml pa_extract_types.ml + +でコンパイルしてみると:: + + File "pa_extract_types.ml", line 21, characters 29-32: + While expanding quotation "sig_item" in a position of "patt": + Parse error: ":" expected after [a_LIDENT] (in [sig_item]) + + File "pa_extract_types.ml", line 1: + Error: Preprocessor error + +へ?なんでっか? ``<:sig_item< external $_$ >>`` の部分で文句を言われた。 ``external …`` に対応おするデータは:: + + | SgExt of loc and string and ctyp and meta_list string + +となっている。 ``string``, ``ctyp``, ``meta_list string`` と ``loc`` を除いて3つ引数を取っているが、 +これは実際の ``external`` の文法:: + + external foobarboo : int -> int = "foobar" "option" + <--ctyp--> + +に対応している、そしてこれらはどれも省略できない。 +``<:sig_item< external $_$ >>`` は ``external`` の後一つしか引数がない。残り2つを忘れていた +ために起こったエラーだ。書き換えよう:: + + | <:sig_item< external $_$ : $_$ = $_$ >> -> st + +なるほど。たしかに途中の記号は略してはいけなさそうだ。再コンパイルしよう:: + + File "pa_extract_types.ml.ml", line 28, characters 24-27: + While expanding quotation "sig_item" in a position of "patt": + Parse error: ":" expected after [a_LIDENT] (in [sig_item]) + + File "pa_extract_types.ml.ml", line 1: + Error: Preprocessor error + +ありゃ?今度は ``<:sig_item< val $_$ >>`` だ。ああ、これも ``val x : type`` に相当する Quote だからニ引数にしてちゃんと記号を書いてあげよう:: + + | <:sig_item< val $_$ : $_$ >> -> st + +これでどうか?:: + + File "pa_extract_types.ml.ml", line 14, characters 30-1080: + Warning 8: this pattern-matching is not exhaustive. + Here is an example of a value that is not matched: + SgDir + (_, _, + (ExId (_, _)|ExAcc (_, _, _)|ExAnt (_, _)|ExApp (_, _, _)|ExAre (_, _, _)| + ....) + File "pa_extract_types.ml.ml", line 26, characters 19-19: + Warning 11: this match case is unused. + +今度はパターンが完全に埋まっていないですと言われた。 ``SgDir`` だからこの部分だ:: + + | <:sig_item< #$_$ >> -> st + +``SgDir`` は ``loc`` 以外に 2引数を取っているのに 1引数しか書いていなかった。これも 2引数にしなければ。しかし何故今回はエラーではなく警告なのか。これは、directive の文法では第2引数は省略できるからだ。つまり、 ``<:sig_item< #$_$ >>`` は第2引数を省略した正しい構文だが、第2引数があるケースは押さえられない。この場合は、第2引数も明示する:: + + | <:sig_item< #$_$ $_$>> -> st + +さあ、コンパイル…やっと完全なパターンマッチになったようだ。(大抵の場合 P4 のモジュールではここまで完璧なパターンマッチは必要なく、興味のないケースについてはデフォルトケース ``| _ ->`` で全て押さえてしまうのが一般的だが、もし自分の書いた Quote パターンが意図したケースをちゃんと処理してくれない場合、このような分析が必要になる。 + +さて… ``ext_sig_item`` については実装できたので、のこりの ``ext_*`` 関数を ``assert false`` からちゃんとした実装のものにしよう:: + +Quotation system まとめ +============================ + +* **[重要]** Quote や Anti-quote の展開がわからなかったら例題を作って camlp4of で実際にどうなるか確かめよう +* **[重要]** Quote/Anti-quote は syntax sugar。使いづらいと思ったら迷わずすぐに Camlp4Ast のコンストラクタを生書きしよう。 +* **[重要]** ``$x$`` と ``$XXX:x$`` は違う。特にパターンでの ``$_$`` と ``$XXX:_$`` の間違いが致命的なので注意しよう + +================================= +文法拡張 +================================= + +やっと肝心の文法拡張について述べることができる。 + +P4 パーサーの性質 +===================== + +CamlP4 のパーサーは、LALR(1) スタイルによるバニラOCamlの文法定義( ``$OCAML/parsing/lexer.mll`` と ``$OCAML/parsing/parser.mly`` )とは別に実装された、 LL(?(シラネ)) スタイルのパーサーである。わしはパーサー技術のことはよく知らんから適当なことを今から言うが、CamlP4 が LL という性質の違うパーサ技術を採用しているのは lex/yacc ではダイナミックな変更が難しいからだと思われる。LL は Parsec でみなさんおなじみの通りそのままコードを書けばよろしい。各文法要素をパースする LL の部品に名前をつけておいて、その名前で部品にアクセス、それを消したり、上書きしたり、新しい部品を追加したり…普通の(どちらかというと継承っぽい)プログラミングスタイルが使える。 + +P4 では LL のパーサーを書くためのストリームパターンマッチのための DSL が用意されている。(この特殊文法も P4 で書かれているとかまあ再帰っぽいのでワクワクするかもしれないが時間の無駄なのでさっさと先に進もう) + +拡張を伴わない文法ルールの基礎 +================================= + +LL ではあるがルール記述は yacc の様なちょっと BNF っぽい書き方ができる DSL が用意されている: + +* ``ルール名: [ ケースグループ | ケースグループ | .. ]`` +* ``ケースグループ: [ ケース | ケース | .. ]`` もしくは ``"名前" associativity [ ケース | ケース | .. ]`` ( ``名前`` と ``associativity`` (``LEFTA`` など) は省略できる) +* ``ケース: ストリームパターン -> Camlp4Ast を生成する式`` +* ストリームパターン内で自分自信を参照する場合は ``SELF`` を使う。(これは継承時の自己参照に必要からだと思われる) + +Yacc と異なりケースの実行は上から下へ。なので順番は重要。 + +わかりますよね?わからない?えっ、パーサーの基礎も知らずに P4 とか無理ですよ?言わなかったけ。 + +改造の基本 +============== + +テンプレ、もしくは誰かが書いた pa_XXX.ml からはじめる。スクラッチするのはめんどくさい。 + +文法拡張はテンプレの「文法拡張部」の部分に ``EXTEND Gram ... END`` という枠内で書く。 + +文法拡張はテンプレの ``Syntax`` という functor 引数内部で既に定義された文法ルールを基に +新しいルールセットを作ることで行う。そのために、 + +* 文法ルールのケースグループ全体を消してしまう: ``Gram.Entry.clear`` +* 文法ルールの中のケースを消してしまう: ``DELETE_RULE Gram 名前: パターン END;`` で消す。パターンはケースを選択するために必要 +* ``EXTEND Gram ... END`` を書いてその中に付け加えるルールやケースを書く + * ``GLOBAL`` 宣言で自分が今からいじりたい既存の文法ルールを列挙せよ! なんで必要なのか、理由は分かんねー。 + * 既存の文法ルールの中のケースの前後に新しいケースを付け加える: 普通に追加分だけのケースをルールに書いておくと、既存のケースの最後にルールが付け加えられる + * ``文法ルール名: BEFORE "hogehoge" [ ケースグループ ] ;`` などと書いてケースグループを挿入する場所を指定することもできる。指定しなければ一番最後に加えられる。 ``BEFORE`` (すぐ前)の他に ``AFTER`` (すぐ後), ``LEVEL`` (同じところ?) などが使える。 + + +元の文法はどこに定義されていますか +======================================= + +わかりました。では、私が変更することになる大本の OCaml 文法はどこで調べたら良いですか? +もっともな疑問であるが…正直これが大変であり、P4 の近寄り難さを演出している + +* 基本的な文法はまず Revised syntax のルールとして定義されている(なんてこった!) ``$OCAML/camlp4/Camlp4Parsers/Camlp4OCamlRevisedParser.ml`` +* バニラOCaml の文法はこの f#$@ing な Revised syntax の文法拡張として定義されている!! ``$OCAML/camlp4/Camlp4Parsers/Camlp4OCamlParser.ml`` +* そしてこいつらは全て Revised syntax で書かれている!! (オーノー) + +camlp4rf を使ってやれば Revised syntax で書かれているコードをバニラで読むことは出来るのだが… Quote が全て展開されているのでかなりきつい。まあここは Revised syntax を理解してなくても空気で読むしか無い… + +Camlp4OCamlParser.ml を眺めよ!! +==================================== + +とこき下ろしたが、実のところ Camlp4OCamlParser.ml は最も複雑な P4 文法拡張なので、ルールの削除や追加の例はこのファイルを眺めるのが最適である。 **読むなよ!眺めるだけだ!** + +========================= +例題 +========================= + +君は Scheme か LCF ML 基地外なので ``let rec`` という OCaml 構文を見ていつも引っかかりを覚えていた。何故だ、何故 ``let rec`` と中央に空白があるのだ。 ``letrec`` でなければいけないはずだ... ついに君は CamlP4 拡張を書き始めた。 + +やるべきこと + +* ``letrec`` という文法ルールを入れる! ``let rec`` のコピペでいいはずだ。 +* ``let rec`` という文法ルールは潰す! + +ステップ1 ルールを探す +============================= + +``let rec`` に関する P4 のルールを探そう。 +``Camlp4OCamlParser.ml`` には、見当たらない。 +では ``Camlp4OCamlRevisedParser.ml`` か?あった:: + + opt_rec: + [ [ "rec" -> <:rec_flag< rec >> + | `ANTIQUOT ("rec"|"anti" as n) s -> Ast.ReAnt (mk_anti n s) + | -> <:rec_flag<>> + ] ] + ; + +これはどうやら ``rec`` という文字列があれば ``<:rec_flag< rec >>`` を返し、それ以外は ``<:rec_flag<>>`` を返すようだ。え? ``ANTIQUOT``? 知るか。残念だがここは変える所ではないらしい。 ``opt_rec`` を使っているルールを探そう...今度は ``Camlp4OCamlParser.ml`` にあった。(``Camlp4OCamlRevisedParser.ml`` にある ``opt_rec`` を使うルールは ``Camlp4OCamlParser.ml`` で ``DELETE_RULE`` により抹消されているので気にする必要はない):: + + str_item: + [ "top" + [ "let"; r = opt_rec; bi = binding; "in"; x = expr -> + <:str_item< let $rec:r$ $bi$ in $x$ >> + | "let"; r = opt_rec; bi = binding -> + match bi with + [ <:binding< _ = $e$ >> -> <:str_item< $exp:e$ >> + | _ -> <:str_item< value $rec:r$ $bi$ >> ] + | ... + + expr: LEVEL "top" + [ [ "let"; r = opt_rec; bi = binding; "in"; + x = expr LEVEL ";" -> + <:expr< let $rec:r$ $bi$ in $x$ >> + | ... + +ステップ2 ルールを消す +=============================== + +では、まずこの汚らわしい ``let rec`` ルールを消そう。消すのには ``DELETE_RULE`` 。 ``Camlp4OCamlParser.ml`` の例を参考にして...:: + + DELETE_RULE Gram str_item: "let"; opt_rec; binding; "in"; expr END; + DELETE_RULE Gram str_item: "let"; opt_rec; binding END; + DELETE_RULE Gram expr: "let"; opt_rec; binding; "in"; expr END; + +ステップ3 **[重要]** テストテストテスト +============================================= + +と君は書いてみた。君は基地外ではあるが慎重でもあるので、この時点でテンプレートにこの三行を書き込みテストするのを忘れない:: + + open Camlp4 + + module Id : Sig.Id = struct + let name = "pa_letrec" + let version = "1.0" + end + + module Make (Syntax : Sig.Camlp4Syntax) = struct + open Sig + include Syntax + open Ast + + DELETE_RULE Gram str_item: "let"; opt_rec; binding; "in"; expr END; + DELETE_RULE Gram str_item: "let"; opt_rec; binding END; + DELETE_RULE Gram expr: "let"; opt_rec; binding; "in"; expr END; + + end + + let module M = Register.OCamlSyntaxExtension(Id)(Make) in () + +コンパイルしてみる:: + + ocamlc -pp camlp4of -I `ocamlc -where`/camlp4 -c pa_letrec.ml + +では実際に使ってみよう:: + + $ ocaml + OCaml version 4.00.0 + + # #load "dynlink.cma";; + # #load "camlp4of.cma";; + Camlp4 Parsing version 4.00.0 + + # #load "pa_letrec.cmo";; + Fatal error: exception Not_found + +あれ?あれれれ?ナンデ?P4ナンデ? + +まあ極まった私から言わせてもらうと、 ``DELETE_RULE`` する際にマッチするルールが無かったんだろう。よく見てみると最後のルール間違っている:: + + DELETE_RULE Gram expr: "let"; opt_rec; binding; "in"; expr LEVEL ";" END; + +と、 LEVEL についても書かないといけないのだ。うーん、トリッキー。これで実行すると:: + + # #load "pa_letrec.cmo";; + # let rec f x = f x;; + Characters 0-3: + let rec f x = f x;; + ^^^ + Error: Parse error: *"module" or "open" expected after "let" (in [str_item])* + +こうなる。なんとエラーメッセージを見なさい。 ``let`` の後は ``module`` か ``open`` しか来ないと言っている。 ``rec`` なんかは絶対来ないのだ!素晴らしい!(というか普通の ``let x = 1`` とかも消してしまったのだが。) + +ステップ4 ルールを追加する +=============================== + +さて、仕事は半分終わった。残りは letrec だ。これは消したルールを基に作れる。まず拡張し終わって出来たパースルール群がどうなるか考えよう:: + + str_item: + [ "top" + [ "let"; bi = binding; "in"; x = expr -> + <:str_item< let $bi$ in $x$ >> + | "letrec"; bi = binding; "in"; x = expr -> + <:str_item< let rec $bi$ in $x$ >> + | "let"; bi = binding -> + match bi with + [ <:binding< _ = $e$ >> -> <:str_item< $exp:e$ >> + | _ -> <:str_item< let $bi$ >> ] + | "letrec"; bi = binding -> + match bi with + [ <:binding< _ = $e$ >> -> <:str_item< $exp:e$ >> + | _ -> <:str_item< let rec $bi$ >> ] + | ... + + expr: LEVEL "top" + [ [ "let"; bi = binding; "in"; + x = expr LEVEL ";" -> + <:expr< let $bi$ in $x$ >> + | "letrec"; bi = binding; "in"; + x = expr LEVEL ";" -> + <:expr< let rec $bi$ in $x$ >> + ... + +こんな感じになるはずだ。 +オリジナルのルールをそれぞれ二つにわけ、 ``let`` と ``letrec`` にし、バニラ側で非再帰、再帰の ``let`` に置き換えてやる。 +元ソースでは Revised syntax だったがこちらは Original に書き換えてある。Quote の中身ではバニラ OCaml を書かねばならないから let rec と書かざるを得ないが…ぐぐぅ。そこは革命のためだ我慢せよ。 + +さてこれを拡張ルールとして書くには + +* str_item と expr をいじることを GLOBAL で宣言する +* let(非再帰)と と letrec のケースを追加する。追加するレベルを LEVEL で明記する。 + +せねばならない。こう書く:: + + GLOBAL: str_item expr; + + str_item: LEVEL "top" + [ [ "let"; bi = binding; "in"; x = expr -> + <:str_item< let $bi$ in $x$ >> + | "letrec"; bi = binding; "in"; x = expr -> + <:str_item< let rec $bi$ in $x$ >> + | "let"; bi = binding -> + match bi with + [ <:binding< _ = $e$ >> -> <:str_item< $exp:e$ >> + | _ -> <:str_item< let $bi$ >> ] + | "letrec"; bi = binding -> + match bi with + [ <:binding< _ = $e$ >> -> <:str_item< $exp:e$ >> + | _ -> <:str_item< let rec $bi$ >> ] + ]; + + expr: LEVEL "top" + [ [ "let"; bi = binding; "in"; + x = expr LEVEL ";" -> + <:expr< let $bi$ in $x$ >> + | "letrec"; bi = binding; "in"; + x = expr LEVEL ";" -> + <:expr< let rec $bi$ in $x$ >> ] + ]; + +おおっと、match の部分がまだ Revised だった:: + + str_item: LEVEL "top" + [ [ + ... + | "let"; bi = binding -> + begin match bi with + | <:binding< _ = $e$ >> -> <:str_item< $exp:e$ >> + | _ -> <:str_item< let $bi$ >> + end + | "letrec"; bi = binding -> + begin match bi with + | <:binding< _ = $e$ >> -> <:str_item< $exp:e$ >> + | _ -> <:str_item< let rec $bi$ >> + end + ] + ... + +これで良いはずだ! ``begin match .. with .. end`` に注意だ! これが出来たら、 ``EXTEND Gram .. END`` の中に入れてコンパイルしよう:: + + open Camlp4 + + module Id : Sig.Id = struct + let name = "pa_letrec" + let version = "1.0" + end + + module Make (Syntax : Sig.Camlp4Syntax) = struct + open Sig + include Syntax + open Ast + + DELETE_RULE Gram str_item: "let"; opt_rec; binding; "in"; expr END; + DELETE_RULE Gram str_item: "let"; opt_rec; binding END; + DELETE_RULE Gram expr: "let"; opt_rec; binding; "in"; expr END; + + EXTEND Gram + str_item: LEVEL "top" + [ [ "let"; bi = binding; "in"; x = expr -> + <:str_item< let $bi$ in $x$ >> + | "letrec"; bi = binding; "in"; x = expr -> + <:str_item< let rec $bi$ in $x$ >> + | "let"; bi = binding -> + begin match bi with + | <:binding< _ = $e$ >> -> <:str_item< $exp:e$ >> + | _ -> <:str_item< let $bi$ >> + end + | "letrec"; bi = binding -> + begin match bi with + | <:binding< _ = $e$ >> -> <:str_item< $exp:e$ >> + | _ -> <:str_item< let rec $bi$ >> + end + ] + ]; + + expr: LEVEL "top" + [ [ "let"; bi = binding; "in"; + x = expr LEVEL ";" -> + <:expr< let $bi$ in $x$ >> + | "letrec"; bi = binding; "in"; + x = expr LEVEL ";" -> + <:expr< let rec $bi$ in $x$ >> + ] + ]; + END + end + + let module M = Register.OCamlSyntaxExtension(Id)(Make) in () + +コンパイルとテストはこうなる:: + + ocamlc -pp camlp4of -I `ocamlc -where`/camlp4 -c pa_letrec.ml + $ ocaml + OCaml version 4.00.0 + + # #load "dynlink.cma";; + # #load "camlp4of.cma";; + Camlp4 Parsing version 4.00.0 + + # #load "pa_letrec.cmo";; + # let x = 1;; + val x : int = 1 + # let rec f x = f x;; + Characters 0-3: + let rec f x = f x;; + ^^^ + Error: Parse error: "module" or "open" or [binding] expected after "let" (in [str_item]) + # letrec f x = f x;; + val f : 'a -> 'b = + # + +あひゃひゃひゃひゃ!革命は成った!! + +演習問題 +------------------ + +* **[重要]** 君も上の letrec を試して国際 Scheme 戦線に参加しなさい。LCF ML 懐古趣味でも可能 + +=================== +まだ書いてない +=================== + +* 新しい Quote を作る +* Filter +* Findlib diff --git a/bb/channel.md b/bb/channel.md new file mode 100644 index 0000000..8f22bc9 --- /dev/null +++ b/bb/channel.md @@ -0,0 +1,19 @@ +Channel に関して +=============================== + +Channel から `Unix.file_descr` を得る際の注意 +------------------------------------------- + +`Unix.descr_of_{in,out}_channel` を使うと `{in,out}_channel` から Unix の fd を +得ることができる、ここから `Unix` の関数が使える。が、いくつか注意が必要: + +* `{in,out}_channel` が GC されると fd は自動的に閉じられる。channel から fd を取得したら fd の作業が終るまで channel の liveness に注意すること。 + +`close` する時の注意: `close_{in,out}` か `Unix.close` のどちらかで閉める。両方はいけない。 + +* `close_{in,out}` で閉じてもよいが、 fd をその後 `Unix.close` してはいけない。Double close で失敗するか、別の所で作った fd を間違って閉じてしまう恐れがある。 fd は内部では整数なので GC とかは気にしなくてもよい。 +* `Unix.close` で閉じてもよいが、元の channel をその後 `close_{in,out}` してはいけない。Double close で失敗するか、別の所で作った fd を間違って閉じてしまう恐れがある。 channel は放っておけば中身は GC が回収する。 +* close に忘れた場合は channel の GC 時に自動的に close されるが、この方法にはあまり頼るべきではない。 + + + diff --git a/bb/clffaq.md b/bb/clffaq.md new file mode 100644 index 0000000..b9d351f --- /dev/null +++ b/bb/clffaq.md @@ -0,0 +1,742 @@ +# Frequently Asked Questions for [comp.lang.functional](news:comp.lang.functional) + + +Edited by [Graham Hutton](http://www.cs.nott.ac.uk/~gmh), University of Nottingham + +Version of November 2002 +(This document is no longer being updated) + +1. This document +2. General topics +2.1. Functional languages +2.2. History and motivation +2.3. Textbooks +2.4. Journals and conferences +2.5. Schools and workshops +2.6. Education +3. Technical topics +3.1. Purity +3.2. Currying +3.3. Monads +3.4. Parsers +3.5. Strictness +3.6. Performance +3.7. Applications +4. Other resources +4.1. Web pages +4.2. Research groups +4.3. Newsgroups +4.4. Bibliographies +4.5. Translators +5. Languages +5.1. ASpecT +5.2. Caml +5.3. Clean +5.4. Erlang +5.5. FP +5.6. Gofer +5.7. Haskell +5.8. Hope +5.9. Hugs +5.10. Id +5.11. J +5.12. Miranda +5.13. Mercury +5.14. ML +5.15. NESL +5.16. OPAL +5.17. Oz +5.18. Pizza +5.19. Scheme +5.20. Sisal + +## この文書について + +Comp.lang.functional は関数型言語に関するデザイン、応用、理論的基礎、実装を含めた +全ての事に関する議論を行う管理者のいない [usetnet ニュース](http://ja.wikipedia.org/wiki/%E3%83%8D%E3%83%83%E3%83%88%E3%83%8B%E3%83%A5%E3%83%BC%E3%82%B9)グループである。 +このグループでの議論は(他のグループの記録と共に)次のサイトに保存されている: + +> http://www.dejanews.com/. + +この文書は comp.lang.functional の「良く聞かれる質問 (FAQ)」の一つであり、 +関数型言語に関して良く聞かれる疑問に対して簡単な回答、もしくは他情報源へのポインタや +関係する文書、インターネット情報を紹介する。 +この文書の最も新しいバージョンは、 + +> http://www.cs.nott.ac.uk/~gmh/faq.html + +から入手可能である。 + +この文書の多くの情報は公共の情報源、主に comp.lang.functional に投稿された記事、 +からとられている。そのような編集経緯から、この文書の作成に協力した事になる人々のリストは存在しない。 +この文書に書かれた意見はこれら個人の協力者の物であり、編集者や、他の関数型プログラミング +コミュニティメンバーの意見を代表するものではない場合もある。この文書の内容はできるだけ正しく、 +また最新であるように注意が払われているが、それはここで提供される情報の正しさを保証するものではない。 +あなたの問題指摘と協力が求められている! + +このFAQリストの初期のバージョンは Mark P. Jones によって編纂された。 +現在の版に対する全ての疑問、コメント、修正、提案は現在の編者である Graham Hutton に連絡されたい。 + +## 一般的なトピック + +このセクションでは関数型プログラミングに対する一般的な疑問に簡単に答え、 +関連する書籍やネット情報を提供する。 + +### 関数型言語 + +What is a "functional programming language"? + +Opinions differ, even within the functional programming community, on the precise definition of what constitutes a functional programming language. However, here is a definition that, broadly speaking, represents the kind of languages that are discussed in comp.lang.functional: + +Functional programming is a style of programming that emphasizes the evaluation of expressions, rather than execution of commands. The expressions in these language are formed by using functions to combine basic values. A functional language is a language that supports and encourages programming in a functional style. +For example, consider the task of calculating the sum of the integers from 1 to 10. In an imperative language such as C, this might be expressed using a simple loop, repeatedly updating the values held in an accumulator variable total and a counter variable i: + +total = 0; +for (i=1; i<=10; ++i) + total += i; +In a functional language, the same program would be expressed without any variable updates. For example, in Haskell, the result can be calculated by evaluating the expression: + +sum [1..10] +Here, [1..10] is an expression that represents the list of integers from 1 to 10, while sum is a function that can be used to calculate the sum of an arbitrary list of values. + +The same idea could also be used in (strict) functional languages such as SML or Scheme, but it is more common to find such programs written with an explicit loop, often expressed recursively. Nevertheless, there is still no need to update the values of the variables involved: + +SML: +let fun sum i tot = if i=0 then tot else sum (i-1) (tot+i) +in sum 10 0 +end +Scheme: +(define sum + (lambda (from total) + (if (= 0 from) + total + (sum (- from 1) (+ total from))))) +(sum 10 0) +It is often possible to write functional-style programs in an imperative language, and vice versa. It is then a matter of opinion whether a particular language can be described as functional or not. + + +2.2. History and motivation + +Where can I find out more about the history and motivation for functional programming? + +Here are two useful references: + +"Conception, Evolution, and Application of Functional Programming Languages", Paul Hudak, ACM Computing Surveys, Volume 21, Number 3, pp.359-411, 1989. +"Why functional programming matters", John Hughes, The Computer Journal, Volume 32, Number 2, April 1989. Available on the web from: +http://www.cs.chalmers.se/~rjmh/Papers/whyfp.html. + +2.3. Textbooks + +Are there any textbooks about functional programming? + +Yes, here are a selection: + +Programming: + +"Introduction to functional programming using Haskell", 2nd edition, Richard Bird, Prentice Hall Europe, 1998. ISBN 0-13-484346-0. +"The Haskell school of expression: Learning functional programming through multimedia", Paul Hudak, Cambridge University Press, 2000. ISBN 0-521-64338-4. Further information is available on the web from: +http://haskell.org/soe. +"Haskell: The craft of functional programming", 2nd edition, Simon Thompson, Addison-Wesley, 1999. ISBN 0-201-34275-8. Further information is available on the web from: +http://www.cs.ukc.ac.uk/people/staff/sjt/craft2e. +"ML for the working programmer", 2nd Edition, L.C. Paulson, Cambridge University Press, 1996. ISBN 0-521-56543-X. Further information is available on the web from: +http://www.cl.cam.ac.uk/users/lcp/MLbook/. +Algorithms and data structures: + +"Purely functional data structures", Chris Okasaki, Cambridge University Press, 1998. ISBN 0-521-63124-6. +"Algorithms: A functional programming approach", Fethi Rabhi and Guy Lapalme, Addison-Wesley, 1999. ISBN 0-201-59604-0. Further information is available on the web from: +http://www.iro.umontreal.ca/~lapalme/Algorithms-functional.html. +Implementation: + +"The implementation of functional programming languages", Simon Peyton Jones, Prentice Hall, 1987. ISBN 0-13-453333-X. +"Compiling with continuations", Andrew Appel, Cambridge University Press, 1992. ISBN 0-521-41695-7. Further information is available on the web from: +http://www.cup.org/Titles/416/0521416957.html. +There are several other textbooks available, particularly in the programming and implementation categories. A comparison of a number of functional programming textbooks is made in the following article: + +"Comparative review of functional programming textbooks (Bailey, Bird and Wadler, Holyer, Paulson, Reade, Sokoloski, Wikstrom)", Simon Thompson, Computing Reviews, May 1992 (CR number 9205-0262). + +2.4. Journals and conferences + +Are there any journals and conferences about functional programming? + +Yes, here are a selection: + +Journals: + +The Journal of Functional Programming (JFP), published by Cambridge University Press. Further information is available on the web from: +http://www.dcs.gla.ac.uk/jfp/. +The Journal of Functional and Logic Programming (JFLP), an electronic journal published by MIT Press, and available on the web from: +http://www.cs.tu-berlin.de/journal/jflp/. +Lisp and Symbolic Computation, published by Kluwer. +Conferences: + +The International Conference on Functional Programming (ICFP). This conference combines and replaces the earlier conferences on Lisp and Functional Programming (LFP), and Functional Programming Languages and Computer Architecture (FPCA). Further information about the next ICFP conference (October 2002 at the time of writing) is available on the web from: +http://icfp2002.cs.brown.edu/. +Mathematics of Program Construction (MPC). Further information about the most recent MPC conference (July 2000 at the time of writing) is available on the web from: +http://seide.di.uminho.pt/~mpc2000/. +Principles of Programming Languages (POPL). Further information about the next POPL conference (January 2001 at the time of writing) is available on the web from: +http://www.daimi.au.dk/~popl01/. +European Symposium on Programming (ESOP). Further information about the next ESOP conference (April 2001 at the time of writing) is available on the web from: +http://www.md.chalmers.se/~dave/esop/. +Most of these conferences have proceedings published by the ACM press, or in the Springer Verlag LNCS (Lecture Notes in Computer Science) series. + +In addition to the above, Philip Wadler edits a column on functional programming for the Formal Aspects of Computer Science Newsletter, which is published by the British Computing Society Formal Aspects of Computing group and Formal Methods Europe. + + +2.5. Schools and workshops + +Are there any schools and workshops on functional programming? + +Yes, here are a selection: + +Schools: + +Summer School and Workshop on Advanced Functional Programming, August 19-24, 2002, Oxford, England. Further information is available on the web from: +http://www.functional-programming.org/afp/. +The Third International Summer School on Advanced Functional Programming Techniques, September 12-19, 1998, Braga, Portugal. Further information is available on the web from: +http://www.di.uminho.pt/~afp. +Spring School on Advanced Functional Programming Techniques, May 24-31, 1995, Baastad, Sweden. The proceedings of the school were published in the Springer Verlag LNCS (Lecture Notes in Computer Science) series, number 925. +The Second International Summer School on Advanced Functional Programming Techniques, August 25-30, 1996, Washington, USA. The proceedings of the school were published in the Springer Verlag LNCS (Lecture Notes in Computer Science) series, number 1129. Further information is available on the web from: +http://www.cse.ogi.edu/PacSoft/summerschool96.html. +Workshops: + +Haskell Workshop, September 2, 2001, Florence, Italy. Held in conjunction with PLI 2001. Further information is available on the web from: +http://www.cs.uu.nl/people/ralf/hw2001.html +Haskell Workshop, September 17, 2000, Montreal, Canada. Held in conjunction with PLI 2000. Further information is available on the web from: +http://www.cs.nott.ac.uk/~gmh/hw00.html +Third Haskell Workshop, October 1, 1999, Paris, France. Held in conjunction with ICFP'99. Further information is available on the web from: +http://www.haskell.org/HaskellWorkshop.html +Workshop on Algorithmic Aspects of Advanced Programming Languages, September 29-30, 1999, Paris, France. Further information is available on the web from: +http://www.cs.columbia.edu/~cdo/waaapl.html. +1st Scottish Functional Programming Workshop, August 29-September 1, 1999, Stirling, Scotland. Further information is available on the web from: +http://www.cee.hw.ac.uk/~gjm/sfp/. +From 1988 to 1998 the Glasgow functional programming group organised a yearly workshop in Scotland. Further information is available on the web from: +http://www.dcs.gla.ac.uk/fp/workshops/. +The 9th International Workshop on the Implementation of Functional Languages, Sept 10-12, 1997, St. Andrews, Scotland. Further information is available on the web from: +http://www.dcs.st-and.ac.uk/~ifl97. +The Haskell Workshop, June 7, 1997, Amsterdam, The Netherlands. Held is conjunction with ICFP'97. Further information is available on the web from: +http://www.cse.ogi.edu/~jl/ACM/Haskell.html. +The 2nd Fuji International Workshop on Functional and Logic Programming, November 1-4, 1996, Shonan Village, Japan. Further information is available on the web from: +http://www.kurims.kyoto-u.ac.jp/~ohori/fuji96.html. +The 1st Workshop on Functional Programming in Argentina, September 12, 1996, Buenos Aires, Argentina. Further information is available on the web from: +http://www-lifia.info.unlp.edu.ar/~lambda/first/english/. + +2.6. Education + +Are functional programming languages useful in education? + +Functional languages are gathering momentum in education because they facilitate the expression of concepts and structures at a high level of abstraction. Many university computing science departments now make use of functional programming in their undergraduate courses; indeed, a number of departments teach a functional language as their first programming language. Further information about the use of functional programming languages in education (including links to relevant conferences and workshops) is available on the web from: + +http://www.cs.kun.nl/fple/. + +3. Technical topics + +This section gives brief answers to a number of technical questions concerning functional programming languages, and some pointers to relevant literature and internet resources. + + +3.1. Purity + +What is a "purely functional" programming language? + +This question has been the subject of some debate in the functional programming community. It is widely agreed that languages such as Haskell and Miranda are "purely functional", while SML and Scheme are not. However, there are some small differences of opinion about the precise technical motivation for this distinction. One definition that has been suggested is as follows: + +The term "purely functional" is often used to describe languages that perform all their computations via function application. This is in contrast to languages, such as Scheme and Standard ML, that are predominantly functional but also allow `side effects' (computational effects caused by expression evaluation that persist after the evaluation is completed). +Sometimes, the term "purely functional" is also used in a broader sense to mean languages that might incorporate computational effects, but without altering the notion of `function' (as evidenced by the fact that the essential properties of functions are preserved.) Typically, the evaluation of an expression can yield a `task', which is then executed separately to cause computational effects. The evaluation and execution phases are separated in such a way that the evaluation phase does not compromise the standard properties of expressions and functions. The input/output mechanisms of Haskell, for example, are of this kind. + +See also: +"What is a purely functional language", Amr Sabry, Journal of Functional Programming, 8(1):1-22, Cambridge University Press, January 1998. + +3.2. Currying + +What is "currying", and where does it come from? + +Currying has its origins in the mathematical study of functions. It was observed by Frege in 1893 that it suffices to restrict attention to functions of a single argument. For example, for any two parameter function f(x,y), there is a one parameter function f' such that f'(x) is a function that can be applied to y to give (f'(x))(y) = f (x,y). This corresponds to the well known fact that the sets (AxB -> C) and (A -> (B -> C)) are isomorphic, where "x" is cartesian product and "->" is function space. In functional programming, function application is denoted by juxtaposition, and assumed to associate to the left, so that the equation above becomes f' x y = f(x,y). + +Apparently, Frege did not pursue the idea further. It was rediscovered independently by Schoenfinkel, together with the result that all functions having to do with the structure of functions can be built up out of only two basic combinators, K and S. About a decade later, this sparked off the subject of combinatory logic, invented by Haskell Curry. The term "currying" honours him; the function f' in the example above is called the "curried" form of the function f. From a functional programming perspective, currying can be described by a function: + +curry : ((a,b) -> c) -> (a -> b -> c) +The inverse operation is, unsurprisingly, refered to as uncurrying: + +uncurry : (a -> b -> c) -> ((a,b) -> c) +For further reading, see: + +"Highlights of the history of the lambda-calculus", J. Barkley Rosser, ACM Lisp and Functional Programming, 1982. +"Ueber die Bausteine der mathematischen Logik", Moses Sch\"onfinkel, Mathematische Annalen, 92, 1924. An English translation, "On the building blocks of mathematical logic", appears in "From Frege to G\"odel", Jean van Heijenoort, Harvard University Press, Cambridge, 1967. +"Combinatory logic", Haskell B. Curry and Robert Feys, North-Holland, 1958. This work also contains many references to earlier work by Curry, Church, and others. + +3.3. Monads + +What is a "monad", and what are they used for? + +The concept of a monad comes from category theory; full details can be found in any standard textbook on the subject. Much of the interest in monads in functional programming is the result of recent papers that show how monads can be used to describe all kinds of different programming language features (for example, I/O, manipulation of state, continuations and exceptions) in purely functional languages such as Haskell: + +"Comprehending monads", Philip Wadler, Mathematical Structures in Computer Science, Special issue of selected papers from 6th Conference on Lisp and Functional Programming, 1992. Available on the web from: +http://www.cs.bell-labs.com/~wadler/topics/monads.html#monads +"The essence of functional programming", Philip Wadler, Invited talk, 19th Symposium on Principles of Programming Languages, ACM Press, Albuquerque, January 1992. Available on the web from: +http://www.cs.bell-labs.com/~wadler/topics/monads.html#essence +"Imperative functional programming", Simon Peyton Jones and Philip Wadler, 20th Symposium on Principles of Programming Languages, ACM Press, Charlotte, North Carolina, January 1993. Available on the web from: +http://www.cs.bell-labs.com/~wadler/topics/monads.html#imperative +"How to declare an imperative", Philip Wadler, ACM Computing Surveys, to appear. Available on the web from: +http://www.cs.bell-labs.com/~wadler/topics/monads.html#monadsdeclare + +3.4. Parsers + +How can I write a "parser" in a functional programming language? + +A parser is a program that converts a list of input tokens, usually characters, into a value of the appropriate type. A simple example might be a function to find the integer value represented by a string of digits. A more complex example might be to translate programs written in a particular concrete syntax into a suitable abstract syntax as the first stage in the implementation of a compiler or interpreter. There are two common ways to write a parser in a functional language: + +Using a parser generator tool. Some functional language implementations support tools that generate a parser automatically from a specification of the grammar. See: +Happy: a parser generator system for Haskell and Gofer, similar to the tool `yacc' for C. Available on the web from: +http://www.dcs.gla.ac.uk/fp/software/happy/. +Ratatosk: a parser and scanner generator for Gofer. Available by ftp from: +Host: ftp.diku.dk; +Directory: /pub/diku/dists. +ML-Yacc and ML-Lex: an LALR parser generator and a lexical analyser generator for Standard ML. Included with SML/NJ, available by ftp from: +Host: ftp.research.bell-labs.com; +Directory: /dist/smlnj. +Using combinator parsing. Parsers are represented by functions and combined with a small set of combinators, leading to parsers that closely resemble the grammar of the language being read. Parsers written in this way can use backtracking. See: +"How to replace failure with a list of successes", Philip Wadler, FPCA '85, Springer Verlag LNCS 201, 1985. +"Higher-order functions for parsing", Graham Hutton, Journal of Functional Programming, Volume 2, Number 3, July 1992. Available on the web from: +http://www.cs.nott.ac.uk/~gmh/bib.html#parsing. + +3.5. Strictness + +What does it mean to say that a functional programming language is "strict" or "non-strict"? + +Here's one (operational) way to explain the difference: + +In a strict language, the arguments to a function are always evaluated before it is invoked. As a result, if the evaluation of an expression exp does not terminate properly (for example, because it generates a run-time error or enters an infinite loop), then neither will an expression of the form f(exp). ML and Scheme are both examples of this. +In a non-strict language, the arguments to a function are not evaluated until their values are actually required. For example, evaluating an expression of the form f(exp) may still terminate properly, even if evaluation of exp would not, if the value of the parameter is not used in the body of f. Miranda and Haskell are examples of this approach. +There is much debate in the functional programming community about the relative merits of strict and non-strict languages. It is possible, however, to support a mixture of these two approaches; for example, some versions of the functional language Hope do this. + + +3.6. Performance + +What is the performance of functional programs like? + +In some circles, programs written in functional languages have obtained a reputation for lack of performance. Part of this results from the high-level of abstraction that is common in such programs and from powerful features such as higher-order functions, automatic storage management, etc. Of course, the performance of interpreters and compilers for functional languages keeps improving with new technological developments. + +Here are a selection of references for further reading: + +Over 25 implementations of different functional languages have been compared using a single program, the "Pseudoknot" benchmark, which is a floating-point intensive application taken from molecular biology. See: +"Benchmarking implementations of functional languages with 'Pseudoknot', a float-intensive benchmark", Pieter H. Hartel et al, Journal of Functional Programming, 6(4):621-655, July 1996. Available on the web from: +ftp://ftp.fwi.uva.nl/pub/computer-systems/functional/reports/. +The paper below compares five implementations of lazy functional languages: +"Benchmarking implementations of lazy functional languages", P.H. Hartel and K.G. Langendoen, FPCA 93, ACM, pp 341-349. Available by ftp from: +Host: ftp.fwi.uva.nl; +Directory: pub/functional/reports. +Experiments with a heavily optimising compiler for Sisal, a strict functional language, show that functional programs can be faster than Fortran. See: +"Retire FORTRAN? A debate rekindled", D.C. Cann, Communications of the ACM, 35(8), pp. 81-89, August 1992. +Postscript versions of a number of papers from the 1995 conference on High Performance Functional Computing (HPFC) are available on the web from: +ftp://sisal.llnl.gov/pub/hpfc/index.html. + +3.7. Applications + +Where can I find out about applications of functional programming? + +Here are a selection of places to look: + +"Special issue on state-of-the-art applications of pure functional programming languages", edited by Pieter Hartel and Rinus Plasmeijer, Journal of Functional Programming, Volume 5, Number 3, July 1995. +"Applications of functional programming", edited by Colin Runciman and David Wakeling, UCL Press, 1995. ISBN 1-85728-377-5. +An online list of real-world applications of functional programming is maintained, which includes programs written in several different functional languages. The main criterion for being considered a real-world application is that the program was written primarily to perform some task, rather than to experiment with functional programming. +Further details are available on the web from: + +http://www.cs.bell-labs.com/~wadler/realworld/. + +4. Other resources + +This section gives some pointers to other internet resources on functional programming. + + +4.1. Web pages + +Philip Wadler's guide to functional programming on the web: +http://cm.bell-labs.com/cm/cs/who/wadler/guide.html. +Philip Wadler's list of real-world application of functional programming: +http://www.cs.bell-labs.com/~wadler/realworld/. +The SEL-HPC WWW functional programming archive: +http://hypatia.dcs.qmw.ac.uk/SEL-HPC/Articles/FuncArchive.html. +Jon Mountjoy's functional languages page: +http://carol.wins.uva.nl/~jon/func.html. +Claus Reinke's functional programming bookmarks: +http://website.lineone.net/~claus_reinke/FP.html. + +4.2. Research groups + +The Chalmers functional programming group: +http://www.md.chalmers.se/Cs/Research/Functional/. +The Glasgow functional programming group: +http://www.dcs.gla.ac.uk/fp. +The Nijmegen functional programming group: +http://www.cs.kun.nl/~clean. +The Nottingham foundations of programming group: +http://www.cs.nott.ac.uk/Research/fop/index.html. +The St Andrews functional programming group: +http://www-fp.dcs.st-and.ac.uk/. +The Yale functional programming group: +http://www.cs.yale.edu/HTML/YALE/CS/haskell/yale-fp.html. +The York functional programming group: +http://www.cs.york.ac.uk/fp/. + +4.3. Newsgroups + +For discussion about ML: +comp.lang.ml. +For discussion about Scheme: +comp.lang.scheme. +For discussion about Lisp: +comp.lang.lisp. +For discussion about APL, J, etc: +comp.lang.apl. + +4.4. Bibliographies + +Mike Joy's bibliography on functional programming languages, in refer(1) format: +Host: ftp.dcs.warwick.ac.uk; +Directory: /pub/biblio. +Tony Davie's bibliography of over 2,600 papers, articles and books on functional programming, available as a text file or a hypercard stack by ftp from: +Host: tamdhu.dcs.st-and.ac.uk; +Directory: /pub/staple. +"State in functional programming: an annotated bibliography", edited by P. Hudak and D. Rabin, available as a dvi or postscript file by ftp from: +Host: nebula.cs.yale.edu; +Directory: /pub/yale-fp/papers. +Wolfgang Schreiner's annotated bibliography of over 350 publications on parallel functional programming (most with abstracts), available on the web from: +http://www.risc.uni-linz.ac.at/people/schreine/papers/pfpbib.ps.gz. + +4.5. Translators + +The smugweb system for typesetting Haskell code in TeX, available from: +http://www5.informatik.uni-jena.de/~joe/smugweb.html. +The miratex package for typesetting Miranda(TM) code in TeX, available from: +http://www.cs.tcd.ie/www/jgllgher/miratex/index.html. +Denis Howe's translators from Miranda(TM) to LML and Haskell, available from: +http://wombat.doc.ic.ac.uk/pub/mira2lml; +http://wombat.doc.ic.ac.uk/pub/mira2hs. + +5. Languages + +This section gives a brief overview of a number of programming languages that support aspects of the functional paradigm, and some pointers to relevant literature and internet resources. The table below classifies the languages into strict/non-strict and sequential/concurrent, and may be useful when searching for suitable languages for particular applications. Some of the languages have multiple versions with different classifications (see the language overviews for further details), but for simplicity only the most common version of each language is considered in the table. + +Sequential: Concurrent: +Strict: ASpecT +Caml +FP +J +Mercury +ML +OPAL +Scheme Erlang +NESL +Oz +Pizza +Sisal +Non-strict: Gofer +Haskell +Hope +Hugs +Miranda Clean +Id + +5.1. ASpecT + +ASpecT is a strict functional language, developed at the University of Bremen, originally intended as an attempt to provide an implementation for (a subset of) Algebraic Specifications of Abstract Datatypes. The system was designed to be as user-friendly as possible, including overloading facilities and a source-level debugger. For reasons of efficiency, the system uses call-by-value evaluation and reference counting memory management. + +Over the years more and more features have been added, including subsorting, functionals, and restricted polymorphism. The ASpecT compiler translates the functional source code to C, resulting in fast and efficient binaries. ASpecT has been ported to many different platforms, including Sun3, Sun4, Dec VAX, IBM RS6000, NeXT, Apple A/UX, PC (OS/2, Linux), Amiga and Atari ST/TT. The ASpecT compiler is available by ftp from: + +Host: ftp.Uni-Bremen.DE; +Directory: /pub/programming/languages/ASpecT. +The most important application of ASpecT to date is the interactive graph visualization system daVinci; currently (September '96), version 2.0.x is composed of 34.000 lines of ASpecT code, 12.000 lines of C code and 8000 lines of Tcl/Tk code. daVinci is an X11 program, and is available for UNIX workstations from Sun, HP, IBM, DEC, SGI, and for Intel PCs with a UNIX operating system. Further information about daVinci is available on the web from: + +http://www.Informatik.Uni-Bremen.DE/~davinci. + +5.2. Caml + +Caml is a dialect of the ML language developed at INRIA that does not comply to the Standard, but actually tries to go beyond the Standard, in particular in the areas of separate compilation, modules, and objects. Two implementations of Caml are available: + +The older implementation, Caml Light, is distinguished by its small size, modest memory requirements, availability on microcomputers, simple separate compilation, interface with C, and portable graphics functions. It runs on most Unix machines, on the Macintosh and on PCs under Ms Windows and MSDOS. The current version at the time of writing is 0.71. +A more ambitious implementation, Objective Caml (formerly known as Caml Special Light), is also available. It adds the following extensions to Caml Light: +Full support for objects and classes, here combined for the first time with ML-style type reconstruction; +A powerful module calculus in the style of Standard ML, but providing better support for separate compilation; +A high-performance native code compiler, in addition to a Caml Light-style bytecode compiler. +Objective Caml is available for Unix and Windows 95/NT, with the native-code compiler supporting the following processors: Alpha, Sparc, Pentium, Mips, Power, HPPA. + +Both implementations of Caml are available by ftp from: + +Host: ftp.inria.fr; +Directory: /lang/caml-light. +Further information about Caml is available on the web from: + +http://pauillac.inria.fr/caml/index-eng.html (English); +http://pauillac.inria.fr/caml/index-fra.html (French). + + +5.3. Clean + +The Concurrent Clean system is a programming environment for the functional language Concurrent Clean, developed at the University of Nijmegen in The Netherlands. The system is one of the fastest implementations of functional languages available at the time of writing. Through the use of uniqueness typing, it is possible to write purely functional interactive programs, including windows, menus, dialogs, etc. It is also possible to develop real-life applications that interface with non-functional systems. With version 1.0, the language emerged from an intermediate language to a proper programming language. Features provided by the language include: + +Lazy evaluation; +Modern input/output; +Annotations for parallelism; +Automatic strictness analysis; +Annotations for evaluation order; +Inferred polymorphic uniqueness types; +Records, mutable arrays, module structure; +Existential types, type classes, constructor classes; +Strong typing, based on the Milner/Mycroft scheme. +Concurrent Clean is available for PCs (Microsoft Windows, Linux), Macintoshes (Motorola, PowerPC), and Sun4s (Solaris, SunOS). The system is available by ftp from: + +Host: ftp.cs.kun.nl; +Directory: /pub/Clean. +Further information about Concurrent Clean is available on the web from: + +http://www.cs.kun.nl/~clean. +A book describing the background and implementation of Concurrent Clean is also available: + +"Functional programming and parallel graph rewriting", Rinus Plasmeijer and Marko van Eekelen, Addison Wesley, International Computer Science Series. ISBN 0-201-41663-8 + +5.4. Erlang + +Erlang is a dynamically typed concurrent functional programming language for large industrial real-time systems. Features of Erlang include: + +Modules; +Recursion equations; +Explicit concurrency; +Pattern matching syntax; +Dynamic code replacement; +Foreign language interface; +Real-time garbage collection; +Asynchronous message passing; +Relative freedom from side effects; +Transparent cross-platform distribution; +Primitives for detecting run-time errors. +Erlang is freely available on the web from: + +http://www.erlang.org. +Erlang is distributed together with full source code for a number of applications, including: + +Inets - HTTP 1.0 server and FTP client; +Orber - CORBA v2.0 Object Request Broker (ORB); +ASN.1 - compile-time and runtime package for ASN.1; +SNMP - extensible SNMP v1/v2 agent and MIB compiler; +Mnesia - distributed real-time database for Erlang; +Mnemosyne - optional query language for Mnesia. +See also: + +"Concurrent programming in Erlang" (second edition), J. Armstrong, M. Williams, R. Virding, and Claes Wikström, Prentice Hall, 1996. ISBN 0-13-508301-X. + +5.5. FP + +FP is a side-effect free, combinator style language, described in: + +"Can programming be liberated from the von Neumann style?", John Backus, Communications of the ACM, 21, 8, pp.613-641, 1978. +A interpreter and a compiler (to C) for FP are available by ftp from: + +Host: gatekeeper.dec.com; +Directory: pub/usenet/comp.sources.unix/volume13/funcproglang; +Directory: pub/usenet/comp.sources.unix/volume20/fpc. +The Illinois FP system supports a modified version of FP that has a more Algol-like syntax and structure, and is described in the following article: + +"The Illinois functional programming interpreter", Arch D. Robison, Proceedings of the SIGPLAN '87 Symposium on Interpreters and Interpretive Techniques, SIGPLAN notices, Volume 22, Number 7, July 1987. + +5.6. Gofer + +The Gofer system provides an interpreter for a small language based closely on the current version of the Haskell report. In particular, Gofer supports lazy evaluation, higher-order functions, polymorphic typing, pattern-matching, support for overloading, etc. + +The most recent version of Gofer, 2.30a, is available by ftp from: + +Host: ftp.cs.nott.ac.uk; +Directory: /nott-fp/languages/gofer. +Gofer runs on a wide range of machines including PCs, Ataris, Amigas, etc. as well as larger Unix-based systems. A version for the Apple Macintosh is also available, by ftp from: + +Host: ftp.dcs.glasgow.ac.uk; +Directory: /pub/haskell/gofer/macgofer. +Please note the spelling of Gofer, derived from the notion that functional languages are GO(od) F(or) E(quational) R(easoning). This is not to be confused with `Gopher', the widely used internet distributed information delivery system. + + +5.7. Haskell + +In the mid-1980s, there was no "standard" non-strict, purely-functional programming language. A language-design committee was set up in 1987, and the Haskell language is the result. At the time of writing, Haskell 98 is the latest version of the language. Further information about Haskell, including the latest version of the Haskell report, is available on the web from: + +http://www.haskell.org/; +http://www-i2.informatik.rwth-aachen.de/Forschung/FP/Haskell/. + +At the time of writing, there are three different Haskell systems available, developed by groups at Chalmers, Glasgow and Yale. These systems are available by ftp from the following sites: + +Host: ftp.cs.chalmers.se; +Directory: /pub/haskell. +Host: ftp.dcs.glasgow.ac.uk; +Directory: /pub/haskell. +Host: haskell.cs.yale.edu; +Directory: /pub/haskell. +Host: ftp.cs.nott.ac.uk; +Directory: /haskell. +Host: src.doc.ic.ac.uk; +Directory: /pub/computing/programming/languages/haskell. +You can join the Haskell mailing list by emailing majordomo@dcs.gla.ac.uk, with a message body of the form: subscribe haskell Forename Surname . + + +5.8. Hope + +Hope is a small polymorphically-typed functional language, and was the first language to use call-by-pattern. Hope was originally strict, but there are versions with lazy lists, or with lazy constructors but strict functions. Further information is available on the web from: + +http://www.soi.city.ac.uk/~ross/Hope/. + +5.9. Hugs + +Hugs, the Haskell User's Gofer System, is an interpreted implementation of Haskell with an interactive development environment much like that of Gofer. Further information about Hugs is available on the web from: + +http://www.haskell.org/hugs/ + +5.10. Id + +Id is a dataflow programming language, whose core is a non-strict functional language with implicit parallelism. It has the usual features of many modern functional programming languages, including a Hindley/Milner type inference system, algebraic types and definitions with clauses and pattern matching, and list comprehensions. + + +5.11. J + +J was designed and developed by Ken Iverson and Roger Hui. It is similar to the language APL, departing from APL in using using the ASCII alphabet exclusively, but employing a spelling scheme that retains the advantages of the special alphabet required by APL. It has added features and control structures that extend its power beyond standard APL. Although it can be used as a conventional procedural programming language, it can also be used as a pure functional programming language. Further information about J is available on the web from: + +http://www.jsoftware.com. + +5.12. Miranda + +Miranda was designed in 1985-6 by David Turner with the aim of providing a standard non-strict purely functional language, and is described in the following articles: + +"Miranda: a non-strict functional language with polymorphic types", D.A. Turner, Proceedings FPLCA, Nancy, France, September 1985 (Springer LNCS vol 201, pp 1-16). +"An overview of Miranda", D.A. Turner, SIGPLAN Notices, vol 21, no 12, pp 158-166, December 1986. +Miranda was the first widely disseminated language with non-strict semantics and polymorphic strong typing, and is running at over 600 sites, including 250 universities. It is widely used for teaching, often in conjunction with "Introduction to Functional Programming", by Bird and Wadler, which uses a notation closely based on Miranda. It has also had a strong influence on the subsequent development of the field, and provided one of the main inputs for the design of Haskell. + +The Miranda™ system is a commercial product of Research Software Limited. Miranda release two (the current version at the time of writing) supports unbounded precision integers and has a module system with provision for parameterized modules and a built in "make" facility. The compiler works in conjunction with a screen editor and programs are automatically recompiled after edits. There is also an online reference manual. + +Further information about Miranda is available on the web from: + +http://miranda.org.uk +Miranda is not in the public domain but is free for personal and educational use. + + +5.13. Mercury + +Mercury is a logic/functional programming language, which combines the clarity and expressiveness of declarative programming with advanced static analysis and error detection facilities. It has a strong type system, a module system (allowing separate compilation), a mode system, algebraic data types, parametric polymorphism, support for higher-order programming, and a determinism system --- all of which are aimed at both reducing programming errors and providing useful information for programmers and compilers. + +The Mercury compiler is written in Mercury itself, and compiles to C. The compiler is available for a variety of platforms running Unix and Microsoft operating systems. + +Further information about Mercury is available on the web from: + +http://www.cs.mu.oz.au/mercury. + +5.14. ML + +ML stands for meta-language, and is a family of advanced programming languages with (usually) functional control structures, strict semantics, a strict polymorphic type system, and parameterized modules. It includes Standard ML, Lazy ML, CAML, CAML Light, and various research languages. Implementations are available on many platforms, including PCs, mainframes, most models of workstation, multi-processors and supercomputers. ML has many thousands of users, and is taught to undergraduates at many universities. + +There is a moderated usenet newsgroup, comp.lang.ml, for discussion of topics related to ML. A list of frequently asked questions for this newsgroup (which includes pointers to many of the different implementations and variants of ML) is available by ftp from: + +Host: pop.cs.cmu.edu; +Directory: /usr/rowan/sml-archive/. +The Standard ML language is formally defined by: + +"The Definition of Standard ML - Revised", Robin Milner, Mads Tofte, Robert Harper, and David MacQueen, MIT, 1997. ISBN 0-262-63181-4. +Further information is available on the web from: + +http://mitpress.mit.edu/promotions/books/MILDPRF97. +"Commentary on Standard ML", Robin Milner and Mads Tofte, MIT, 1990. ISBN 0-262-63137-7. Further information is available on the web from: +http://mitpress.mit.edu/promotions/books/MILCPF90. +There is now a revised version of Standard ML, sometimes referred to as "Standard ML '97" to distinguish it from the original 1990 version. The new version combines modest changes in the language with a major revision and expansion of the SML Basis Library. Further details about Standard ML '97 are available on the web from: + +http://cm.bell-labs.com/cm/cs/what/smlnj/sml97.html. + +5.15. NESL + +NESL is a fine-grained, functional, nested data-parallel language, loosly based on ML. It includes a built-in parallel data-type, sequences, and parallel operations on sequences (the element type of a sequence can be any type, not just scalars). It is based on eager evaluation, and supports polymorphism, type inference and a limited use of higher-order functions. Currently, it does not have support for modules and its datatype definition is limited. Except for I/O and some system utilities it is purely functional (it does not support reference cells or call/cc). + +The NESL compiler is based on delayed compilation and compiles separate code for each type a function is used with (compiled code is monomorphic). The implementation therefore requires no type bits, and can do some important data-layout optimizations (for example, double-precision floats do not need to be boxed, and nested sequences can be laid out efficiently across multiple processors.) For several small benchmark applications on irregular and/or dynamic data (for example, graphs and sparse matrices) it generates code comparable in efficiency to machine-specific low-level code (for example, Fortran or C.) + +The current implementation of NESL runs on workstations, the Connection Machines CM2 and CM5, the Cray Y-MP and the MasPar MP2. + +Further information about NESL is available on the web from: + +http://www.cs.cmu.edu/afs/cs.cmu.edu/project/scandal/public/www/nesl.html +or by ftp from: + +Host: nesl.scandal.cs.cmu.edu; +Directory: nesl. +You can join to the NESL mailing list by emailing nesl-request@cs.cmu.edu. + + +5.16. OPAL + +The language OPAL has been designed as a testbed for the development of functional programs. Opal molds concepts from Algebraic Specification and Functional Programming, which shall favor the formal development of large production-quality software that is written in a purely functional style. The core of OPAL is a strongly typed, higher-order, strict applicative language that belongs to the tradition of Hope and ML. The algebraic flavour of OPAL shows up in the syntactical appearance and in the preference of parameterization to polymorphism. + +OPAL is used for research on the highly optimizing compilation of applicative languages. This has resulted in a compiler which produces very efficient code. The OPAL compiler itself is entirely written in OPAL. Installation is straightforward and has been successfully performed for SPARCs, DECstations, NeXTs, and PCs running LINUX. + +Further information about OPAL is available by ftp from: + +Host: ftp.cs.tu-berlin.de; +Directory: /pub/local/uebb/. + +5.17. Oz + +Oz is a concurrent constraint programming language designed for applications that require complex symbolic computations, organization into multiple agents, and soft real-time control. It is based on a new computation model providing a uniform foundation for higher-order functional programming, constraint logic programming, and concurrent objects with multiple inheritance. From functional languages Oz inherits full compositionality, and from logic languages Oz inherits logic variables and constraints (including feature and finite domain constraints.) Search in Oz is encapsulated (no backtracking) and includes one, best and all solution strategies. + +DFKI Oz is an interactive implementation of Oz featuring am Emacs programming interface, a concurrent browser, an object-oriented interface to Tcl/Tk, powerful interoperability features (sockets, C, C++), an incremental compiler, a garbage collector, and support for stand-alone applications. Performance is competitive with commercial Prolog and Lisp systems. DFKI Oz is available for many platforms running Unix/X, including Sparcs and 486 PCs, and has been used for applications including simulations, multi-agent systems, natural language processing, virtual reality, graphical user interfaces, scheduling, placement problems, and configuration. + +Further information about Oz is available on the web from: + +http://www.ps.uni-sb.de/oz/ +or by ftp from: + +Host: ftp.ps.uni-sb.de; +Directory: /pub/oz. +Specific questions on Oz may be emailed oz@ps.uni-sb.de. You can join the Oz users mailing list by emailing oz-users-request@ps.uni-sb.de. + + +5.18. Pizza + +Pizza is a strict superset of Java that incorporates three ideas from functional programming: + +Parametric polymorphism; +Higher-order functions; +Algebraic data types. +Pizza is defined by translation into Java and compiles into the Java Virtual Machine, requirements which strongly constrain the design space. Thus Pizza programs interface easily with Java libraries, and programs first developed in Pizza may be automatically converted to Java for ease of maintenance. The Pizza compiler is itself written in Pizza, and may be used as a replacement for Sun's Java compiler (except that the Pizza compiler runs faster). + +Pizza was designed by Martin Odersky and Philip Wadler, and implemented by Odersky. The design is described in the following paper: + +"Pizza into Java: translating theory into practice", Martin Odersky and Philip Wadler, 24th ACM Symposium on Principles of Programming Languages, Paris, January 1997. +The paper, downloads, and other information on Pizza is available on the web from any of the following locations (which mirror each other): + +http://www.cis.unisa.edu.au/~pizza; +http://cm.bell-labs.com/cm/cs/who/wadler/pizza/welcome.html; + +http://wwwipd.ira.uka.de/~pizza; + +http://www.math.luc.edu/pizza/; + +ftp://ftp.eecs.tulane.edu/pub/maraist/pizza/welcome.html. + +Pizza has received a `cool' award from Gamelan ( http://www-c.gamelan.com/.) + + +5.19. Scheme + +Scheme is a dialect of Lisp that stresses conceptual elegance and simplicity. It is specified in R4RS and IEEE standard P1178. Scheme is much smaller than Common Lisp; the specification is about 50 pages. Scheme is often used in computer science curricula and programming language research, due to its ability to simply represent many programming abstractions. + +Further information about Scheme is available on the web from: + +http://www.schemers.org. +There is an unmoderated usenet newsgroup, comp.lang.scheme, for the discussion of topics related to Scheme. A list of frequently asked questions (which includes details of the many books and papers concerned with Scheme) for this newsgroup is available by ftp from: + +Host: ftp.think.com; +Directory: /public/think/lisp/. + +5.20. Sisal + +Sisal (Streams and Iteration in a Single Assignment Language) is a functional language designed with several goals in mind: to support clear, efficient expression of scientific programs; to free application programmers from details irrelevant to their endeavors; and, to allow automatic detection and exploitation of the parallelism expressed in source programs. + +Sisal syntax is modern and easy to read; Sisal code looks similar to Pascal, Modula, or Ada, with modern constructs and long identifiers. The major difference between Sisal and more conventional languages is that it does not express explicit program control flow. + +Sisal semantics are mathematically sound. Programs consist of function definitions and invocations. Functions have no side effects, taking as inputs only explicitly passed arguments, and producing only explicitly returned results. There is no concept of state in Sisal. Identifiers are used, rather than variables, to denote values, rather than memory locations. + +The Sisal language currently exists for several shared memory and vector systems that run Berkeley Unix(tm), including the Sequent Balance and Symmetry, the Alliant, the Cray X/MP and Y/MP, Cray 2, and a few other less well-known ones. Sisal is available on sequential machines such as Sparc, RS/6000, and HP. Sisal also runs under MS-DOS and Macintosh Unix (A/UX). It's been shown to be fairly easy to port the entire language system to new machines. + +Further information about Sisal is available on the web from: + +http://www.llnl.gov/sisal/SisalHomePage.html. +The original version of this Frequently Asked Questions list (FAQ) was compiled and edited by Mark P. Jones. All questions, comments, corrections, and suggestions regarding this document should be addressed to the current editor, Graham Hutton. \ No newline at end of file diff --git a/bb/duck.rst b/bb/duck.rst new file mode 100644 index 0000000..c90d162 --- /dev/null +++ b/bb/duck.rst @@ -0,0 +1,168 @@ +===================================== +タイピングの心理学 +===================================== + +結論 +======================= + +* "How to duck type? - the psychology of static typing in Ruby" (邦訳: ダックタイピングの手引き? - Rubyでの静的型付けの心理学) という記事は意味不明文書なので読む価値はありません (訳は良くできています) +* 「静的ダックタイピング」という用語は「原動機なしバイク」のようなもの。 + +はじめに(FIXME) +======================= + +近年(FIXME)、「ダックタイピングなんたらの静的型けの心理学」という呟きが Twitter 上で散見される。 +筆者ら(FIXME)には何のことかサッパリ判らない、狂気すら感じる呟きであったので気になってサーベイ(ggrks) +してみたら邦訳記事があった。邦訳記事を数行読んでもサッパリわからないのでオリジナルを読んで見たところ、 +これは訳が悪いのではなく(いや訳は実際大変良い)、単に元記事がトンデモ系なだけであった。 +トンデモ系文章を読んでフンフンとうなづきながら何か判ったトンデモさんが増えないように釘を刺さねばならない。 + +静的型ナンタラとは何か +========================= + +静的型システム、静的型検査、静的型推論、とにかく静的型ナンタラというものは型を使った +静的なプログラム検証技術である。 + +静的なプログラム検証、とは、プログラムを実際に動かさずにプログラムに何か良い性質があるか +どうか検査することだ。何か良い性質、とは、これは場合によっていろいろだ。プログラムを実行しても +Segmentation fault が絶対起こらないとか、一般保護例外が絶対起こらないとか、デッドロックが起こらないとか、 +必ず金が儲かるとか、死後裁きに遭わないで済むとか、そういう話である。君が良いと思う性質を +静的に検証できればいいですね。 + +静的な検証と言っても、じっとプログラムのプリントアウトを見台に置いて座すこと数時間、 +クワッ!「見えたァあああ、これは安全!」タツジン!!とかそういう事ではない。 +実際のプログラムを何かしら大幅に簡略化したものをある意味動かしてみてそれが上手くいっているかどうか +確かめるのだ。この簡略化にもいろいろレベルがある。凄く簡単にすれば検証もすぐ済むから凄く静かだけど +大したことは検証できない。元のプログラムとほとんど同じであれば何かすごいことが検査できるかもしれないが +検証にも時間がかかる。経済だよキミ。コスト対効果ってやつだ。 + +例えば一番簡単な静的なプログラム検証はプログラムを実行する前にパースしてみて文法があっているかどうか +調べることだ。これはあまりに簡単すぎて普通は静的検証とは言わないのだが、ロケットを飛ばしてみて月着陸寸前に +ここの何行目にミススペルがあったのでプログラムの実行を中断しますテヘッっという通信をロケットから +受け取りたくなければ、まあ、費用もかからないし、事前にやっといて損はないだろう。 + +凄くコストの掛かる方の静的検証はモデル検査とか呼ばれる奴が代表。要はプログラムを実際に動かすのだが、 +さすがにそのまま動かしていたらキリが無いので色々と簡略化する(連中はカッコよく、抽象化する、と言っている)。 +例えば整数は無限にあるから整数に関するプログラムの検証は虱潰しにではテストしていられない(無限である)が、 +整数を素数か合成数かと大まかに二つに抽象化してやるとうまくするとそのレベルでは虱潰しが有限で現実的な物になる、 +かもしれない。で、うまくすると…この関数は必ず合成数しか返さないので東工大生が狂喜乱舞しないので会社は黒字になる、ということが証明できたりする。抽象化の度合いによっては検査に膨大な時間とメモリ、つまり金がかかる訳だが、どこまで頑張るかは、世知辛いけどお財布とか、マジでコケた時にどれくらい損が出るかとかを考えることになる。 + +静的型検査というのはこの中間位の検査方法だ。型検査ではプログラムを抽象化して安価に実行する、 +というよりはむしろプログラムの部品部品の型を見て組み合わせ部分の整合性を確認する。 +外科手術で使う麻酔ガスを始めとするガス管のつなぎの部分はガスの種類に寄って異なってデザインされており、 +間違ったガスが -- 例えば麻酔ガスの所に二酸化炭素が -- 流れないようになっているのだが、 +これに似ている。 +ボンベ、チューブ、マスク、それぞれ接続するところが合っていれば、正しいガスが流れてくる。 +とりあえず接続してみてから医者が(看護師でもいいですけど、まあ、高職位が責任を取らないとネ!)試しにマスクを被ってみて、 +ペロッ、これは麻酔ガs zzzzz...とか一々やらなくても安全が保証できる。 +コストがかからない。 +実際にガスを吸わないところがキモなのでこれは覚えておいて欲しい。 +(一方の動的型検査ではチューブの形を見るのではなく、実際にガスを吸ってみる。) + +静的型検査が剛直であるというのはそういう点だ。どう見たって安全じゃないですか、 +ほら、ボンベから患者の口までこう流れてるんだから麻酔ガスしか流れないっすよ? +決まった規格のプラグ付きでないと使えないとか面倒くさくないっすよ? +とは言ってもこれは規則で決まっているんだ、規則は規則、キミも人事に睨まれたくは無いだろう? +悪いようにはならないヨ。 + +例えば次のようなコードを大抵の静的型システムは排除する:: + + 10 * (if true then 10 else "hello") // 何の言語かは知らない。お前にも読めんだろ? + +どう見たって安全じゃないですか、ほら、条件節は true なんだから掛け算の右辺は常に整数っすよ? +でも排除するのである。規則は規則、キミも人事に睨まれたくは無いだろう?これだ。 + +大抵の静的型システムと書いたがもちろんキミは頭が素晴らしくいいのでちゃんと空気を読んで +if e0 then e1 else e2 の型は e0 が常に true であることを発見した場合、e1 の型であって e2 の型は無視する、 +みたいな静的型システムを作ることができますよね?私は面倒なのでやらないけど。 + +もちろんガス会社が麻酔ガスのボンベに水素ガスを詰めていたらどうしようもないが、 +それはこの型システムで抑えるべき安全性ではない。 +(これはどんな安全性に関しても同じ事だ。幾ら静的プログラム検証でプログラムの正しさが検証されていても、 +今まさにベテルギウスが爆発してガンマ線バーストでメモリ内容が吹っ飛んでプログラムがおかしくなることはある。が、もちろんそれは検証の責任ではない。) + +また、このプラグの形による静的安全性は、流れるガスの種類が想定ないであることしか保証しない。 +ガス圧が100気圧くらいで患者が風船のようにボン!となるかもしれないがそれはもちろん保証外である。 +ガス圧も静的に保証したいなら圧力調整弁を買いなさい。破裂した患者から訴えられるのが嫌で、 +調整弁を買うお金があるなら。 + +ついでだから、求められる安全性というものは相対的なものだという事も言っておこう。 +例えば、ボンベ、チューブ、マスクのプラグの形が合っていなければ使えないという安全性のデザインは +野戦病院では全くの邪魔でしかないかもしれない。 + +型付けが強いとか弱いとか +============================= + +普通の街の病院、ボンベ、チューブ、マスクのプラグの形が合っていなければ使えないはずのところでも、 +ガスを間違えて患者が死んだり植物状態になったりすることがある。 +バカが間違ったプラグでもよく噛んでから無理やり力技でハメて使ってしまうからである。 +手術を受けるのが怖くなりましたか?静的型検査にもこういうのがある。 + +型を使って静的に検証したので、必ず(CPUにバグがあるとか飼い猫がマシンの上でおしっこをしたとか、 +想定外の自体が無い限りは)何か良い性質が保証されている場合は、強い型付けがされている、などと言う。 + +一方、型を使って静的に検証したので、まあ、大抵、何か良い性質があるんですが、 +たまに、ほら、ね?わかるでしょう、あなたもオトナだ。金か?金が欲しいのか? +みたいなのは弱い型付けという。 +要するに気休めでしかない。剛力(あやめではない)がいる病院の麻酔システムはプラグがちゃんとしていても +弱い型付けしかされていないかもしれない。 +基本的に弱い型付けは何の保証にもならないので、普通の人間は相手にしない。 +弱い型付けは弱い型付けしか無い言語を使わされている人間があれこれ悩むものである。 +キミはもちろんそういう人間ではないね。えっ?オトナ?あ、そう。 + +動的ナンタラ +========================= + +一方動的ナンタラとは。動的検査というのはプログラム実行中に何かしら良い性質が保たれていることを検査する。 +兎に角プログラム実行中に何か調べて条件分岐すりゃあ動的検査と言えんこともないので、ここは動的型検査に +限って話す。 + +動的型検査ではプログラム実行中にプログラム内を流れる実際の値の型を見てそれが今からやる演算に対して +使えるものかどうか確かめる。麻酔ガスのマスクの所で手術中、本当に麻酔ガスが流れているかどうか、 +常に味見をするようなものである。医師(か看護師)は窒息するかもしれないが患者の家族からは訴えられないし、 +医師(か看護師)は交換が効くアセットである。もちろん理事長のあなたは交換が効かない。そういうものでしょう? + +動的型検査が柔軟だというのはそういう点だ。野戦病院では資材が足りないからプラグの形なんかでチューブやら +なんたら複数揃えてられない。だから適当につなぐ。適当につないでもどこかクリティカルな所で実際に流れてくるやつが安全かどうか確かめればいいわけだ。野戦病院では理事長はいないし、医師や看護師が吸ってたら困るから、誰が他人が試しに吸うことになるが誰が? +お察しください。 + +例えば次のようなコードを大抵の動的型システムでは何の問題もない:: + + 10 * (if true then 10 else "hello") // 何の言語かは知らない。お前にも読めんだろ? + +掛け算を行うとき、動的型付き言語では必ず引数が数値であることを動的型検査によって確認するはずだ +(その言語が 2 * "こんにちは" = "ぼくあひるちゃん!こんにちはこんにちは!!" という +大変ユニイクな文字列の掛け算概念を提唱していない限り。 +筆者ら(FIXME)はパなんとかとかルなんとかという言語の事は知ってるから指摘不要) + +だらららら(何か埋草を書きましょう) + +そういう見切り発車は困る、という場合はテストをやる。テストの方法にはいろいろある。各部品に分けたテスト +はユニットテストと言う。小部品に分けてテストするのでテストすべき組み合わせが減りコストが下がる。 +ユニットテストが終わった小部品を組み合わせて全体として上手く動くのかを見るのが結合テスト。 +まあこの辺は私は詳しくない。 + +静的と動的どちらがいいのか +=============================== + +筆者ら(FIXME)は静的型検査と動的型検査のどちらが良いのかを主張しない。 +まあ、基本的に静的方検査の方が理論とか難しいのでカッコイイ、というのはあるが… + +静的型付き言語を使っていても、全ての保証したいこと、特にプログラムを動かせば私が大金持ちになってイケメンになっている、とか、が静的型システムで保証できるわけではない。 +むしろ静的型システムで保証できることは非常に限られている。その上、保証したいことを強くした場合、 +型システムが非常に難解になったり、プログラムに沢山型を書かなくてはいけなくなったり、 +いろいろと使いにくくなってくる。 +「静的型ぁ?あいつら静的型で強い安全とか言ってるけど何が保証できるってんだよ?馬鹿?」 +とか仰る静的型付けの無い括弧の多い言語使用者がままいらっさるが、そういう意味である。 +そんな時は静的型付き言語使用者ももちろん動的な検査を行う。それは文字通り動的型検査と呼ばれたり、 +プログラム中でプログラムの型が扱えないような言語では適当な条件分岐を行なって調べる。 + +筆者ら(FIXME)は、まあプログラムが型アノテーションでややこしくなったり、 +プログラマが PhD を取らない限り読めない型が出てこない限りは、 +静的型で確かめられることは静的型で確かめたらぁ?残りは動的でいいっしょ、という立場である。 + +概要 +======================= + +* "How to duck type? - the psychology of static typing in Ruby" (邦訳: ダックタイピングの手引き? - Rubyでの静的型付けの心理学) という記事は意味不明文書なので読む価値はありません (訳は良くできています) +* 「静的ダックタイピング」という用語は「原動機なしバイク」のようなもの。 diff --git a/bb/exception.md b/bb/exception.md new file mode 100644 index 0000000..2a1fd01 --- /dev/null +++ b/bb/exception.md @@ -0,0 +1,233 @@ +例外について +================================================== + +例外の効率: OCaml の例外は早い、は本当か +====================================================================== + +例外による再帰関数からの大域脱出は OCaml ではランタイムのペナルティはほとんどない、 +という事になっている。 +``try with`` を書いてそれでもコードが読みやすければ使って構わない。 +が、実際のところ、どうか。 -g を付けてコンパイルした場合、遅くなる。 +さらに、 OCAMLRUNPARAM 環境変数に "b" が入っていると更に遅くなる。 + +しっかりした OCaml プログラムを開発したい場合はバックトレースは是非欲しい +ところなので、 -g を付けて OCAMLRUNPARAM 環境変数に "b" を入れてプログラム +を実行することは普通にある。だから、安易に例外を使うとパフォーマンスに影響する:: + + let gen_timed get minus f v = + let t1 = get () in + let res = f v in + let t2 = get () in + res, minus t2 t1 + + let timed f v = gen_timed Unix.gettimeofday (-.) f v + + let f1 x = + match x with + | 1 -> 1 + | 2 -> 2 + | 3 -> 3 + | _ -> 0 + + let f2 x = + try + match x with + | 1 -> 1 + | 2 -> 2 + | 3 -> 3 + | _ -> raise Exit + with + | Exit -> 0 + + let loop f () = + for i = 0 to 1073741823 do + ignore (f i) + done + + let () = + let _, sec = timed (loop f1) () in + Format.eprintf "%f@." sec; + let _, sec = timed (loop f2) () in + Format.eprintf "%f@." sec + +例えば上記のプログラムでは、 ocamlopt で -g 無しでコンパイルした場合:: + + 2.507164 + 5.330632 + +と 2倍くらいなのだが、 -g 付きでコンパイルした場合は:: + + 2.471575 + 21.626229 + +さらに OCAMLRUNPARAM=b した場合:: + + 2.478992 + 30.855514 + +ということになり、 12倍近く遅くなる。 + +実際これをどう受け取るかはコンテキストによるところだ。 +このベンチは例外を発生させて受け取る、この処理以外パターンマッチ一回やるだけ +なので、この10倍近い比も最悪の場合の数字であって、実際のコードではこの比は +どんどん小さくなるはずだ。 +また、raise して try で受けるとバックトレースの処理 10億回に +28秒しか掛かっていない、結構早いじゃんと考えることもできる。 + +例えば、 一連の操作をやっていって、全ての操作が成功したら Some x を +返し、どこかで何かしらエラーが出たら None を返す、というコードの場合、 +option モナドをチェーンして書く方法と、エラーが出たら例外で脱出する +という場合の2つの方法があるが、次のコードのように 10回チェーンさせる場合 +だと -g と OCAMLRUNPARAM=g を付けても両者はほとんど変わらない。 +(ちなみに -g も OCAMLRUNPARAM=g もない場合は例外版が 2倍早くなる):: + + let (>>=) v f = match v with + | None -> None + | Some v -> f v + + let g i = match i mod 2 with + | 0 -> Some i + | _ -> None + + let f1 i = g i >>= g >>= g >>= g >>= g >>= g >>= g >>= g >>= g >>= g + + let f2 i = + try + match i mod 2 with + | 0 -> + begin match i mod 2 with + | 0 -> + begin match i mod 2 with + | 0 -> + begin match i mod 2 with + | 0 -> + begin match i mod 2 with + | 0 -> + begin match i mod 2 with + | 0 -> + begin match i mod 2 with + | 0 -> + begin match i mod 2 with + | 0 -> + begin match i mod 2 with + | 0 -> + begin match i mod 2 with + | 0 -> Some i + | _ -> raise Exit + end + | _ -> raise Exit + end + | _ -> raise Exit + end + | _ -> raise Exit + end + | _ -> raise Exit + end + | _ -> raise Exit + end + | _ -> raise Exit + end + | _ -> raise Exit + end + | _ -> raise Exit + end + | _ -> raise Exit + with + | Exit -> None + + let loop f () = + for i = 1 to 1073741823 do + ignore (f i) + done + + let () = + let _, sec = timed (loop f1) () in + Format.eprintf "%f@." sec; + let _, sec = timed (loop f2) () in + Format.eprintf "%f@." sec + +え? match を 10回もネストさせないと?いやいや、これはただの例だ。 +それぞれが例外を投げるような手続き型命令を10回実行する場合に、 +try with で包む代わりに一つ一つを Either を返すようにして bind で +チェーンすると流石に遅くなりますよという事である。 +そういう事であれば普通に起こりうるだろう。 + +さてさて、ではどうすればいいのか。私はこうしたいと思っている: + +* ライブラリ関数のような誰かがどこかで再帰やループで何度も呼び出すかもしれない + 関数については安易に大域脱出しない。 +* アプリケーションコードで、呼び出される回数が読め、かつ十分少ない場合、 + 例外で書くと読みやすくなる場合は例外で書く。 +* 一関数内部でのローカルに完結した脱出は気にしない + +ちなみに、この、例外が遅いので大域脱出に気軽に使えない、 +という問題を解決するため、バックトレースを生成しない速度の早い例外 +を実験している人もいる: http://caml.inria.fr/mantis/view.php?id=5879 +確かに、「安全な goto」として例外を使いたい場合はそのバックトレースは +別に興味がない。本当に例外的な事が起こった時だけトレースが欲しいわけだから +フローコントロールの道具としての例外と、 +非常事態のための例外は分けるべきなのかもしれない。 + + +``raise Exit``, ``raise Not_found`` でコードが読みやすくなるなら使う… +===================================================================== + +CR jfuruse: 前エントリとの整合性を考えること + +特に例外を使うべきなのは Option モナドや Result(Either)モナドで処理を長ーく ``bind`` (``>>=``) 連結する場合。 +クロージャーを多用するので、どうしてもパフォーマンスが落ちる。例外にしたほうがよい。 +Option モナドの None には Not_found を使えばいいだろう。 +Result のエラーには何か例外を作らなければならないが、例えば、 +ローカルモジュールで作ってみよう(ちょっと長くなる):: + + let module E = struct exception Error of error in + let fail e = raise (E.Error e) in + try `Ok begin + ... Result モナドの bind チェーンに相当するものを fail を使って書く... + end with + | E.Error e -> `Error e + +エラーの型を明示しなければならないのは面倒だ。他には:: + + let ref error = ref None in + let fail e = error := Some e; raise Exit in + try `Ok begin + ... Result モナドの bind チェーンに相当するものを fail を使って書く... + end with + | Exit -> + match !error with + | Some e -> `Error e + | None -> assert false + +とも書けるか。 ``Exit`` が一般的すぎるならやはりローカルに例外を定義すればよい。 +まああとはこれをチョイチョイと一般化して高階関数にすれば ``Result.catch`` の出来上がり。 +``Option.catch`` ももちろんできますね。 + +ただしできるだけ例外は発生させるコードの周辺でローカルに処理すること。 +さもなくば例外の取りこぼしによるバグに悩まされることになる。 + +OCaml 例外の静的型検査の研究はあるが採用されていない。 + +とはいえ例外に頼りすぎるな +============================================================== + +要修正: 前エントリとの整合性を考えること + +例外に頼った再帰からの脱出を使わずとも同等の再帰関数を書くことは +(初心者には難しいかもしれないが)全く可能である。例外に頼りすぎるのはよくない。:: + + let exists p list = + try + List.iter (fun x -> if p x then raise Exit) list; false + with Exit -> true + +この手続き感のあるコードと:: + + let rec exists p = function + | [] -> false + | x::_ when p x -> true + | _::xs -> exists p xs + +この関数型的コードは同じである。どちらが好まれるかは、もちろん後者である。 +前者のように書いていても構わないが… +OCaml を書き続けるならどこかで努力をして後者に切り替えるべきである。 diff --git a/bb/fwo.md b/bb/fwo.md new file mode 100644 index 0000000..0443f90 --- /dev/null +++ b/bb/fwo.md @@ -0,0 +1,96 @@ +Fantasy World OCaml について +=============================== + +Language +=========== + +Lexical structure +--------------------- + +### UTF-8 自体に決め打ちするのは問題ないが、識別子などに自由に許しはじめるとモジュール Module が module.ml に対応する現在の実装では大文字小文字変換で問題の起る utf-8 はそう簡単にはいかない。 + +### ISO-8859-1 の文字列のコードが動かなくなるので良く考える必要がある。 + +### キーワードの問題。無理 + +### `::` は leopard で解決済 + +Module language +------------------- + +### `mod` の様な infix operator のユーザー定義可能性: leopard の ``` ``div``` でほぼ解決済 + +### Mixins: pure fantasy + +### Hierarchical module name space: stdlib で使われているのと同じモジュール名を使えないのは偶に困るので改善されるべきだが、 name space をやっている人がいるのでそれ待ち + +### Stateless module: コンパイラの問題というか… js_of_ocaml とか embeded とか考え無い限りあまり必要を感じない + +Language builtins +----------------------- + +### UTF-8 encoded string: UTF-8 hell 判ってない人特有の fantasy + +### byte と bytestring: 別に自分でモジュール作ればよろしい + +### Immutable strings: 4.02 で半分実現される + +### Non hard-coded `[]` and `(::)(_, _)` in the syntax: 下で議論 + +Types and type definitions +--------------------------------- + +### `type not rec t = t` は Core に既にあるし `type _t = t;; type t = _t;;` で無問題。 + +### Constructors as functions: leopard で解決済 + +### `type t = T of int * bool` と `type t = T of (int * bool)` を一緒にする: 内部表現の都合上、無理。むしろ文法を変えて違いを明確にすべき: `type t = T (int, bool) と type t = T (int * bool)` のように。ただし backward compatible で無くなる + +### `let f : 'a -> 'a = fun x -> x + 1` をやめる。 Type constraint と type annotation の違いが判っていない人が言いそうなこと。型変数のスコープなどは綺麗に考える必要があると思われるが簡単な問題ではない + +### Views : I hate views. Views are evil. + +Expressions and value definitions +--------------------------------------- + +### `fun x x -> x` を受け付けるべきではない: その通り + +### `match e with p -> true | _ -> false` の略記としての ` match? ` : leopard の pattern guard で大体同機能ある + +### ` as x` を拡張して ` as ` に。 And pattern …あまり欲いと思ったことがないし pattern guard でよいのでは + +### Inlined let: `let! f arg1 ... argn = ...`: いらん + +### `do .. done` の代りに `begin .. end` (必須)にする: 必須なところが余計混乱を招く。不可。閉じるのがいやなら leopard の `do:` がある + +### 左から右へ引数を評価する: 引数評価順が何故不定にしてあるのか?理解していないとこう言う事を思い付く。 + +Pervasives +-------------------------- + +### List のコンストラクタを `Nil` と `Cons` にして `[]` と `::` は関数呼出にする。利点を感じない。 + + +Build Tools and Runtime +========================== + +### OCaml のビルドツールを ocamlbuild にしてドキュメントちゃんとする: ocamlbuild はオワコン。 + +### Camlp4 がなんとか…: Camlp4 はメンテ不可能。オワコン状態にもっていくしかない。 + +### MinGW でもコンパイルできるようにする: 頑張ってやってください + +### ocamlyacc を Menhir に入れ替える: Menhir 入れればいいだけ + +### “runtime context”: やっている人いる + +### There is an LLVM backend.: やっている人いる + +### ライセンス: fantasy + +Libraries +============== + +### LablTk, Graphics, Str と Num はコアから無くなるべき: どうも自分が使わない場合の勝手な fantasy がおおい。 + +### `stdlib` はコンパイルビルド用ライブラリにして Batteries なり JS Core なりもっと簡単にはめこめるようにする: したらいいんじゃん? diff --git a/bb/include.md b/bb/include.md new file mode 100644 index 0000000..b07ed8b --- /dev/null +++ b/bb/include.md @@ -0,0 +1,149 @@ +呪文 include module type of struct include X end +=============================================================== + +(mli と sig の関係を判っている人向け用) + +モジュールを拡張することってよくある: + +```ocaml +module Base : sig + type t = Foo +end = struct + type t = Foo +end + +module Ext = struct + include Base + let f Foo = () +end +``` + +問題は、拡張したモジュールのインターフェース(mli)書くのが面倒臭いってこと。 + +上の `Ext` は `ocamlc -c -i` によれば、こんな sig 持っている: + +```ocaml +module Ext : sig + type t = Base.t = Foo + val f : t -> unit +end +``` + +さて基本モジュールの sig が大きい場合、これを手で書くのは大層辛いのだが、 +こう書くことができる: + +```ocaml +module Ext : sig + include module type of struct include Base end + val f : t -> unit +end +``` + +何の事かちょっと判らないが、実際これは `ocamlc -c -i` してやると A と同じ +結果が出て来るのである。 + +これが、 + +```ocaml +module Ext : sig + include module type of Base + val f : t -> unit +end +``` + +ではいけない。これは + +```ocaml +module Ext : sig + type t = Foo (* Ext.t と Base.t との関係は隠蔽されてしまった *) + val f : t -> unit +end +``` + +になる。 `Base.t` と `Ext.t` は別の型と看做されるので `Base` と `Ext` を混用することができなくなる。 + +なぜこうなるか +======================================== + +こうなっているらしい。 + +```ocaml +module Base = struct + type t = Foo +end + +module Copy = Base + +module Included = struct include Base end +``` + +こいつらはこんな感じの sig を持つ: + + +```ocaml +module Base : sig + type t = Foo +end + +module Copy = Base + +module Included : sig + type t = Base.t = Foo +end +``` + +`Copy` の型 `module Copy = Base` は 4.02.x から入ったもので、 `Copy` は `Base` の +エイリアスですよ、ということを表す。まあこれはエイリアスを多用するこのごろのライブラリの為の最適化で、 +内容的には `Included` の sig と同じで `type t = Base.t = Foo` を持っていると思ってよい。 + +さて、`Base` を include することで拡張した `Ext` の型を `module type of X` で +表現しようとすると結果どうなるか、次のコードでわかる: + +```ocaml +module Ext = struct + include Base + let f Foo = () +end + +module ExtBase : module type of Base = Ext +module ExtCopy : module type of Copy = Ext +module ExtIncluded : module type of Included = Ext +``` + +これを `ocamlc -c -i` してやると: + +```ocaml +module Ext : sig + type t = Base.t = Foo + val f : t -> unit +end + +module ExtBase : sig + type t = Foo (* Ext.t と Base.t との関係は隠蔽されてしまった *) +end + +module ExtCopy : sig + type t = Base.t = Foo +end + +module ExtIncluded : sig + type t = Base.t = Foo +end +``` + +ごらんのとおり `module type of Base` で制限すると `ExtBase.t` は `Base.t` との +関連性を失う。`Copy` と `Included` では関連性は保たれたままある。 + +たいそう気持悪いのだがこれが `module type of X` の仕様である。 `module type of X` +は `X` の sig をそのまま持って来る。 `module type of Base` であれば `type t = Foo` +が `Base` の sig であるので、それが入ってくる。この際 `t` が `Base.t` であったとかは +自動的に入らない。`Included` および `Copy` はその sig に `t` が実は `Base.t` と +同じである事が `type t = Base.t = ..` と情報として含まれているので `module type of Included` や +`module type of Copy` にも受け継がれる、ということになる。 + +`module type of Base` が `Base` とは関係が無い sig になるというのはどうも私は気持ち悪いが、 +そのような sig が作りたくなる場合もあると思われるので理解できる。 + +一方 `Base` と関連性を保ったままの `Base`互換の sig は +`module type of struct include Base end` というお経のようなコードを書かなくてはいけない +のが辛い。覚えられない。やはり一時的に隠し機能としてあった `(module Base)` とか書けないですかね。 diff --git a/bb/index.rst b/bb/index.rst new file mode 100644 index 0000000..3dda57f --- /dev/null +++ b/bb/index.rst @@ -0,0 +1,8 @@ +================================= +Zippy OCaml チュートリアル +================================= + +OCaml の基礎を抑えた人のために書かれた大雑把なチュートリアル記事集。 +細かい点は書かず、読む人の検索力と考察力に任せることで +できるだけ多くの話題を取り上げたいと思う。 + diff --git a/bb/install.md b/bb/install.md new file mode 100644 index 0000000..99103e3 --- /dev/null +++ b/bb/install.md @@ -0,0 +1,357 @@ +OCaml のインストール +=============================================== + +OCaml でプログラム開発を行う場合、まずコンパイラを含む OCaml 処理系がまず必要です。 + +次に重要なのは OPAM。OCaml ソフトウェアのインストールを自動的に行うパッケージマネージャです。 +それ以外のライブラリやツールはほぼ OPAM を通してインストールする事ができます。 +ですから、各種 Unix や OS X など、OPAM が動く環境の場合は必ずインストールするべきです。 +(Windows では OPAM はまだ動きません。) + +この章では + +* OCaml 処理系のインストール +* (OPAM がインストール可能な環境での) OPAM パッケージマネージャのインストール +* OPAM を使った OCaml の処理系の(再)インストール(`opam switch`) + +の手順について解説します。 + +大まかなインストール手順フロー +-------------------------------------------- + +OCaml を自分のシステムにどうインストールするか、次のフローチャートが参考になるでしょう。 + +* OPAM が動作する環境 (Unix, Linux, OS X など) + * OPAM のバイナリ配布が使用可能で、使用したい場合 + * OPAM のバイナリをインストール + * `opam switch` による OCaml 処理系のインストール + * OPAM が動作する環境だが、OPAM のバイナリ配布が使用不可能、もしくは使用したくない場合 + * OPAM をソースからインストールするための OCaml 処理系が必要 + * OCaml のバイナリ配布が使用可能で、使用したい場合 + * OCaml のバイナリをインストール + * OCaml のバイナリ配布が使用不可能、もしくは使用したくない場合 + * OCaml をソースコードからインストール + * インストールした OCaml 処理系を使って OPAM をインストール + * `opam switch` による OCaml 処理系の再インストール +* OPAM が動作しない環境 (Windowsなど) + * OCaml のバイナリ配布が使用可能で、使用したい場合 + * OCaml のバイナリをインストール + * OCaml のバイナリ配布が使用不可能、もしくは使用したくない場合 + * OCaml をソースコードからインストール + +(CR jfuruse: Graphviz とかで書きたい) + +### Windows は避けたい + +Windows 上で OCaml は使用可能ですし、実際に Windows 上で開発を行っている +個人、企業は複数存在します。が、Unix由来のプログラム開発全般に言えることですが、 +Windows ではかなり苦労をすることになります。OCaml を使って純粋にプログラミングを +楽しみたいのであれば、 Windows は避けましょう。Windows の上に仮想 Linux 環境を +VMWare Player や VirtualPC で構築した方がスムーズに事が進みます。 + +ただそれでもあえて Windows 上で OCaml を使用したいという場合は +後述の各OSでの注意点を参考にしてください。 + + + + + + +OCaml のインストール +------------------------------------------- + +OCaml 処理系をインストールするには、三つの方法があります: + +* ソースコードを公式ウェブサイトから入手し、アーカイブ内のドキュメントに従い手動でインストールする +* OCaml のソースパッケージマネージャである OPAM を使い、OCaml のソースコードの入手からインストールまでを自動的に行う +* OS ディストリビューションなどが提供するバイナリパッケージを利用する + +### ソースコードからの手動インストール + +公式ページ http://ocaml.org/releases/ から最新版(latest)の OCaml ソースコードを入手します。 +ソースコードアーカイブの中にある `INSTALL` と `PREREQUISITES` +(Windows 系の場合はさらに `README.win32`) というファイルの手順に従い +インストール作業を行ってください。 + +非常に理想的な環境でインストール作業が問題なく成功した場合の手順は次のようになります: + +```shell +$ ./configure --prefix <プリフィクス> <== /usr などインストール先を指定 +... +$ make world.opt <== コンパイル +... +$ make install +... +``` + +もちろんあなたの環境は理想的ではないので、こう簡単にはいきません。是非ソースコードに付属している上記ドキュメントを良く読んで作業を行ってください。一度うまく動いたインストール手順は次に OCaml 処理系のバージョンアップがあった場合でもまず同じように動きます。手順をスクリプトにまとめておくと便利です。 + +#### 手動でインストールした場合でも、OPAM をインストールして、OPAM を通して OCaml をインストールしなおす + +OPAM は種々のライブラリのインストールを自動化してくれます。OPAM を使わずソースコードから +OCaml をインストールした場合でも、OPAM がサポートされている環境であるならば、 +是非 OPAM をインストールするべきです。 +そしてインストールした OPAM を使って OCaml 処理系を再度インストールしなおしてください。 + +OPAM からインストールできる OCaml 処理系のコードでは、リリース時には発見できていなかった +あなたの環境特有のバグが修正されている可能性があります。このようなバグフィクスは、 +公式ソースコードを使っているかぎり、次のリリースまで統合されません。ですから、本来ならば +自分でパッチをみつけてきて手動で適用、再コンパイルしなければいけないのです。 +OPAM を使えばそのステップが自動化できる場合があります。 +(実際 Mac OS X では、最近の clang に関する変更により、公式 OCaml 4.02.1 では外部 +C 関数のコードがうまくコンパイルできません。OPAM からインストールした OCaml +ではこの問題は修正されています。) + +### OPAM を使った OCaml のインストール + +OPAM が利用可能である場合、 OCaml 処理系のインストールは非常に簡単です: + +```shell +$ opam switch 4.02.1 +... <== しばらく待つ +``` + +`4.02.1` は OCaml 処理系のバージョンです。特に支障が無い限り最新の物を選びましょう。 +`opam switch list` で利用可能なバージョンリストが表示されます。 + +#### ``eval `opam config env` `` を忘れない + +`opam switch` を行った後、**必ず** ``eval `opam config env` `` を行って +環境設定することを忘れないでください。 + +通常、 `opam switch <バージョン>` を行うと OCaml 処理系は +`$HOME/.opam/<バージョン>` ディレクトリの下にインストールされます。以降の +OPAM パッケージのインストール先もこのディレクトリの下になります。 +``eval `opam config env` `` は各種環境変数がこのディレクトリを指すように +変更します。 + +これを忘れると異る OCaml 処理系のバージョンが複数存在する場合、不可解な現象に +悩まされることになります。 +(実際には自分の想定しているバージョンの処理系を使っていない、というだけなのですが、 +知らずにこの問題に引っ掛ると次から次に起る全く理解できない問題に苦しむことになります。) + +#### `opam switch` がうまくいかなかった場合 + +特殊なOSやディストリビューションを使っていないかぎり、正式リリースされたバージョンの +`opam switch` が失敗することは無いはずです。 +それでも失敗する場合には、依存するツールが足りないなどの原因があるかもしれません。エラーログを良く読んで +必要なツールを揃えてみてください。OPAM でのインストールを一旦あきらめて、 +インストールのドキュメントを読みながら、ソースコードからの手動インストールを行うのも一手です。 + +#### `opam switch` できたら `system` は捨ててしまってもよい。 + +OPAM を通さずに手動やディストリビューションパッケージからインストールされた OCaml は +`system` と呼ばれます。`opam switch <バージョン>` でインストールした +OCaml はもしバージョンが 4.02.1 ならば `4.02.1` と呼ばれます。 +`opam switch` で OCaml 処理系をインストールしなおした後で、 `system` コンパイラを +積極的に使う必要はありません。もし `system` と `<バージョン>` が共に存在しているのが +混乱を招くと思われるのならば、`<バージョン>` の安定動作を確認の後、 `system` は +消してしまってもかまいません。 + +(CR jfuruse: どうやって system を消すか) + +### OS ディストリビューションなどのパッケージシステムから OCaml をインストールする + +各種 Linux, Mac OS X, Windows など、OCaml 処理系のパッケージが用意されている +OS、ディストリビューションは意外と多いので、それを利用して OCaml をインストールしてしまうのも +楽で良い方法かもしれません。 + +ただし…パッケージによっては大小いろいろな問題があるようです。 +一番良くあるのが、古い OCaml のバージョンしか用意されていない、というものです。 +アンインストーラーにバグがあり、アンインストール時に他のシステムにも重要な環境変数が +上書きされてしまった、という困ったバイナリパッケージもありました。 + +OCaml 使用者と比べると、特定ディストリビューションの OCaml パッケージを使っているという +人の数はとても少くなりますし、 OCaml 開発チームも個々のバイナリパッケージやインストーラーに +対して責任や興味は持っていません。これは、何かパッケージ特有の問題があった場合、助けてくれる人の数 +が少いということです。ですから、あまりお勧めしたくない方法です。 + +なお、OPAM をスムーズにコンパイルするためにはおそらくバージョン 4.00.0 以上の OCaml が +パッケージシステムにより提供されている必要があると思います。それ以下の、例えば 3.12.1 +などの場合は始めからソースからのインストールを行ったほうがよいでしょう。 + +(CR jfuruse: OPAM に必要な OCaml のバージョンは未確認。確認しても上る可能性ある…) + +各ディストリビューションでのパッケージのインストール方法についてはそれぞれの +ディストリビューションにより異るので、触れません。 + +もし OS ディストリビューションの OCaml パッケージを使って OCaml をインストールした場合でも、 +OPAM が利用できるところでは OPAM をインストールして、是非 `opam switch` で OCaml 処理系 +一式をソースからコンパイルしなおしてください。ディストリビューションのパッケージでは修正されていない +バグが直っている事があるかもしれません。 + +### OCaml がインストールされているか確認 + +さて、最後にちゃんと OCaml がインストールされているかどうか、確認しておきましょう: + +```shell +$ ocamlc -version +4.02.1 <== あなたのインストールしたバージョンが表示されるはず +``` + +ちゃんとあなたのインストールした OCaml のバージョン番号が表示されているでしょうか。 + + + +OPAM のインストール +------------------------------------------- + +OPAM のインストールもソースから自分でコンパイルする方法と、 +バイナリパッケージを使用する方法とがあります。 +共に詳しい説明は https://opam.ocaml.org/doc/Install.html を参照してください。 + +### OPAM をソースからコンパイルする + +OPAM は OCaml で書かれています。ですから、 OPAM をソースコードからインストールする場合には +事前に何らかの方法で OCaml がインストールされていなければいけません。上記の OCaml の +インストール方法を参考にまず OCaml を用意してください。もちろんこの時点で OPAM はインストール +されていないはずですから、 「OPAM を使った OCaml のインストール」の方法は使えません。 + +OPAM のソースからのインストール方法は `README.md` に書いてあります。良く読んでください。 +Git でレポジトリから直接取ってきた場合など、周辺ライブラリが同梱されていないソースをダウンロードした +場合には `configure` 後に `make lib-ext` する事を忘れなければ問題無いはずです。 + +### バイナリパッケージによる OPAM のインストール + +いくつかのディストリビューションや OS では OPAM のバイナリパッケージが用意されていますので、 +それを使うのもよいでしょう。ただし、あまりお勧めしません。 + +OPAM は比較的新しいソフトウェアなので、最新の安定バージョンでさえユーザーによっては不具合いが +発生する可能性がまだあります。そのような場合はソースレポジトリから OPAM の開発版を入手しソース +から再インストールすると問題が解決されていることが多いのです。バージョン 1.1.0 の頃はこのような事が +頻繁に起きました。現在の安定バージョン 1.2.0 ですがこのような事が起る可能性は否定できません。 +ですので、OPAM はソースコードからインストールすることをお勧めします。 + +### OPAM は OPAM ではインストールできない + +今のところ、OPAM は `opam install opam` ではインストールできません。 +自動的に OPAM のバージョンを上げる際に `$HOME/.opam` に存在するデータベースファイルの +整合性をちゃんと保ったまま正しく移行できるのか、不安な事からサポートされていないようです。 + +(CR jfuruse: では手動で OPAM をアップグレードした場合、 `$HOME/.opam` はどうしたらいいの?) + +### OPAM がインストールされているか確認 + +書く +書く +書く +書く +書く +書く +書く +書く +書く + +### OPAM を初めて使う + +書く +書く +書く +書く +書く +書く +書く +書く +書く + +### OPAM でにっちもさっちも行かなくなったら + +書く +書く +書く +書く +書く +書く +書く +書く +書く + +OPAM の調子が悪くなり、どうしても直らない場合は、 `$HOME/.opam` ディレクトリを消去して +全てをやりなおす事ができます。再構成には時間がかかるかもしれませんが確実です。 + +各OS での注意点 +----------------------------------------------- + +### Linux や BSD系 + +OCamlシステムの開発者たちが好んで使ってきたのが Linux や BSD系の OS、ディストリビューションです。 +一般のユーザーも多いと思われますので、何か固有の問題があった場合でも情報の共有は楽な部類です。 + +Debian は OCaml 開発チームで長らく使われてきた環境です。おそらく今でも使っている人は +多いでしょう。その流れから Ubuntu での一般ユーザー数も多いと思われます。 + +Red Hat や Fedora は Jane Street などで使われています。これも安心です。 + +BSD系は Linuxに比べると人は少いですが、FreeBSD で OCaml を開発している人もいました。 + +### OS X + +Mac は意外にも Caml界では長く愛されてきたコンピューターで、 OCaml の前進である +Caml Light は Mac Plus (1986年発売の 68000が 8MHz でノロノロと動く、 +メモリが最大でも4MB しか乗らないマシンですよ?!?!)でも動作しました。 + +その伝統もあるのでしょうか、OS X でも OCaml は問題なく動きますし、ユーザーも +沢山います。 + +これらのユーザーのほとんどは外部ライブラリのインストールには Homebrew を使っているようです。 +MacPorts を使う強い理由が無い限りは Homebrew を使ってください。 + +### Windows + +Windows 上での OCaml プログラム開発を行うことは不可能ではありませんが、困難を伴います。 + +Windows でも Chocolatey https://chocolatey.org/packages ソフトウェア +パッケージシステムの普及にともない、開発ライブラリを揃えるのは少しずつ楽になっては +来ましたが、それでもまだ Unix 系のパッケージシステムの便利さに比べるといろいろ厄介です。 + +#### 三種類の「移植」 + +まず、Windows 版 OCaml と言っても Cygwin, MinGW, MS native の三種類の移植が +存在しており、それぞれ使える機能、出来たバイナリの配布ライセンス、スピード特性が +違います。詳しくは OCaml コンパイラのソースコードにある `README.win32` を +参照してください。(さらに MS native には 32bit版と 64bit版があります。) + +ただでさえ少ない OCaml の Windows の使用者はさらにこの三種類の移植によって +分断されています。コミュニティで何か質問するにしてもこの移植の違いを理解している人が +まずあまりいません。例えば、「Windows で OCaml を使っているのですが◯◯がうまくいきません」 +という(まずい)質問をあなたがした場合、折角答えてくれる人が表れても、別の移植での +話だった…ということは十分にありえます。何か問題が起った場合でも、コミュニティのサポートは +Unix系 OCaml ほどは期待できません。 + +#### Windows での動作が未確認の OCaml ライブラリが多い + +Windows 特有の事情、Unix とは違う改行コードや、EOF の扱いを意識して書かれた +OCaml ライブラリは少ないのでしばしば修正が必要になります。 +また、 Cygwin 以外の移植の Unix モジュールは機能が制限されており、 +`Unix.exec` などが動きません。Cygwin 以外では OCaml から別のプログラムを +実行する場合は Windows API を呼び出すライブラリを自作する必要があります。 + +どの移植を使うにせよ、Windows での OCamlプログラム開発では Cygwin 環境を +ビルドツールとして使うことが多くなると思います。(外部ライブラリが GNU Make +を仮定しているなどしているとそうなります) が、Cygwin はプロセス生成(fork+exec) +に大きなオーバーヘッドがあり、 `make` コマンドなどのようなサブプロセスを +多数発生させるビルドは異様に時間がかかります。ウィルスチェッカーを走らせている場合、 +OCaml コンパイラの起動にさらに時間が取られる事になるので設定に注意が必要です。 +Cygwin や OCaml プログラムに対し頻出するウィルス感染偽陽性警告の扱いも面倒です。 + +#### OPAM は動かない + +OPAM はまだ Windows では動きません。そのため、OCaml ライブラリをインストールする +際にはソースコードから一つ一つ入れていく事になります。 + +#### それでもインストールしたい場合 + +これらの問題を理解してなお OCaml を Windows で使いたい場合は、 +`README.win32` を良く読んで、自分の使用条件にあった移植を選び、 +ソースコードからインストールするか対応するインストーラーを使ってください。 + +MinGW 移植には Wodi というパッケージシステムがありますが、活動はあまり +活発ではありません。使ってもあまり得られるものはないでしょう。 +ただ、Wodi のレポジトリには各パッケージを MinGW OCaml でコンパイルするための +パッチが用意されています、これを貰ってきて手元のライブラリのコンパイルに +使用するのは賢い方法だと思われます。 + +また、Unix 上で Windows の MinGW 用バイナリをクロスコンパイルしている +人もほんの少しですがいらっしゃるようです。ただしこれを行うには C コンパイラの +クロスコンパイル状況であったり、 OCaml コンパイラのビルド事情に詳しい方でなければ +手を出さないほうがよいでしょう。 diff --git a/bb/js_of_ocaml.rst b/bb/js_of_ocaml.rst new file mode 100644 index 0000000..c049552 --- /dev/null +++ b/bb/js_of_ocaml.rst @@ -0,0 +1,539 @@ +========================================= +ウェブブラウザで関数型言語を使う: js_of_ocaml +========================================= + +js_of_ocaml が熱い。 Google の Dart とか、そんな場合じゃない!! + +OCaml で書かれたプログラムがなぜか JavaScript に変換され、それがブラウザで動く。 +JS で型がついていないオブジェクトでも何となく型をつけて OCaml 側で型安全性にブラウザで動くプログラムを書ける。生産力向上のチャンス! + +え?よくわからない? http://ocsigen.org/js_of_ocaml/manual/ の demo を試してご覧なさい。これが全部 OCaml で書かれている…! + +レシピと下準備 +============== + +- ocaml http://ocaml.inria.fr/ + なきゃぁ話しにならんわな + +- findlib http://projects.camlcity.org/projects/findlib.html + 真面目に OCaml やるなら入れてるよな! + +- lwt http://www.ocsigen.org/lwt/ + Light Weight Thread ライブラリ。 協調スレッドですからロックとかいりません。 Monadic に書きます。 Jane Street の Async も同じようなもの。片方わかればもう片方も普通に書ける。 + +- js_of_ocaml http://ocsigen.org/js_of_ocaml/ + 肝心要の js_of_ocaml + +これを上から下に順番にインストール。 +Linux なら特に問題ないはず。問題ある?それは残念ですね… + +動いてる? +----------------- + +js_of_ocaml の examples ディレクトリで make したらブラウザで index.html にアクセス。いくつかデモがあるから動かしてみよう。 +動くはず。動かない?それは… + +どうやって使う? +================= + +じゃあ早速 js_of_ocaml で何か作ってみよう! と言いたいところだが、まずは例題を自分の環境でコンパイルしてみるところから。 +examples ディレクトリにある例題の Makefile はソースをビルドした環境を仮定しているので、それを単にコピーするわけにはいかない。 +examples/cubes を自分の作業用ディレクトリにコピーして、次のコマンドを実行してみよう:: + + # camlp4o を利用した js_of_ocaml の文法拡張を使い、 lwt ライブラリを使用して cubes.ml をコンパイル + ocamlfind ocamlc -syntax camlp4o -package lwt,js_of_ocaml.syntax -g -c cubes.ml + + # lwt と js_of_ocaml ライブラリを使って、 cubes.cmo とライブラリを cubes.byte にリンク。 + ocamlfind ocamlc -package lwt,js_of_ocaml -linkpkg -o cubes.byte cubes.cmo + + # cubes.byte を js_of_ocaml コンパイラを使用して cubes.js ファイルに変換 + js_of_ocaml cubes.byte + +上手くいけば cubes.js ファイルが出来上がっている。 +index.html をブラウザで開けばなんか妙なデモが始まるはず。始まらない?そ… + +この 3つのビルドステップは Makefile に書いておくといい。私は OMake を使っているが極まりすぎているので公開してもあまり意味がないだろう。 +まあ、遊ぶだけなら shell スクリプトでも書いておけばいいはず。Shell スクリプトが書けない?… + +じゃあ、何か作ってみよう! +============================ + +例として既存の javascript を使用した例題を js_of_ocaml に少しづつ以降していくことにしよう。 +Google chart とか、うまくいくと便利そうだから、これにしようっと。 + +まず http://code.google.com/intl/ja/apis/chart/interactive/docs/quick_start.html の例をコピーしてブラウザで動くかどうか確認:: + + + + + + + + + + +
+ + + +動くよね? + +js_of_ocaml 第一歩 +======================= + +じゃあ、この2つ目の script タグの部分を js_of_ocaml に移していこう! まず、この部分を完全にカットして、 chart.js を読み込むようにする:: + + + + + + + + +
+ + + +で、この chart.js の部分を js_of_ocaml を使って chart.ml で記述していきましょう。どうするか?まずは超簡単に:: + + open Js + + let _ = Unsafe.eval_string " + + // Load the Visualization API and the piechart package. + google.load('visualization', '1.0', {'packages':['corechart']}); + + // Set a callback to run when the Google Visualization API is loaded. + google.setOnLoadCallback(drawChart); + + // Callback that creates and populates a data table, + // instantiates the pie chart, passes in the data and + // draws it. + function drawChart() { + + // Create the data table. + var data = new google.visualization.DataTable(); + data.addColumn('string', 'Topping'); + data.addColumn('number', 'Slices'); + data.addRows([ + ['Mushrooms', 3], + ['Onions', 1], + ['Olives', 1], + ['Zucchini', 1], + ['Pepperoni', 2] + ]); + + // Set chart options + var options = {'title':'How Much Pizza I Ate Last Night', + 'width':400, + 'height':300}; + + // Instantiate and draw our chart, passing in some options. + var chart = new google.visualization.PieChart(document.getElementById('chart_div')); + chart.draw(data, options); + } + " + +あれ?ほとんど元の JavaScript ではないか。そう。とりあえず、 Js.Unsafe.eval_string という文字列をそのまま JS として評価する関数があるので、それを使ってみたわけだ。これで、:: + + ocamlfind ocamlc -syntax camlp4o -package lwt,js_of_ocaml.syntax -g -c chart.ml + ocamlfind ocamlc -package lwt,js_of_ocaml -linkpkg -o chart.byte chart.cmo + js_of_ocaml chart.byte + +を実行する、そんで index.html を読み込む。 Pie chart が出るはず。出ない?… + +そら eval するだけだから出るのは当たり前だろう、バカにしているのか?と言ってはいけない。 js_of_ocaml、まず第一歩はこういう eval から始めるのがいいみたい。とりあえずワケわからなくなったら Js.Unsafe.eval_string で様子を見てみる、これ大切。 + +関数を作って JS に渡してみよう! +==================================== + +もうすこし複雑なことをしてみよう。 JS の drawChart 関数を OCaml に移す:: + + open Js + + let drawChart () = Unsafe.eval_string " + // Create the data table. + var data = new google.visualization.DataTable(); + data.addColumn('string', 'Topping'); + data.addColumn('number', 'Slices'); + data.addRows([ + ['Mushrooms', 3], + ['Onions', 1], + ['Olives', 1], + ['Zucchini', 1], + ['Pepperoni', 2] + ]); + + // Set chart options + var options = {'title':'How Much Pizza I Ate Last Night', + 'width':400, + 'height':300}; + + // Instantiate and draw our chart, passing in some options. + var chart = new google.visualization.PieChart(document.getElementById('chart_div')); + chart.draw(data, options); + " + + let _ = Unsafe.eval_string " + // Load the Visualization API and the piechart package. + google.load('visualization', '1.0', {'packages':['corechart']}); + "; + Unsafe.meth_call (Unsafe.variable "google") "setOnLoadCallback" [| Unsafe.inject drawChart |] + +JS の drawChart 関数をそのまま OCaml の drawChart 関数に写しただけ。相変わらず、中身は eval_string。 +この OCaml の drawChart 関数は js_of_ocaml コンパイラでコンパイルしても drawChart という名前にはならない。 +だから、drawChart を使う、元の JS の google.setOnLoadCallback(drawChart); メソッド呼び出しはそのまま eval_string することはできない。 +Unsafe.meth_call を使う:: + + Unsafe.meth_call (Unsafe.variable "google") "setOnLoadCallback" [| Unsafe.inject drawChart |] + +- Unsafe.meth_call は JS のメソッド呼び出し。第一引数が JS のオブジェクト、第二がメソッド名、第三が引数配列。 +- オブジェクトは JS で google と言う変数に束縛されているので Unsafe.variable "google" として、その変数を使う +- メソッド名は文字列なのでそのまま +- 引数はひとつ、 drawChart 関数を渡すのだけど、そのままでは型が合わないので Unsafe.inject を使う + +これで動くはず。動かない?それは残念ですね… と言いたいところだが、 + +動かなかったら +-------------------- + +js_of_ocaml で何か上手く行かなかったら、こうするといい + + - アウトプットの js ファイルを良く見る。なんとなく読める。 (というか OCaml のバイトコードからそれなりに人間が読める JS コードを吐ける事に驚く。バイトコードがあればリバースエンジニアリングできるということだからだ! (10年ほど前にはそんな事は出来っこないから、商用コードでもバイトコードで配布すれば安心!というのが常識だった)) + - ブラウザのエラーコンソールを良く見る。なんとなくわかる。 + +とにかく、急いで全部 OCaml にしない事。一歩々々確かめて、知見はメモするのがいい。この Chart 移植作業中にもいくつかポイントがあった。瑣末だから敢えて書かないけど。 + +まあ、 Unsafe ですから!! +-------------------------- + +Unsafe モジュールの関数は超低レベル。とにかく JS と話をするためだけに作られている。型を合わせていない。だから簡単な間違いでも型検査で見つけることができない。そこんとこ宜しく。 + +文字列と JS literal object +=============================== + +とりあえず drawChart の eval_string は置いておいて、下の数行をもうちょっと OCaml っぽくしていこう:: + + let google = Unsafe.variable "google" + let _ = + (* Load the Visualization API and the piechart package. *) + Unsafe.meth_call google "load" [| Unsafe.inject (Js.string "visualization"); + Unsafe.inject (Js.string "1.0"); + Unsafe.inject (Unsafe.variable "{'packages':['corechart']}") |]; + Unsafe.meth_call google "setOnLoadCallback" [| Unsafe.inject drawChart |] + +ここでの改変ポイントは + +- OCaml 文字列は Js.string 関数で JS の文字列オブジェクトに変更。 Unsafe.meth_call に不安全に突っ込むために Unsafe.inject を使用。 +- JS literal object {'packages':['corechart']} は今の所良い記述法が無いので Unsafe.variable "文字列" で代用 + +JS literal object については実は {: packages = [ "corechart" ] :} みたいな書き方ができるようなパッチがつい最近出たみたいだけど、 stable 版には入っていないみたい。とりあえず変数として文字列をぶち込めば、気持ち悪いけど動く。 取り入れられるまで、待ちましょう。 + +とりあえず、ここんとこ改変して動かしてみよう。 + +Class type で JS のオブジェクトをエンコード +========================================= + +さて、ここから面白くなってくる。 JS に型もクソもないが、JS のオブジェクトの型を何となく OCaml の class type として記述することで、 JS のオブジェクトのインターフェースを OCaml内のクラスとして型安全に使用することが出来る!。 今まで例を引き続き使って、 google オブジェクトのクラス型を考えよう:: + + class type g = object + method load : js_string t -> js_string t -> 'a t -> unit meth + method setOnLoadCallback : (unit -> unit) -> unit meth + end + +とりあえず、 google のメソッドは load と setOnLoadCallback を使っている。このメソッドを持つ class type g を定義している。 + +メソッドの OCaml でのあるべき型を何となく想像しよう。例えば、 load は string を二つ、その次によくわからない JS object を受け取り、返り値は unit でいいだろう。つまり、 string -> string -> 'a -> unit だ。 'a はとりあえず、よくわかんないから型変数にしておいた。 + +class type g の load メソッドが、この型を持つと宣言するのだが、そのまま string -> string -> 'a -> unit と書くわけではなく、ちょっとした変換が必要だ。ここんとこちょい面倒で自動で出来そうなものだが、まあ、ルールは簡単だから手でもできる + +- JS のオブジェクトの型は 'a Js.t。 'a は phantom type でオブジェクトの中身の型。例えば JS の文字列オブジェクトの型は js_string Js.t になる。 ここでは open Js しているので js_string t になっている。 + +- リターンの型は別の phantom type 'a meth で修飾する。ここでは、なんとなく想像したリターン型は unit だから unit meth。 + +- わかんない型もとりあえず 'a t として、何か JS のオブジェクトが来るってことにする。もちろん型安全性は失われるが、どうせ JS だから。 + +- 引数の型が関数の場合、オブジェクトではないので t で修飾する必要は無い。 + +というわけで、 method load の型は js_string t -> js_string t -> 'a t -> unit meth になる。 + +setOnLoadCallback も同様。このメソッドはコールバック関数をもらってそれを登録するから、 OCaml 的には (unit -> unit) -> unit の型を持つ。これを上のルールに従って変換する。 (unit -> unit) -> unit meth。 + +さて、インターフェースを OCaml の型で宣言できた。 変数 google にはこのインターフェースを持つオブジェクトが入っているはずだから、それを明示しよう:: + + let google : g t = Unsafe.variable "google" + +google は JS object なので g t って型になる。 t を忘れないように。 + +js_of_ocaml では c JS.t という型、つまり c というインターフェースを持つ JS object に対し、特殊な糖衣構文を使って型安全にメソッド呼び出しができる:: + + let _ = + (* Load the Visualization API and the piechart package. *) + google##load (Js.string "visualization", + Js.string "1.0", + Unsafe.variable "{'packages':['corechart']}"); + google##setOnLoadCallback (drawChart) + +ここでのポイントは + +- OCaml の普通のメソッド呼出 # と違って、 ## を使う +- JS のメソッドは uncurry form で呼び出す。 class type での宣言は curried であるのだが。 +- 一引数、ゼロ引数であっても JS メソッド名の後には () が必須。 google##setOnLoadCallback drawChart とは書けない + +当然ながら今度は Unsafe を多用していた時と違って、かなり型安全になっている。例えば setOnLoadCallback に違う型の関数を適用することはできない。 + +js_of_ocaml ではこんな風に、既存の JS クラスに適当な型を与えて OCaml 側で型安全性を使ったプログラミングが出来る。もし完全に型をエンコードできなければ型変数を使ってとりあえず、その部分だけの型安全性を諦めることも出来る。非常に柔軟かつ簡単に複雑な JS 資産を OCaml 側で利用できる仕組みを持っていると言えるだろう。 + +例によって、最後の部分をこの class type 宣言、 google の定義、 google の使用のコード片に書き換えて動作を確認しよう。 + +プロパティと new +============================================ + +さて、これで元の JS の最後の部分は OCaml に移すことが出来た。 (JS literal object が甘いが、今の所エレガントにはできないのだからまあ、よしとする) こんどは drawChart の eval_string の部分を移植していこう。 + +ここでの問題は、 new google.visualization.PieChart() に見られる、 + +- google.visualization というプロパティアクセス +- new + +の二つ。 + +プロパティも class type にエンコード +-------------------------------------------- + +JS object のプロパティも class type にエンコードすることで OCaml 側でアクセスすることが可能だ。 visualization というプロパティを google のクラス型 g に足してみよう:: + + class type g = object + method load : js_string t -> js_string t -> 'a t -> unit meth + method setOnLoadCallback : (unit -> unit) -> unit meth + method visualization : 'a t readonly_prop + end + +とりあえず、 google.visualization の型は何かわからないので 'a t という型にしておいた。 google.visualization はメソッドではなく、プロパティなので、 meth の代わりに readonly_prop という phantom type を使う。こう記述しておくと、 google##visualization という OCaml コードで JS の google.visualization にアクセスできる。 + +もし JS object o のプロパティ p が変更可能な場合、 readonly_prop の代わりに普通の prop を使う。その場合は、 o##p でプロパティを読み出すだけでなく、 o##p <- e でプロパティの上書きが可能だ。 + +クラスコンストラクタ は constr でエンコード +------------------------------------------ + +上では visualization はとりあえず 'a t という型だと想定したが、 new google.visualization.PieChart(...) という使われ方をしているから、 + +- PieChart にアクセスできる +- PieChart は HTML の要素を取って new できる + +事が判る。今度はこの visualization を OCaml の class type にエンコードしよう:: + + class type v = object + method _PieChart : (Dom_html.element t -> 'a t) constr readonly_prop + end + +- PieChart は大文字から始まる。 OCaml では大文字から始まるメソッドは定義できないので _ を前に付ける。 _PieChart。 +- PieChart は read only prop +- PieChart はオブジェクトではなく新しいオブジェクトを new できるコンストラクタ。なので constr phantom type でそれを明示。 +- new google.visualization.PieChart(e) は HTML の element を取る。その型は Dom_html.element t。 そして作られるオブジェクトは…例によって良く判らないので 'a t にしておく + +v を用意したので、 google.visualizaiton の型は v t と書くことが出来る。 class type g を修正:: + + class type g = object + method load : js_string t -> js_string t -> 'a t -> unit meth + method setOnLoadCallback : (unit -> unit) -> unit meth + method visualization : v t readonly_prop + end + +これで、準備完了。 new google.visualization.PieChart(e) は OCaml では次のように書くことが出来る:: + + jsnew (google##visualization##_PieChart) (e) + +- PieChart へのアクセスは ## を使う +- JS object の new は OCaml の new ではなく、 jsnew を使う +- jsnew の引数にはカッコが必須。 (constructor に ## が入っている場合もカッコがいる + +どんどん変えていこう +================================= + +さて、ここらで一度動くコードが提示できると嬉しいのだけど…残念ながら、一気にやっていかないといけない。 (eval_string 内で変数にバインドしてもその後使えないので…) + +- new google.visualization.PieChart(...) の結果は 'a t では寂しい。結果の chart は draw というメソッドを持っているので、 chart という class type を定義。 draw メソッドを宣言 + +- PieChart と同様に、 DataTable を constr readonly_prop として class type v に定義 + +- new google.visualization.DataTable() の結果は addColumn と addRows というメソッドを持っているので、それも class type に定義 + +これを全部やったのが次:: + + open Js + + class type dataTable = object + method addColumn : js_string t -> js_string t-> unit meth + method addRows : 'a t -> unit meth + end + + class type chart = object + method draw : dataTable t -> 'a t -> unit meth + end + + class type v = object + method _DataTable : dataTable t constr readonly_prop + method _PieChart : (Dom_html.element t -> chart t) constr readonly_prop + end + + class type g = object + method load : js_string t -> js_string t -> 'a t -> unit meth + method setOnLoadCallback : (unit -> unit) -> unit meth + method visualization : v t readonly_prop + end + + let google : g t = Unsafe.variable "google" + + let drawChart () = + let data = jsnew (google##visualization##_DataTable) () in + data##addColumn (Js.string "string", Js.string "Topping"); + data##addColumn (Js.string "number", Js.string "Slices"); + data##addRows ( Unsafe.eval_string "[ + ['Mushrooms', 3], + ['Onions', 1], + ['Olives', 1], + ['Zucchini', 1], + ['Pepperoni', 2] + ]" ); + let options = Unsafe.variable "{'title':'How Much Pizza I Ate Last Night', + 'width':400, + 'height':300}" + in + let div = Unsafe.eval_string "document.getElementById('chart_div')" in + let chart = jsnew (google##visualization##_PieChart) (div) in + chart##draw(data, options) + + let _ = + (* Load the Visualization API and the piechart package. *) + google##load (Js.string "visualization", + Js.string "1.0", + Unsafe.variable "{'packages':['corechart']}"); + google##setOnLoadCallback (drawChart) + +注意点は… + +- addRows の第一引数と draw の第二引数の型は、まあ、とりあえず放っとく。 Unsafe.eval_string したものを渡すので +- options は例によって JS literal object なので Unsafe.variable "文字列" で代用 + +まだちょっと Unsafe な部分はあるが、大部分が OCaml の型安全な世界に移ってきた。 + +仕上げ +================================= + +残りの Unsafe や取りあえずの method 型宣言内の型変数を減らそう。 (JS literal object は置く。) + +- addRows の第一引数の型は JS object の配列の配列なので、 'a t js_array t js_array t。 ('a の部分は…難しい) +- OCaml で記述したピザデータから JS 文字列の配列の配列を作るためのコード +- HTML の chart_div という id を持ったエレメントを探すため Dom_html モジュールを使用 + +最終的にはこんなコードになる:: + + open Js + + class type dataTable = object + method addColumn : js_string t -> js_string t-> unit meth + method addRows : 'a t js_array t js_array t -> unit meth (* 引数の型を明確化 *) + end + + class type chart = object + method draw : dataTable t -> 'a t -> unit meth + end + + class type v = object + method _DataTable : dataTable t constr readonly_prop + method _PieChart : (Dom_html.element t -> chart t) constr readonly_prop + end + + class type g = object + method load : js_string t -> js_string t -> 'a t -> unit meth + method setOnLoadCallback : (unit -> unit) -> unit meth + method visualization : v t readonly_prop + end + + let google : g t = Unsafe.variable "google" + + let drawChart () = + let data = jsnew (google##visualization##_DataTable) () in + data##addColumn (Js.string "string", Js.string "Topping"); + data##addColumn (Js.string "number", Js.string "Slices"); + (* 食べたピザデータを OCaml の (string * int) list で表現 *) + let rows = [ ("Mushrooms", 3); + ("Onions", 1); + ("Olives", 1); + ("Zucchini", 1); + ("Pepperoni", 2) ] + in + (* JS のオブジェクトへ変換 *) + let rowsJS = + Js.array (Array.of_list (List.map (fun (name,q) -> + Js.array [| Js.string name; + (* No phantom for top type? *) + Obj.magic q |]) rows)) + in + data##addRows(rowsJS); + let options = Unsafe.variable "{'title':'How Much Pizza I Ate Last Night', + 'width':400, + 'height':300}" + in + (* Dom アクセスで chart_div という名前のエレメントを取得。無ければ、残念です… *) + let div = match Opt.to_option (Dom_html.window##document##getElementById (Js.string "chart_div")) with + | None -> assert false + | Some div -> div + in + let chart = jsnew (google##visualization##_PieChart) (div) in + chart##draw(data, options) + + let _ = + (* Load the Visualization API and the piechart package. *) + google##load (Js.string "visualization", + Js.string "1.0", + Unsafe.variable "{'packages':['corechart']}"); + google##setOnLoadCallback (drawChart) + +残った Unsafe は、ごくわずか。 + +- google オブジェクトは g t という型を持つよー。これはしょうがない +- JS literal object の部分。これは多分すぐにエレガントに書けるようになる。 Wktk して待て! + +まとめ +================================= + +js_of_ocaml を導入すれば、既存の JS 資産を利用した HTML ページを、簡単な eval_string を使ったものから始めて、最終的にほとんどのコードを OCaml に移植する事が出来る。これを Google の Chart API を使った例を通して見てみた。実際カンタン! + +JS のオブジェクトのインターフェースは、いくつかのルールを覚えれば、簡単に OCaml の class type として宣言し、 OCaml 内で静的型安全に使用することができる。とはいえ、ガッチムチに硬いわけでもなく、完全な静的型安全性が得にくい場合は、その部分だけの安全性を捨て、 JS 側の動的型検査にまかせることができる。すごく柔軟だ!! + diff --git a/bb/lazy_split_at.rst b/bb/lazy_split_at.rst new file mode 100644 index 0000000..f435827 --- /dev/null +++ b/bb/lazy_split_at.rst @@ -0,0 +1,172 @@ +============================== +splitAt を OCaml で +============================== + +Haskell の関数:: + + splitAt :: Int -> [a] -> ([a], [a]) + +を OCaml での遅延リストに翻訳することを考える。ちなみに:: + + type 'a t = 'a desc Lazy.t + + and 'a desc = + | Null + | Cons of 'a * 'a t + + let (!!) = Lazy.force + +まずどう翻訳したいのか考える +======================================= + +Haskell の ``[a]`` を単に ``'a t`` に変えればいいだけではないか? +という単純な考えは一般的に良くない。ちゃんと立ち止まって考える。 + +``splitAt n xs`` は ``xs`` の prefix ``n``個分と、それ以外の postfix +を返す。だから、 prefix は常に有限長になる。有限長であって無限長に +ならないのであれば、そもそも ``'a t`` を使わず ``'a list`` ではないのか、 +と考えるべきだ。つまり、:: + + val split_at : int -> 'a t -> 'a list * 'a t + +と考える。そうすると、単に ``[a]`` を ``'a t`` に変えた関数:: + + val split_at' : int -> 'a t -> 'a t * 'a t + +と型が違う。型が違うと意味が違うはずだ。そう考えると、 +``split_at`` と ``split_at'`` がどう動くべき関数であるか +それなりに演繹できる。 + +まず無限入力にも停止するミニマルなものを書く +============================================================ + +はじめから欲張って ``split_at'`` を書こうとすると大抵何か +間違いを犯す。まず取り敢えず、どのような ``'a t`` にも結果を返す +(force すると止まらない ``undefined`` かも知れないが)関数を実装しよう。 +つまり、この場合、 ``split_at`` になる:: + + let rec split_at n t = + if n = 0 then [], t + else match t with + | lazy Null -> [], t + | lazy (Cons (x,t)) -> + let xs, t' = split_at (n-1) t in + x::xs, t' + +``Lazy.force`` や上述の ``!!`` ではなくパターン部で ``lazy`` を +使って force した上でマッチしていることに注意。 ``Lazy.force`` や ``!!`` +を使って書いても良い。 + +しかしそんな事より、この関数が末尾再帰になっていない事が問題。 +ある程度大きい ``n`` を入れるとスタックを食いつぶしてしまう。 +こう書くべき:: + + let split_at n t = + let rec aux rev n t = + if n = 0 then rev, t + else match t with + | lazy Null -> rev, t + | lazy (Cons (x,t)) -> aux (x::rev) (n-1) t + in + let rev, postfix = aux [] n t in + List.rev rev, postfix + +これで出来上がり。``split_at n t`` は ``t`` の ``n`` 個の prefix +をそこまでの lazy cons を force した上で普通のリストにして返す。 +呼び出し時に Lazy cons が force されるのは ``split_at`` の型 +に明示されている。Prefix は force されているのでリーク +(プログラマがもういらないはずなのに、と期待していても GC されないようなデータ) +もしない。うん、よろしい。 + +そして一番重要なことだが、ストリームが無限でも ``split_at`` は停止する。 +停止性のみを満たす物を書くのは、これは Haskell の遅延評価を鍵とする関数を +eager evaluation の言語に移す時の最低基準だからだ。そして最低基準であるから、 +正しい関数も書きやすい。 + +それから、できるだけ評価が遅延される物を書く +============================================================ + +さて、 ``split_at`` を書けたら、次にやることはより遅延が効いた関数を +書くことだ。 ``split_at n xs`` は prefix の cons を force してしまう。 +``n`` がいくつで有っても結果にアクセスしない限りできるだけ ``xs`` の +cons が force されないような物を書こう。 ``split_at'`` だ。 + +事前に ``split_at`` を書いていることで、 ``split_at'`` は次のような +関数でないのは明らかだ:: + + let split_at'_bad n t = + let prefix, postfix = split_at n t in + t_of_list prefix, postfix + +これでは単に ``split_at`` の結果の prefix 有限リストをストリームに +変えただけだ。確かに型は目指すものと同じだが、``split_at'_bad n t`` +を呼び出した時点で先頭の ``n`` 個の cons が force されるのは +``split_at`` と変わらない。 + +そんなアホな事はしない!?そうだろうか。 +気をつけていてもそういう物を書いてしまうことはある。 + +分解する時、は lazy で wrap する +--------------------------------------------- + +Haskell でのなんとなく lazy になる環境に慣れていたり、 +不注意で OCaml の普通の list の split_at の様にプログラムを書き出すと +こんなコードになる:: + + let rec split_at'_bad2 n t = + if n = 0 then lazy Null, t + else match t with + | lazy Null -> lazy Null, t + | lazy (Cons (x,t)) -> + let pref, post = split_at'_bad2 (n-1) t in + lazy (Cons (x,pref)), post + +これはダメ。何故ダメか。これはちゃんと考えて欲しい。ポイントは二つ:: + +* 関数に ``t`` を渡すと即座に lazy pattern で force されてしまう。 +* ``split_at'_bad2`` がそのまますぐに再帰呼び出しされているので、 + ``split_at'_bad2 n t`` は 即座に ``n``回再帰してしまう。 + +この二つから、この関数は型こそ目標のものと同じだがやっていることは +結局上の「そんなアホな事はしない!?」と全く同じ。型を合わせただけ、だ。 + +遅延データを force したら必ずそのコードを lazy で囲もう。つまり、:: + + let rec split_at'_bad3 n t = + if n = 0 then lazy Null, t + else + lazy (match t with + | lazy Null -> lazy Null, t + | lazy (Cons (x,t)) -> + let pref, post = split_at'_bad3 (n-1) t in + lazy (Cons (x,pref)), post) + +こうなる。 ``split_at'_bad3`` の再帰呼び出しも lazy の中にあるので +上記の2つ目の問題、すぐに再帰呼び出しが行われる問題も解決できている。 +しかし、これは型が合っていない。 lazy で wrap してしまったからだ。 + +Lazy.t を単に !! で外すのはまず間違い +--------------------------------------------- + +ここで初めて型合わせをやることになる。とはいえ、次のような型合わせは間違い:: + + let rec split_at'_bad3 n t = + if n = 0 then lazy Null, t + else + !!( lazy (match t with + | lazy Null -> lazy Null, t + | lazy (Cons (x,t)) -> + let pref, post = split_at'_bad3 (n-1) t in + lazy (Cons (x,pref)), post)) + +lazy で wrap したのを ``!!`` (force) ですぐさま元に戻している、 +これじゃあ意味がない。 ``!!(lazy e)`` は ``e`` と同じだから。 +ここで我々が欲しい型は確かに ``('a t * 'a t) Lazy.t -> 'a t * 'a t`` +なのだが、一番外側の Lazy.t は force や再帰が勝手に進まないために +導入したものだから外してはいけない。ではどこを外すのか。その内側、つまり:: + + ('a t * 'a t) Lazy.t = ('a desc Lazy.t * 'a desc Lazy.t) Lazy.t + +の tuple 要素についている ``Lazy.t`` を外すことになる。 + + let detuple tpl = fst !!tpl diff --git a/bb/module_coercion.md b/bb/module_coercion.md new file mode 100644 index 0000000..c851d16 --- /dev/null +++ b/bb/module_coercion.md @@ -0,0 +1,100 @@ +Module coercion ˤĤ +============================= + +Module coercion ȤΤϽƸȲΤȤ狼ʤ路⤽äΤǡ狼롣 +ʤΤǴñ˥ǥƤ + +OCaml Ǥ module Υѥ +============================== + +OCaml Ǥ module tuple Ʊ֥å¤Ѵ: + + + $ ocaml -dlambda + ... + # module M = struct let x = 42 let y = "hello" end;; + (apply (field 1 (global Toploop!)) "M/1023" + (let (x/1021 42 y/1022 "hello") (makeblock 0 x/1021 y/1022))) + module M : sig val x : int val y : string end + + +`(makeblock 0 x/1021 y/1022)` ȤΤʬ Tuple ǤƱ: + + + # (fun x y -> (x,y)) (42, "hello");; + (after //toplevel//(1):0-32 + (apply + (function x/1021 y/1022 + (funct-body //toplevel//(1):0-18 + (before //toplevel//(1):12-17 (makeblock 0 x/1021 y/1022)))) + [0: 42 "hello"])) + - : '_a -> (int * string) * '_a = + + +`(x,y)` ʬ `(makeblock 0 x/1021 y/1022)` ˤʤäƤ롣 + +ʤΤ `sig val x : int val y : string end` Υ⥸塼 `int * string` +Ʊ֥å¤򤷤Ƥ롣 + +ޤס + +ML Υ⥸塼ηդ tuple +====================================== + +`(int * string)` Ȥ tuple äȤơ `(string * int)` Ȥ +˻Ȥ뤫ȤȤ󤽤ʤȤϤǤʤ + + + # (Obj.magic (42, "hello") : (string * int));; + Segmentation fault (core dumped) + + +ML Υ⥸塼ϡ `sig val x : int val y : string end` Ȥ +⥸塼 `sig val y : string val x : int end` ȤˤƤɤ + + + # module M = struct let x = 42 let y = "hello" end;; + module M : sig val x : int val y : string end + # module N = (M : sig val y : string val x : int end);; + module N : sig val y : string val x : int end + + +졩 Tuple Ǥϥå夹Τˤʤ module Ǥϥå夷ʤ +`sig val x : int val y : string end` ηĥ⥸塼 `(int * string)` +Ʊ¤򤷤ƤϤ `sig val y : string val x : int end` ηˤ +`(string * int)` ƱϤɤƾ꤯Ԥ + +Module coercion +====================================== + +⤷ѥ餬äˤƤʤäȤС tuple Ʊǥå夹Ϥ +å夷ʤȤȤϲ̤ʤȤ򤷤Ƥ뤫顣 + + + $ ocaml -dlambda + ... + # module M = struct let x = 42 let y = "hello" end;; + (apply (field 1 (global Toploop!)) "M/1020" + (let (x/1018 42 y/1019 "hello") (makeblock 0 x/1018 y/1019))) + module M : sig val x : int val y : string end + + # module N = (M : sig val y : string val x : int end);; + (let (M/1020 (apply (field 0 (global Toploop!)) "M/1020")) + (apply (field 1 (global Toploop!)) "N/1023" + (makeblock 0 (field 1 M/1020) (field 0 M/1020)))) + module N : sig val y : string val x : int end + + +ñ `N` `M` ƱͤǤϤʤѴäƤ롣 +`(makeblock 0 (field 1 M/1020) (field 0 M/1020))` ʬ +`M` 1ܤ 0ܤˡ0ܤ 1ܤ֤֥åäƤ롣 + +줬 OCaml Typedtree ˽ФƤ module_coercion sig ĥ⥸塼ͤ +˸ߴΤ뤫Ū sig ؤȷѤˡԤ sig Υ쥤Ȥؤ +⥸塼֥åǤ֤뤿ξǤ + +Ū module coercion Ͽ sig ˵ sig ǤΤɤΰ֤ΥΤ˽ФƤ뤫 +ʤΤ int list ι¤ˤʤ뤬ФƤоݤ module ä硢 module Ф +module coercion ɬפˤʤ롣ޤ primitive ФƤϡ˺줿ɤ䡣 + + diff --git a/bb/monad_functor.rst b/bb/monad_functor.rst new file mode 100644 index 0000000..939b8b1 --- /dev/null +++ b/bb/monad_functor.rst @@ -0,0 +1,532 @@ +==================================================================== +Monad にしちゃう functor を通して今時の OCaml モジュールプログラミングを俯瞰 +==================================================================== + +これは私用の覚え書き。 +OCaml や ML のモジュールシステム、 value polymorphism、さらには relaxed value polymorphism +を知っている人にも役に立つかもしれない。 + +OCaml 3.12.0 位から以降限定の技だ。 + +既存モジュールの型 'a t が実はモナドだったので、モナドのインターフェースを加えたい、という時が、ままある。 +例えば、 'a Lazy.t は、モナドになる。でもモナドの bind とか return が定義されていない。 +モナドの便利な関数群もない。それを functor で拡張していこう。 +ここで言う functor とは ML のそれである。モジュールを受取りモジュールを返す関数。モナドのそれと勘違いしないこと。 + +最低限の Monadic interface を作る +================================= + +まずモナドとして最低限提供されてなきゃいけない関数群をモジュールとして見て、その型を考える:: + + module type S = sig + type 'a t + val return : 'a -> 'a t + val bind : 'a t -> ('a -> 'b t) -> 'b t + end + +この場合は、 return と bind が最低限必要ってことにした。 + +'a Lazy.t にモナド風味を付け加えるには、:: + + module MLazy : S (* 便宜的に Lazy.t の実装を隠してある *) = struct + type 'a t = 'a Lazy.t + let return v = Lazy.lazy_from_val v + let bind x f = f (Lazy.force x) + end + +これでいい。あ、でもこれじゃ MLazy に Lazy の元々の関数が入ってないじゃないか! まあそれは後で考える。 +MLazy の型を明示的に S と書くことで、外部には MLazy.t が Lazy.t と同じことを隠している。 +本来、これを書く必要は無いのだが、次の説明に必要なので。 + +Covariant にしよう +================================= + +ちょっとここでひと工夫。 OCaml は (relaxed) value polymorphism があるので、関数適用時の多相性に制限がある:: + + let x = MLazy.return [] + +これは多相値にならない。 '_a list MLazy.t である。 +Monadic なプログラミングでは return や bind を多用するので、これは困る。 +そこで、 S.t の型パラメタを covariant だということにして、この問題を回避できる。:: + + module type S = sig + type +'a t (* Covariant ですぞ *) + val return : 'a -> 'a t + val bind : 'a t -> ('a -> 'b t) -> 'b t + end + + module MLazy : S (* 便宜的に Lazy.t の実装を隠してある *) = struct + type 'a t = 'a Lazy.t + let return v = Lazy.lazy_from_val v + let bind x f = f (Lazy.force x) + end + +こうしておくと、 relaxed value polymorphism のお蔭で、 return や bind 適用時にも多相性が失われることがない!:: + + let x = MLazy.return [] + +はちゃんと 'a list MLazy.t になる。 + +もちろんモジュール型 S は元の S より条件が厳しくなるので、今から作る functor の取りうるモジュールの幅が狭まるが、 +今のところ covariant 以外の 'a t をモナドにしようと思ったことが無いので、まあこれでよかろう。 +(もし covariant でない 'a t をモナドにしようとすれば別の S' を定義して以下の functor Make もまた別の Make' +を定義することになる。もちろんその Make'(A) の関数群は relaxed value polymorphism の恩恵は受けることはできないが。) + +MLazy の型制限も必要ないから外しておこう:: + + module MLazy = struct + type 'a t = 'a Lazy.t + let return v = Lazy.lazy_from_val v + let bind x f = f (Lazy.force x) + end + +強化 functor, Make を作ろう +================================== + +S の型を持つ基本モジュールを作るのはプログラマの仕事。そこからいろいろ自動的にモナディックな関数群を創りだす +functor、 Make を定義しよう。まず、強化されたモジュール型から:: + + module type T = sig + include S + + val (>>=) : 'a t -> ('a -> 'b t) -> 'b t + val map : ('a -> 'b) -> 'a t -> 'b t + val sequence : 'a t list -> 'a list t + end + +T は S に関数を足したもので、まあ、二つしか足してないが…まあ好きなだけ足せばいい。 +Make は S の型を持つ基本モジュール A をもらって型 T に強まったモジュールを返す。 +実装:: + + module Make (A : S) : T = struct + include A + + let (>>=) = bind + + let map f t = bind t (fun x -> return (f x)) + + let rec sequence = function + | [] -> return [] + | x::xs -> + x >>= fun x -> + sequence xs >>= fun xs -> + return (x::xs) + end + +(>>=) と sequence の定義はごく普通。 ふむ、出来た。使ってみる:: + + module XLazy = Make(MLazy) + + let v = [ MLazy.return []; XLazy.return [] ] (* 型エラー *) + +あれ? 強化前のモナドと強化後のモナド、同じはずなのに、一緒の型を持てないからリストに入れられない… +型システムが MLazy.t と XLazy.t を別の型だと思っているのだ。 + +なぜか? module Make (A : S) : T = ... の所で明示的に結果のモジュールの型を T と提示しているが +T.t が A.t と同じであると宣言するのを忘れている! この情報を教えてやらなければいけない:: + + module Make (A : S) : T with type 'a t = 'a A.t (* 型の同値を足した *) = struct + include A + + let (>>=) = bind + + let map f t = bind t (fun x -> return (f x)) + + let rec sequence = function + | [] -> return [] + | x::xs -> + x >>= fun x -> + sequence xs >>= fun xs -> + return (x::xs) + end + +なんと面倒な! 実は、この返り型を書かなければ、型システムがよろしくやってくれる! :: + + module Make (A : S) = struct + include A + + let (>>=) = bind + + let map f t = bind t (fun x -> return (f x)) + + let rec sequence = function + | [] -> return [] + | x::xs -> + x >>= fun x -> + sequence xs >>= fun xs -> + return (x::xs) + end + +この様に、モジュールを使ったプログラミングではプログラマが情報を必要以上に減らしたモジュール型を書いてしまって +型の同値性が知らず知らずに失われてしまう、ということが、ままある。モジュールを使わない所では型推論の恩恵に +慣れきっている我々には難しいところだ… + +基本的には functor の引数の型など必要なモジュール型以外は、とりあえず、書かない、のが吉なようだ。 +ただし、ライブラリとして functor の返りモジュールの型情報は合ったほうがよいし(特に .mli)、 +そうなると、上の T やT with 'a t = 'a A.t の記法も避けられない。 +ここんとこの勘所を身につけるには修行が必要だ。 + +Binary operator のための特殊名前空間を作る +================================================ + +さて、 module XLazy = Make(MLazy) で、強化モジュールが出来た。 +XLazy.(>>=) をバリバリ使いたいのだが、それには XLazy.(>>=) と一々書くのは面倒だから、 +open XLazy を唱えてちゃんと二項演算子として使えるようにしてやろう! +と言いたいところだが、ちょっと待て。 open XLazy すると (>>=) どころか map や sequence も +アクセス可能になる。 map は特に一般的なイディオムだから名前空間を開きすぎだ。 +やはり map はあくまでも map ではなく XLazy.map としてアクセスしたい。 +そのために open 用のモジュール、 XLazy.Open を定義しよう:: + + module Make (A : S) = struct + include A + + module Open = struct + let (>>=) = bind + end + + include Open + + let map f t = bind t (fun x -> return (f x)) + + let rec sequence = function + | [] -> return [] + | x::xs -> + x >>= fun x -> + sequence xs >>= fun xs -> + return (x::xs) + end + +こうすると、 module XLazy = Make(MLazy) とした場合、 (>>=) は XLazy.(>>=) としても、 +XLazy.Open.(>>=) としてもアクセスできる。 open XLazy.Open を唱えれば Open の中で定義されている +(>>=) だけがグローバルな名前空間に踊りでてくるという寸法だ。 + +まあ、型クラスがあればこんな事気にしなくて良いのだが…無いものはしょうがない。 + +拡張前の元のモジュールと統合: with type 'a t := ... を使う! +========================================================= + +Lazy モジュールには、型 'a t の他にいろいろな関数が定義されているが、 MLazy そして強化された XLazy +にもそれらの関数は入っていない。 これは、 XLazy を MLazy ではなく、 Lazy に return と bind を加えたものから +作っても同じ事だ。:: + + module MLazy' = struct + include Lazy + val return : 'a -> 'a t + val bind : 'a t -> ('a -> 'b t) -> 'b t + end + + module XLazy' = Make(MLazy') + +XLazy' は Lazy そして MLazy' に存在する関数群が継承されていない。残念だが functor の引数は閉じた型しか +取れないので、いくら functor 引数にリッチなモジュールを与えても、 functor 内部では引数モジュールの型として +宣言したプアーな型しか持っていないのだ。他は、忘れ去られてしまう。 + +まあ、 Lazy と XLazy を使い分ければ良いのだが…できればこの二つの機能を合わせ持つ 俺Lazy が欲しいところだ。 +これならどうだ?:: + + module Lazy = struct + include Lazy + include XLazy (* うまくいかない! *) + end + +残念だが上手くいかない。 Lazy も XLazy も 'a t を定義している。同じ名前の型定義が二つ以上モジュールには存在できないのだ。 +しかしこれは妙な話だ。 'a XLazy.t = 'a Lazy.t という同値関係を型システムは知っているのだから、問題はないはずなのに。 +'a XLazy.t が 'a Lazy.t と同じであることを宣言してみよう:: + + module Lazy = struct + include Lazy + include (XLazy : T with 'a type t = 'a Lazy.t) (* うまくいかない! *) + end + +これも上手くいかない。無理なのか、と思いきや、こんなのが使える:: + + module Lazy = struct + include Lazy + include (XLazy : T with type 'a t := 'a Lazy.t) (* たった一文字加えただけなのに! *) + end + +一体何が? これは次の例を見れば、わかる、かもしれない:: + + module type S = sig + type t + type s = Foo of t + end + + module type S' = S with type t = int + + module type S'' = S with type t := int + +上のソースを ocamlc -c -i でコンパイルすると次のような解釈になっているのがわかる:: + + module type S = sig type t type s = Foo of t end + + module type S' = sig type t = int type s = Foo of t end (* t = int という同値関係が導入されている *) + + module type S'' = sig type s = Foo of int end (* t が無くなって int に置き換わっている! *) + +with type ... = と with type ... := の違いがわかるだろうか。 := では「代入」された元の型は結果のモジュール型から +消え去り、右辺の型に置き換わってしまっている。これを使って、 T with type 'a t := 'a Lazy.t と書けば、 +XLazy のモジュール型を型 t の定義の無いものへと制限することが出来る。そして、 Lazy と (XLazy : T with type 'a t := 'a Lazy.t) +には危険な t の定義対はもはや存在しない! + +この := を Make に移してみよう:: + + module Make (A : S) : T with type 'a t := 'a A.t (* t を置換する! *) = struct + include A + + module Open = struct + let (>>=) = bind + end + + include Open + + let map f t = bind t (fun x -> return (f x)) + + let rec sequence = function + | [] -> return [] + | x::xs -> + x >>= fun x -> + sequence xs >>= fun xs -> + return (x::xs) + end + +こうすれば、 Make(Lazy) には最早 t の型定義は存在しない。だから、:: + + module MLazy = struct + type 'a t = 'a Lazy.t + let return v = Lazy.lazy_from_val v + let bind x f = f (Lazy.force x) + end + + module XLazy = Make(MLazy) + + module Lazy = struct + include Lazy + include XLazy + end + +で上手く書ける! + +結果を Monad モジュールにまとめよう +==================================== + +今までの結果を Monad モジュールにまとめよう:: + + (* 最低限の monadic module type *) + module type S = sig + type +'a t (* covariant *) + val return : 'a -> 'a t + val bind : 'a t -> ('a -> 'b t) -> 'b t + end + + (* 強化された monadic module type *) + module type T = sig + include S + + module Open : sig + val (>>=) : 'a t -> ('a -> 'b t) -> 'b t + end + val (>>=) : 'a t -> ('a -> 'b t) -> 'b t + val map : ('a -> 'b) -> 'a t -> 'b t + val sequence : 'a t list -> 'a list t + end + + (* 最低限から強化版を創りだす functor *) + module Make(A : S) : T with type 'a t := 'a A.t = struct + include A + + module Open = struct + let (>>=) = bind + end + + include Open + + let map f t = bind t (fun x -> return (f x)) + + let rec sequence = function + | [] -> return [] + | x::xs -> + x >>= fun x -> + sequence xs >>= fun xs -> + return (x::xs) + end + +実際に私が使っている monad.ml はこんな感じ。 https://bitbucket.org/camlspotter/spotlib/src/c97d21e10999/lib/monad.ml + +- より多くのモナド操作関数 +- 2 つの型パラメータのモナド用 functor (OCaml には kind inference が無い…無念!) + +Oreore モジュールで強化版モジュール群を管理 +========================================================= + +さて、これでモナドのインターフェースを持たないモジュールを + +- 最低限のモナディックインターフェースを与える (MLazy) +- それを functor で強化してやる ( module XLazy = Make(MLazy) ) +- 元のモジュールと統合する ( include Lazy, include XLazy ) + +の三ステップでモナドインターフェースを与えることができた。 + +例えば、 oreore.ml に:: + + (* oreore.ml *) + + ... + + module Lazy = struct + include Lazy + include XLazy + end + +と書いておけば、 open Oreore と唱えれば、 +さっきまでヒヨワだったハズの Lazy がモナディックな Lazy として立ち上がってくるわけだ。 +Oreore モジュールにはこうやって自分で強化したモジュールをどんどん足していけばいい。 + +Lazy の中で Lazy を include しているのが気持ち悪い、という人は、:: + + module Stdlib = struct + module Lazy = Lazy + end + + module Lazy = struct + include Stdlib.Lazy + include XLazy + end + +とでもして、明示的に include されてる Lazy はオリジナルの stdlib 由来だと判るようにすればいいだろう。 +私はそこまでしてもあまりしょうがないかな、と思っている。 + +oreore.mli を完結に書く +========================================================= + +最後に oreore.mli を書いておこう。上の例だと強化 Lazy の signature を書くことになる。 + +一番カンタンなのは、オリジナルの Lazy からコピペする方法。でも、ダサい。というかコピペ死すべし。 +コピペでは、オリジナルの Lazy に関数が足された場合、 Oreore.Lazy の signature 方が追随できない。 + +それより完結なのは module type of Lazy を使う方法。こんな感じだ:: + + (* oreore.mli *) + module Lazy : sig + include module type of Lazy + (* Inherits the original Lazy module *) + + include Monad.T with type 'a t := 'a Lazy.t + (* Adding monadic interface *) + end + +これを見れば Lazy はオリジナルの Lazy と Monadic インターフェースの両方持ってるのね、とわかる。 + +実はこんなにすっきり行くのは Lazy.t が type 'a t = 'a lazy_t というエイリアスだから。 +Variant や record 型が含まれている場合は厄介だ。:: + + (* oreore.mli *) + module Unix : sig (* 実は良くない *) + include module type of Unix + (* Inherits the original Unix module *) + + val usleep : float -> unit + (** better sleep *) + end + +これは Unix モジュールに usleep を足してみたものの signature (良くない)。 usleep の実装は、まあ適当に interval_timer を +使えば OCaml だけで書けるので実装は省略。 + +実はこれが良くない。オリジナルの Unix 中の variant や record 型と強化 Unix の型が別モノと認識されるのだ。 +強化 Unix だけ使っていれば問題はないが…もし第三者のライブラリがオリジナルの Unix を使っていて、そこから得られる +値、例えば型 open_flag の値を強化 Unix で使おうとすると…使えない。 + +なんで?これは次の例でわかる:: + + $ ocaml unix.cma + + # module U : module type of Unix = Unix;; + + module U : sig + ... + + type open_flag = + O_RDONLY + | O_WRONLY + | O_RDWR + | O_NONBLOCK + | O_APPEND + | O_CREAT + | O_TRUNC + | O_EXCL + | O_NOCTTY + | O_DSYNC + | O_SYNC + | O_RSYNC + + ... + end + +え? open_flag ちゃんとあるじゃん? でも、これは Unix.open_flag じゃなくて U.open_flag。両者には何の関係もない! +Unix.O_RDONLY = U.O_RDONLY は型エラーになる! これは、例えば:: + + module A = struct type t = Foo end + module B = struct type t = Foo end + +と書いたときに A.t と B.t は定義の字面は同じだけど違う型なのと同じ理由だ。(同じだったら困る!) +この B.t が A.t と実は同じ、と言いたければその事をちゃんと示してやらねばならない:: + + module A = struct type t = Foo end + module B = struct type t = A.t = Foo end (* 同値関係を明示 *) + +これと同じ事を(U や)強化Unix に対してもしてやれば、オリジナル Unix との interoperability を実現できる:: + + (* oreore.mli *) + module Unix : sig + include module type of Unix with type open_flag = Unix.open_flag + (* Inherits the original Unix module *) + + val usleep : float -> unit + (** better sleep *) + end + +あー、これで完成! いや、 Unix には他にも沢山型があるのだ。それについても同値性を宣言しなければ…ならない! :: + + (* oreore.mli *) + module Unix : sig + include module type of Unix + with type error = Unix.error + and type process_status = Unix.process_status + and wait_flag = Unix.wait_flag + and open_flag = Unix.open_flag + and seek_command = Unix.seek_command + and file_kind = Unix.file_kind + and stats = Unix.stats + and ... + (* Inherits the original Unix module *) + + val usleep : float -> unit + (** better sleep *) + end + +これは…めんどくさい!! 残念ながら今のところ、この with type の長い列がついたモノと同等の signature を +簡単に得る記法は存在しない。 + +この場合は、敢えて、 強化 Unix の signature を書かない、つまり、 oreore.mli を書かないのも選択の一つだ。 +.mli の無いライブラリモジュールなんて! いやしかし、 .ml は至極カンタンに書ける。ならば .mli は必要ない! :: + + (* xunix.ml *) + let usleep sec = ... + + (* xunix.mli *) + val usleep : float -> unit + (** better sleep *) + + (* oreore.ml *) + module Unix = struct + include Unix + (* Inherits the original Unix module *) + + include Xunix + (* Inherits my extended XUnix *) + end + +ここでは Unix に付け加える関数 usleep を xunix.ml に定義、そのドキュメントを xunix.mli に書いて、 +oreore.ml でそれを include している。 oreore.mli は無い。 oreore.mli が無いからドキュメントが… +心配する必要は無い。 oreore.ml はシンプルだ。 Unix はオリジナルの Unix と強化 Xunix から出来ている。 +オリジナル Unix も Xunix もちゃんとドキュメント化された .mli が用意されているからそれを参照すればよい。 diff --git a/bb/mutable.md b/bb/mutable.md new file mode 100644 index 0000000..b17dfb5 --- /dev/null +++ b/bb/mutable.md @@ -0,0 +1,154 @@ +変更可能(mutable)なデータの扱い +==================================== + +String は mutable +============================================================= + +`string` は mutable。コピーせずに let で引き回しても、 alias が作られるだけで実体は同じ。 +Alias の片方の中身を変更するともう片方も変化する: + +``` +let s = "hello world";; +let s' = s;; +s.[0] <- 'x';; +s';; +``` + +さて `s'` はどうなっているか。 + +同じ string 定数に見えても実体は違う +============================================================= + +`"hello" != "hello"` である。なぜなら上の通り、 string は mutable だから。 +定数のつもりでも定数ではない: + +``` +let s = "hello" in +let s' = "hello" in +s.[0] <- 'x'; +s, s' +``` + +が、: + +``` +let s = "hello" in +let s' = s in +s.[0] <- 'x'; +s, s' +``` + +に最適化されると、困るだろう。 + +このことから、同じ文字列がコード中に複数出現した場合、 OCaml ではそれらは +それぞれ別の文字列としてコンパイルされる。 string の mutability を使わない場合 +メモリの無駄になるので、特にコード自動生成の場合に、 +同じ文字列が大量に作られることがあり、注意。 + +同じ string 定数を返す関数は全く同じ実体の string 定数を返す +============================================================= + +文字列定数について、もう一つ: + +``` +let f () = "hello" +``` + +としたとき: + +``` +f () == f ();; +``` + +は真。 `f ()` は何やら新規に文字列を作る関数の様に読みたくなるけれども、同じポインタを返してくる。 + +これは普通のイミュータブルデータの場合と同じ: + +``` +let f () = [1;2;3] in +let () = assert (f () == f ()) + +let g x = [x;2;3] +let () = assert (g 1 != g 1) +``` + +`[1;2;3]` は定数なのでわざわざコピーしない。 + +ただ問題は文字列が mutable なこと。このため: + +``` +let s = f () in +s.[4] <- '!' +``` + +とすると次回から `f ()` を呼ぶと、 `"hello"` ではなく `"hell!"` が返って来る +ようになる。たまに慣れていない人がハマるとかなり困ったバグを引き起す。一回一回新しい文字列を +作る場合は: + +``` +let f () = String.copy "hello" +``` + +や `String.create len` を使う。 + +同じ配列定数を返す関数は全く同じ実体の配列定数を返…さない!!! +======================================================= + +ところがだ、: + +``` +let f () = [|1;2;3|] +``` + +としたとき: + +``` +f () != f ();; +``` + +である。おいおい、実際 `char` の配列である文字列と挙動が違うだろう!! +中身が mutable であるデータ構造はコピーされるのだが、文字列だけは挙動が違うということになっている :-( + +String は immutable になる(予定…) +================================= + +文字列が mutable だと色々とこのようなデバッグしにくい問題が出て来るので 4.02.0 からは +`string` を immutable にして mutable なバイト列は `bytes` として区別を +していこうということになった。ただしこの区別はまだ過渡的なものでコンパイラに +`-safe-string` を付けないとこの二つの型の区別されないようになっている。 + + +`Array.create` を使ってたらまずバグを疑う +============================================================= + +`Array.create` を使う前に、それは `Array.init` の間違いではないか確認すべし。 +`Array.create` はもらった値のアドレスをコピーせずに配列全体に埋めるので、 +配列の要素の実体は全て同じになる:: + +``` +let r = ref 0 +let a = Array.create 10 r +let () = r.(0) := 42 +``` + +この時、 `a` の中身はこのようになっている:: + +``` +[|{contents = 42}; {contents = 42}; {contents = 42}; {contents = 42}; + {contents = 42}; {contents = 42}; {contents = 42}; {contents = 42}; + {contents = 42}; {contents = 42}|] +``` + +配列を mutable な値を使って `Array.create` で作ってはいけない。 +もちろん君が何をやっているのか完全に把握している時は話は別である。 + + +`Hashtbl.t` は mutable +============================================================= + +実装が隠蔽されているので忘れがちだが OCaml の幾つかのデータ、 +特に Hashtbl.t は mutable で、 pure ではない。 +だからスレッドと一緒に使うときは mutex で保護しなければ謎の誤動作やクラッシュが頻発する。 + +要はスレッドなど使わなければ良い。どうせいまのところ OCaml は parallel GC が無いので +並列(スピード)的に利点は無いし、並行性が欲しければ Lwt などの協調スレッドを使えばよいのだ。 diff --git a/bb/oasis.rst b/bb/oasis.rst new file mode 100644 index 0000000..31cfaa1 --- /dev/null +++ b/bb/oasis.rst @@ -0,0 +1,360 @@ +========================================== +OCaml のパッケージシステム OASIS を使ってみた +========================================== + +*この文書はかなり古い。今は OCaml でパッケージと言えば OPAM の時代だ。 + +今日も OCaml ライブラリのソースをダウンロードするところから始めている皆さん、こんにちは。 + +はっきり言って、面倒ですよね。一度ダウンロード、コンパイルに成功したら、それのソースツリーを置いておけばそれまでなんですが、 +他のマシンでコンパイルしたくなったりしますよね。 +私はブチ切れて OMake で指定 URL からダウンロードして apt-get+configure+make+install までオンデマンドでやっちゃうシステムを組みました。 +マシンが変わっても ``omake`` 一発で全部やってくれるのが気持ちいいです。 (https://bitbucket.org/camlspotter/omy/overview) + +それでもやっぱり、ライブラリがバージョンアップしたら、また始めからソース取ってきて確認、は変わりません。 + +そうなるとやっぱり、 OCaml にも、 Haskell の Cabal みたいなパッケージシステムが欲しい所ですよね。 +GODI (http://godi.camlcity.org/godi/index.html) とかもあるんですが、 GODI のパッケージは GODI にしか使えない。また、良くも悪くも GODI の為のパッケージングしか提供しません。 + +他のパッケージシステム(例えば Debian とかのバイナリパッケージ)にも使えるメタデータを提供できる枠組みが欲しいよね、 +あとどうせパッケージの詳細書くのだったら、そこからビルドスクリプト(Makefile とか)も生成できる方がいいよね、 +ということで、 OCamlForge の人たちが OASIS http://oasis.forge.ocamlcore.org/ というのを作っています。 +OCaml のビッグユーザである Jane Street も賛同しているので、これはメインストリームになりますから必見ですよ! +まあ、まさに今皆でバグ出ししている最中なのでまだホットすぎるけど。 + + しかしこの OASIS いうのが一般名詞過ぎて検索しづらいのよね… ocaml oasis 位で検索しないとどもならん + +さて、その OASIS なんですが、今のところターゲットビルドシステムとして OCamlBuild を考えてる。普通の Makefile とか、私の好きな OMakefile は + + OMake (todo) + +とか書かれていて、その時点で試す気が沸かなかったのですが、実は OMake な人でも(もちろん、ほかのビルドシステムでも)それなりに書けることが解ったので +ちょっと触ってみることにしました。 + + +そもそも OASIS て何よ +========================================= + +まあぶっちゃけ Cabal のパクリですわ。(だから多分、 Cabal と同じような依存地獄が出てきますよw 一応、 Cabal ユーザにどこがムカツク?ってサーベイしてたけど。) + +_oasis + ソフトウェアパッケージの名前、作者、バージョン、ライセンス、依存する他のパッケージ名、ビルドするターゲットモジュール等をつらつらと書きこむ +setup.ml + ``_oasis`` を ``oasis`` コマンドで変換すると ``setup.ml`` というファイルができる。このファイルを使ってパッケージをビルドしたりインストールしたりする +myocamlbuild.ml + ``_oasis`` を ``oasis`` コマンドで変換すると OCamlBuild 用のビルドファイルが出来る。 ``setup.ml`` は実はこれを使う。(OCamlBuild じゃない人は作る必要なし +INSTALL.txt, AUTHOR.txt + ``_oasis`` に必要パッケージとか、作者名とか書いといてくれたら勝手に作ってくれるんだ。英語書かなくてもいい!! + +あなたが ``_oasis`` とソースコードを書けば、 ``oasis`` コマンドが ``myocamlbuild.ml`` (Makefile みたいなものね) と ``setup.ml`` (configure と make みたいなコマンドインターフェース) を作ってくれる、その上、_oasis は共通フォーマットだから、(将来)それをアップしとくだけでパッケージサイト(Oasis DB)に登録されたりするわけで、もうとっても Cabal チック。パッケージに興味のある人はダウンロードして、 ``ocaml setup.ml -configure`` ``-build`` ``-install`` で完了。めでたしめでたし! という感じで、パッケージ書く人も使う人も嬉しい、ことになっています。 + + でも、俺の環境じゃコンパイルできねえお前何とかしろっていうスパムももれなくついてきそうだね! + まあ、パッチ送ってくるならともかく、デフォでガン無視だよね! + +OCamlBuild 以外でも OASIS が使える +=================================== + +さて、そんな OASIS なんだけど、今のところビルドシステムは OCamlBuild しか対応してない。んで、私は OCamlBuild 使わないんです。多分日本一 OMake 使ってますんでー。 + + OCamlBuild、簡単なことするんだったら、いいらしいですよ、ホント。試してみて下さいよ。 + + 私はね、OCamlBuild との出会いが良くなかった。 + OCamlBuild は開発時から OCaml に付属する CamlP4 や OCamlDoc をビルドできるように開発されたんだけど 、 + 両方共結構大きいアプリケーションだから、その myocamlbuild も当時世界一複雑だった。 + それを、まず例として見てしまった、ウゲェ無理ですよ!でやめちゃったんです。 + まあ、 hello world すっ飛ばしてコンパイラの実装を見てしまったような感じですね。 + + もう一度言うけど簡単なとこから始めるといいらしいよ。 + OMake も極まってくると一見さんさようならデスカラネ。 + +でも実は実は!別に OCamlBuild じゃなくっても OASIS 使えるんです!知らなかったんです! +今のところ、_oasis から OMakefile の雛形を作れないってだけで、自分ですでに OMakefile とか、 +普通の Makefile 書いてる場合は問題ないのでした。(ちゃんと目立つとこにそう書いといて欲しいです…) +なんで、 私の(ビルドシステム的に)極まったライブラリ達を OASIS化してみようじゃあ、あーりませんか。 + +OASIS をインストールするよ! +=================================== + +なんでパッケージシステムを使うのに、パッケージからじゃなくてソースからビルドしなきゃいけないんですか、ぷんぷくりーん、 +なので、バイナリを使うことにしました。 +https://forge.ocamlcore.org/frs/?group_id=54&release_id=343#oasis-0-2-0-title-content +Linux のバイナリは…なんか GUI のインストーラが立ち上がったぞw Ubuntu だと libpcre.so.0 のリンクが問題があるようです。 ``ln -s libpcre.so.3 libpcre.so.0`` して ``LD_LIBRARY_PATH`` でおk。 ( https://forge.ocamlcore.org/tracker/?func=detail&aid=784&group_id=54&atid=291 ) + +OASIS をとりあえず使ってみるよ +========================= +QuickStart 読めや: http://oasis.forge.ocamlcore.org/quickstart.html + +やる気 +----- +とりあえず、新しいプロジェクトを始めよう! という意気込みを持つ(ふりをする。練習なので + +QuickStart +---------- +新しいディレクトリ掘って ``oasis quickstart`` でもれなくアンケートに答えよう! +変な答を入れるとまた始めからやりなおしなので、体力が必要だ! ( https://forge.ocamlcore.org/tracker/?group_id=54&atid=291&func=detail&aid=797 ) +例えばモジュール名は大文字で始めないと門前払い! + +適当に答えてたら MyGreatLibrary の為の ``_oasis`` ができた! 見てみよう:: + + OASISFormat: 0.2 + Name: MyGreatLibrary + Version: 42.0.0 + Synopsis: My great library + Authors: My name is great! + License: LGPL-2.0 with OCaml linking exception + + Library my_great_library + Path: lib # . はやめた方がいいよ + BuildTools: ocamlbuild # ocamlbuild 用スクリプトを生成してくれる + Modules: Great # 大文字で始める。 lib/great.ml を書くこと + InternalModules: GreatInternal # 指定しなくてもよい + +この時点での注意は上にコメントで書いといた。 + +特に、 Path: はトップディレクトリ ``.`` でも、いいんだけど、やめといた方がいい。 +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +既に自分で書いたパッケージを OASIS 化する時、特に。 +後述の ``ocaml setup.ml -ほげほげ`` する時に、トップディレクトリもサーチパスに入っているので ``stream.ml`` とか +OCaml 標準ライブラリと同じ名前のファイルがあると爆発するんだ。 + +ソースをでっち上げる +----------------- + +次にやることは、ソースを書くこと。 ``lib/great.ml`` に君の極まったライブラリを書いてください。今回は ``touch lib/great.ml`` で許しといたるわ。 + +oasis setup そして、設定、ビルドしてインストール! +-------------------------------------------------- + +``oasis setup`` で ``_oasis`` からビルドに必要な ``myocamlbuild.ml``, ``setup.ml`` をなんとなく自動生成してくれるよ。 + +OCaml トップレベルと ``setup.ml`` を使ってビルドしてみよう!:: + + $ ocaml setup.ml -configure + ... + $ ocaml setup.ml -build + I: Running command '.../bin/ocamlbuild lib/great.cma lib/great.cmxa lib/great.a -tag debug' + .../bin/ocamlopt.opt ... myocamlbuild.ml ... -o myocamlbuild + ocamlfind ocamldep -modules lib/great.ml > lib/great.ml.depends + ocamlfind ocamlc -c -g -I lib -o lib/great.cmo lib/great.ml + ocamlfind ocamlc -a lib/great.cmo -o lib/great.cma + ocamlfind ocamlopt -c -g -I lib -o lib/great.cmx lib/great.ml + ocamlfind ocamlopt -a lib/great.cmx -o lib/great.cmxa + +あ、なんか出来た…(もちろん空だけど) あとは ``ocaml setup.ml -install`` でインストールしたり、 ``-uninstall`` でアンインストールしたりできる。まったくカンタンだ。 + +あとは、 ``_oasis`` や ``setup.ml``, ``myocamlbuild.ml`` 他、生成されたファイルを github か bitbucket に突っ込んだら一丁上がり! +君も OCaml デベロッパだ! おっと、 ``lib/great.ml`` も忘れないようにな! + +既存ライブラリを OASIS でパッケージ化してみる +======================================= + +さて、ここまで読むと、なんだか OASIS って勝手に ``myocamlbuild.ml`` 作ってくれるのはいいけど、それで決め打ち見たいだし、 +「俺の極まった ``myocamlbuild.ml`` を上書きするんじゃねぇー」 +とか、 +「俺は OMake 信者だから OCamlBuild は死んでも使わねー」 +という人が出てきます。で、 OASIS 使えねー、というとそうでもないんですね! +今度はそれを見ていきましょう! + +まず、トップディレクトリから .ml/.mli をサブディレクトリに移動 +----------------------------------------------------- + +上でも書きましたけど、 ``ocaml setup.ml`` との相性が悪い場合があるので、トップにソースを置かないことです。 +当然、ビルドのための Makefile (OMakefile も同様、以下省略) はトップからサブを呼び出すようにします。 + +_oasis をでっち上げる +-------------------- + +``oasis quickstart`` で適当に答えられるところだけ答えて ``_oasis`` を作ってしまいましょう。 +例えば、上の MyGreatLibrary みたいなので構いません。 + +_oasis を変更しよう +-------------------- + +``_oasis`` を変更して、自動ビルドスクリプト生成をオフ、そしてビルドコマンドを指定します。結論から言うと OMake だとこんなファイルをつくる:: + + OASISFormat: 0.2 + Name: MyGreatLibrary # 多分スペース無しで、小文字の方がよいかも。 + Version: 42.0.0 + Synopsis: My great library # ここはかっこいい名前を自由に書ける + Authors: My name is great! + License: LGPL-2.0 with OCaml linking exception + Plugins: StdFiles (0.2) # INSTALL.txt や README.txt を自動で作ってくれる。 + BuildType: Custom (0.2) # ocaml setup.ml -build の時に XCustomBuild を使うおまじない + InstallType: Custom (0.2) # ocaml setup.ml -install の時に XCustomInstall を使うおまじない + XCustomBuild: omake # ビルドの時はこのコマンドをつかうぜ + XCustomInstall: omake install # インストールの時はこのコマンドをつかうぜ + XCustomUninstall: omake uninstall # アンインストールの時はこのコマンドをつかうぜ + BuildTools: omake # omake コマンドが無いとコンパイルできないよ! + + Library my_great_library + Path: lib + FindlibName: my_great_lib # findlib で my_great_lib っていう名前にするよ + BuildDepends: unix # unix という findlib package が必要なんだ! + Modules: Great, # モジュール名はカンマで区切るんだ + Greater, + EvenGreater, + Greatest + +要するに、キモは、 ``BuildType``, ``InstallType`` を ``Custom (0.2)`` に指定して、 +``XCustomHogehoge`` にそれぞれのコマンドを書けばいいだけなんだね! +``ocaml setup.ml -hogehoge`` は単に ``XCustomHogehoge`` のコマンドを実行するラッパになります。 +ていうか、それだけの事なんだ… OMake(todo) とか書かンといて欲しいわ… + +もちろん、 BuildType を Custom にすると ``oasis setup`` しても ``myocamlbuild.ml`` は生成されなくなる。 + + ``BuildType`` や ``InstallType`` 、そして ``ConfType`` を ``Custom (2.0)`` に指定し忘れていると + 現時点では ``XCustomHogeHoge`` を書いてもガン無視する素敵バグがあるので注意だ!! + +後は、 ``ocaml setup.ml -hogehoge`` をテストしてちゃんとビルドやインストールできるか確認しよう。 + +パッケージ化した! で、どうすんのん? *将来* OASIS DB で公開しよう +============================================================ + +*今じゃないぞ!* + +ソフトウェアを OASIS でちゃんとパッケージ化すると、 ``_oasis`` に依存情報が書きこまれているはず。 +例えば、上の例では、 ``BuildTools: omake`` とか、 ``BuildDepends: unix`` とか書いてありますね。 +例えばここにバージョン情報も書けるようです。例えば ``BuildDepends: oUnit (>= 1.0.3)`` とかね。 +findlib がバージョン 1.0.3 以上の oUnit を見つけないと ``ocaml setup.ml`` が失敗しちゃうわけです。 + +OASIS では、この ``_oasis`` に記述された提供バージョンと、依存バージョンを使って、ああ、このパッケージには +このパッケージが必要だな、とか、考えるわけですね。 + + いやー、もうこの辺りで OASIS が Ports や Cabal より、上手くいくはずが無いような気がしますが… + まあこれは、ソースパッケージの宿命ですよね。 + まあ、パッチ送ってくるならともかく、デフォでガン無視だよね! + +様々なパッケージの様々なバージョンを管理しよう、という、パッケージレポジトリ (CPAN とか Hackage に対応するもの)が OASIS DB です。 +( http://oasis.ocamlcore.org/ ただいま準備中。人柱は http://oasis.ocamlcore.org/dev/home ) + +あるパッケージのあるバージョンが欲しい、そういう時は OASIS DB を検索しますし、 +また、パッケージを作成して、それが検索されるようにするには OASIS DB に登録することになります。 + +残念ながら、 OASIS DB は OASIS に輪をかけて絶賛テスト中の状態です、 +だから、おおっ、よさげ、 OASIS を使うよー、というカジュアル OCaml ユーザーは、あいや、暫く!あいや、暫く!、です。 + +OASIS DB で公開する方法(今日現在) +------------------------------- + +まず、 OASIS DB にパッケージを登録してみましょう。これは超ムズイ、というか、今のところ間違って変なものを登録すると、 +やり直せない、というドキドキ仕様です。私も spotlib-1.0.0 を変に登録してしまいました。消せません。メールして直してもらいました。 +でも多分少しでも多く皆がアップロードして盛り上げていったほうがいいと思うので、簡単にハマリポイントを説明しますね。 + +基本的にこっから先は地雷原です。 +何かあったらすぐ思考停止して https://forge.ocamlcore.org/tracker/?atid=294&group_id=54&func=browse にレポートするのが良いみたいですね。 + +2011/06/06: 一週間かそこらで、投稿パッケージの _oasis ファイル変更が可能になるそうです。これで間違った依存情報を上げてしまっても後から修正することができますね。 + +OCamlForge のアカウントを作る +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +えっ、持ってないの? +この際です。作りましょう: https://forge.ocamlcore.org/account/register.php + +Tarball を作る +~~~~~~~~~~~~~~~~~~~~ + +OASIS DB には "tarball" でソフトを提出せよ、とあるんですが、これが曲者で、どんな tarball か、まぁあああああたく、記載がないのです。 + + 記載してクレロンってメール送っといたから何か改善されるかもしれないね + +以下は私が推測した今日現在(2011/06/05)の条件です。 + +``xxx.tar.gz`` の形であること + 例えば、上の例で言うと、 ``MyGreatLibrary-1.0.0.tar.gz`` でしょうか。(パッケージ名、バージョン名を持つ必要はありません) +Tarball の中身がトップディレクトリ一つで、 ``xxx/_oasis`` を持っていること + ``MyGreatLibrary-1.0.0/_oasis``, ``MyGreatLibrary-1.0.0/lib/great.ml`` ってことですね。(ディレクトリ名がパッケージ名、バージョン名を持つ必要はありません) +.tar.gz であること + .tar.bz や .tbz は無理です。 .tgz はどうでしょうね。試してません。 + +Tarball をどこかに置く +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +これも謎なんですが、 OASIS DB 自体に tarball をコピーして取っておいてくれるのに、他の所からも同じ tarball を入手できるように +しなきゃいけません。 bitbucket や github とかそういうのに何かそういう機能ありますよね?そこに置きましょう。 + +Upload しる! +~~~~~~~~~~~~~~~~~~~~~ + +Tarball の準備ができたら、アップします。 + +まず、 http://oasis.ocamlcore.org/dev/home の Upload というリンクを押す + Upload page に移動します。今日現在、あまりに素っ気無い作りに脱力すること請け合いです +Tar ball にローカルに存在する tar.gz ファイルを指定 + 今のところ tar.bz ダメです。 tar.gz です。 +Public link に同じ Tar ball を http でダウンロードできる URL を入れる + じゃあなんで、ローカル tarball を指定せなイカンのか判りません。 + ここで私は意味がわからなかったので、 tarball ではなくドキュメントの URL を入れてしまい、放置プレイ中です。 +Upload を押す + 何か問題があると、全く愛想のないエラーメッセージで上手くいかなかったことがわかります。 + 何がどううまくいかなかったかは判りません (>_<) +サマリを確認して、確定する + やり直しできません。漢気を感じさせる作りですね! (>_<) + +さて、無事に upload が済むと、 http://oasis.ocamlcore.org/dev/browse にあなたのパッケージがリストされているはず。 + +OASIS DB からパッケージを落としてきてインストールする +============================================================ + +OASIS DB からパッケージを落としてきて宜しくやるには、 ODB ってパッケージャを使います: http://oasis.ocamlcore.org/dev/odb/ +``odb.ml`` てファイルを ``ocaml odb.ml`` って立ち上げるといい。 密かに curl が必要です。 + +あるパッケージと、依存パッケージをガガっとインストールするには ``ocaml odb.ml --repo unstable <パッケージ名>`` とするようです。 +``--repo unstable`` はパッケージレポジトリの種類を選んでいます。将来的には OASIS DB の人が頑張って、これは stable、 +これは testing ってやってくれるみたいです。ホンマかいな。とりあえずは遊びですので、一番数のある unstable ですね。 +たとえば csv パッケージをインストールしてみました:: + + # ocaml odb.ml --repo unstable csv + Getting URI: http://oasis.ocamlcore.org/dev/odb/unstable/pkg/info/csv + Getting URI: http://oasis.ocamlcore.org/dev/odb/unstable/pkg/info/ocamlbuild + ... + Package ocamlbuild dependency satisfied: true + ocamlfind: Package `csv' not found + Package csv dependency satisfied: false + % Total % Received % Xferd Average Speed Time Time Time Current + Dload Upload Total Spent Left Speed + 100 64377 100 64377 0 0 8718 0 0:00:07 0:00:07 --:--:-- 10090 + csv-1.2.2/ + csv-1.2.2/INSTALL.txt + ... + I: Running command '.../bin/ocamlc.opt -config > '/var/tmp/oasis-b142a0.txt'' + I: Running command '.../bin/ocamlfind query -format %v findlib > '/var/tmp/oasis-94ff1d.txt'' + + Configuration: + + ocamldoc: ...................................... .../bin/ocamldoc + OCamlbuild additional flags: ................... + Compile with ocaml profile flag on.: ........... false + ... + + I: Running command '.../bin/ocamlbuild src/csv.cma src/csv.cmxa src/csv.a examples//example.native -tag debug' + ... + I: Installing findlib library 'csv' + I: Running command '.../bin/ocamlfind install csv src/META $HOME/.odb/install-csv/csv-1.2.2/_build/src/csv.cmi ... + Installed $HOME/.odb/lib/csv/csv.mli + Installed $HOME/.odb/lib/csv/csv.cma + Installed $HOME/.odb/lib/csv/csv.cmxa + Installed $HOME/.odb/lib/csv/csv.a + Installed $HOME/.odb/lib/csv/csv.cmi + Installed $HOME/.odb/lib/csv/META + ocamlfind: Package `csv' not found + Package csv dependency satisfied: false + Problem with installed package: csv + Installed package is not available to the system + Make sure $HOME/.odb/bin is in your PATH + and $HOME/.odb/lib is in your OCAMLPATH + +ふーむナルホド。 csv パッケージを取ってきて、(途中の微妙なエラーメッセージは気になりますが)ビルドして、デフォルトでは ``$HOME/.odb`` にインストールするのですね。 +うーん、 Cabal そっくりだね。ホントに Cabal と同じ問題がありそうダネ… +最後の二行にあるように、 PATH と OCAMLPATH を設定してあげればあとは良いみたいです。 +基本的な機能は動いているみたいですね!! + +まとめ: みんな _oasis 書こう +============================================================ + +そんなこんなで OASIS 体験してみました。親指シフトより簡単です。 +見てきたように、別に OCamlBuild 使わないとダメということもありません。 +OCaml でプログラムを書いてる皆さんはとりあえずゆっくりと _oasis を書き始めてみたらいいんじゃないかな? + +で、余裕があれば OASIS DB に登録してみてくださいね!! diff --git a/bb/ocaml-ext-tools.rst b/bb/ocaml-ext-tools.rst new file mode 100644 index 0000000..0fbe01f --- /dev/null +++ b/bb/ocaml-ext-tools.rst @@ -0,0 +1,619 @@ +============================================================= +OCaml 開発環境について ~ コンパイラに付属しない非公式ツールたち +============================================================= + +2012年12月での関数型言語 OCaml コンパイラ一式には入っていない +内部もしくは外部開発されたのツール群の紹介を行う。 +例によって多岐に渡るので、一つ一つの詳しい説明は行わない。 +各ツールの細かい情報はそれぞれのドキュメントを参照して欲しい。 +リンクは貼るの面倒だからググって。 + +もし知らないツール名があったらちょっと読んでみて欲しい。 +もしかしたらあなたの問題を解決するツールがあるかもしれないから。 +ライブラリとツールの中間のようなコード生成系も取り上げた。 +あくまでも基本的に私が触ったことのある物しか紹介しないから、 +そっけなかったりするのはあまり触ってないということ。 +なんでこれはなんで取り上げてないの?と思ったら、それは使ったことないから。ごめんね。 +不満があったら自分で紹介記事書いてください夜露死苦! + +★は重要度。五点満点。 + +コンパイラ同梱のツールの紹介はもうした。 +http://d.hatena.ne.jp/camlspotter/20121204/1354588576 + +@tmaeda さんも既に似たようなのを更に詳しく書いてた… +http://tmaeda.s45.xrea.com/td/20121028.html + +ビルド関連 +================================ + +OMake ★★★★ +-------------------------------- + +ビルドツール。 + +Make や OCamlBuild と同様のビルドツール。 + +* Makefile のような文法によるビルドルール表記が可能で参入障壁が低い +* Makefile よりも随分マシなセマンティックスな言語。関数による際利用可能なビルドルール記述 +* ファイル更新時ではなくファイルのチェックサムによるリビルド。更新されたが変化はなかった自動生成ファイルの不要なコンパイルが行われない +* サブディレクトリでのビルドが楽 +* 依存関係を解析して複数コアを使った並列ビルドが簡単 +* ``-P`` スイッチによるファイル変更時の自動リビルド +* OCaml プログラムビルドのための便利な機能がもともと入っているので、OCaml のための複雑な関数記述が不要 +* LaTeX や C のルールももうある +* ちゃんとスケールする (Jane Street の 100万行程度の多岐のディレクトリにわたる OCaml プロジェクトでも動く) +* マニュアルに日本語の訳がある (英語: http://omake.metaprl.org/manual/omake.html 日本語: http://omake-japanese.sourceforge.jp/ ) + +私は OCaml のプログラムは今のところ OMake を使っている。スケール感半端無いので。問題がないわけではない: + +* スコープルールが特殊。ビルドのために便利なようになっているらしいが、わかったようなわからないような、である +* 依存関係を完全に抑えなければいけない。さもないと1コアだとコンパイルできるけど複数コアだとビルドに失敗する +* お仕着せの OCaml ビルドルールで満足できなくなると関数を沢山書き始めなければならない。あまり嬉しくない。 +* ld as needed と相性が悪く ``-P`` のビルドが上手くいかない人が ( http://d.hatena.ne.jp/camlspotter/20121002/1349162772 ) + +それでも小さい OCaml プロジェクトの OMakefile はあっけないほど簡単に書けるので使ってみてほしい。 +(まあこれは OCamlBuild にも言えることなんだけどね) + +OCamlMakefile ★★ +------------------------------ + +OCaml のための Makefile マクロ集 + +OMake や OCamlBuild みたいな新しい物を覚えるのは年を取ってしまってどうも…という人は +OCamlMakefile という OCaml プログラムをビルドする際に便利なマクロが詰まった Makefile +を使うと良い。が、 OCamlMakefile のマクロを覚えるコストと OMake や OCamlBuild の初歩を +学ぶコストはどちらが小さいか。 + +私は5日程使った記憶がある + +OCamlFind / Findlib ★★★★★ +--------------------------------- + +ライブラリパッケージマネージャおよびビルド補助ツール + +非常に重要なツール。 +OCamlFind はある種のパッケージシステムでもあるわけだけど +パッケージ配布には焦点を置いていないのでビルドシステムに分類した。 + +OCaml コンパイラはモジュールをまとめてライブラリにするリンカ機能は当然あるが、 +その出来たライブラリをどのように管理すべきか、統一的な方法はコンパイラ開発チームは +あまり提唱しなかった。そのため、作ったライブラリは ``ocamlc -where`` で表示される +OCaml 標準ライブラリがインストールされている場所に直接放り込む、とか、 +まあそこに hogehoge ライブラリなら ``hogehoge`` というディレクトリを掘ってそのこに +放んでり込む、とか、各人が思い思いにインストールしていたのだった。 + +またライブラリを実際に使用して実行プログラムにリンクする際のコンパイラフラッグの管理 +も大変だった。もし hogehoge というライブラリを使いたいとすれば、 ``hogehoge.cma`` +などが格納されていパスを ``-I`` で指定する必要があるし、 +もし hogehoge が fugafuga に依存していれば当然 fugafuga に必要なコンパイルスイッチ +も一緒にコンパイラに渡してやらなければならない。もちろんこれは丁寧に Makefile を書いて +再利用できるようにしておけば何の問題もないのだが、まあ、面倒だった。 + +OCamlFind はこの二つの点を次のように解決する: + +* ライブラリ(群)をパッケージとして扱い、パッケージ名による管理を可能にする +* パッケージ管理者は各ライブラリの依存関係をパッケージの ``META`` ファイルに記述 +* OCamlFind は OCaml コンパイラのラッパとして動作、使用するライブラリ名を渡すと必要なスイッチをコンパイラに自動的に渡す + +例えば、今私がいじっている OCamltter を改造したライブラリをビルドすると +こんなコマンドが発行される:: + + ocamlfind ocamlc \ + -for-pack Twitter \ + -package cryptokit,str,sexplib,spotlib,tiny_json_conv \ + -syntax camlp4o -package sexplib.syntax,meta_conv.syntax \ + -thread -w A-4-9-27-29-32-33-34-39 -warn-error A-4-9-27-29-32-33-34-39 \ + -g -I ../base -I ../twitter -I . -c \ + api_intf.ml + +``-package ...`` とか ``-syntax ...`` のオプションは OCamlFind に +cryptkit, str ,sexplib, spotlib, tiny_json_conv +というパッケージを使うこと、拡張文法として camlp4o を使うこと、 +そして文法プラグインとして sexplib.syntax と meta_conv.syntax を使うことを指示している。 +それ以外は ocamlc にある普通のオプション。え、これで既に長いって? + +上のコマンドから OCamlFind が実際にどのような ocamlc コマンドを発行しているか、 +-verbose オプションを付けてると、表示してくれる:: + + ocamlfind ocamlc -verbose ... (上と同じ) + + ocamlc.opt -verbose -for-pack Twitter -w A-4-9-27-29-32-33-34-39 -warn-error A-4-9-27-29-32-33-34-39 -g \ + -I ../base -I ../twitter -I . -c -thread -I /my_home/.opam/system/lib/num \ + -I /my_home/.opam/system/lib/cryptokit -I /my_home/.opam/system/lib/spotlib \ + -I /my_home/.opam/system/lib/tiny_json -I /my_home/.opam/system/lib/tiny_json_conv \ + -I /my_home/.share/prefix//lib/ocaml/camlp4 -I /my_home/.opam/system/lib/type_conv \ + -I /my_home/.opam/system/lib/sexplib -I /my_home/.opam/system/lib/meta_conv \ + -pp "camlp4 '-I' '/my_home/.share/prefix//lib/ocaml/camlp4' '-I' '/my_home/.opam/system/lib/type_conv' \ + '-I' '/my_home/.share/prefix//lib/ocaml' '-I' '/my_home/.share/prefix//lib/ocaml' \ + '-I' '/my_home/.share/prefix//lib/ocaml' '-I' '/my_home/.opam/system/lib/num' \ + '-I' '/my_home/.opam/system/lib/sexplib' '-I' '/my_home/.opam/system/lib/sexplib' \ + '-I' '/my_home/.opam/system/lib/meta_conv' '-I' '/my_home/.opam/system/lib/meta_conv' \ + '-parser' 'o' '-parser' 'op' '-printer' 'p' 'pa_type_conv.cma' \ + 'unix.cma' 'bigarray.cma' 'nums.cma' 'sexplib.cma' \ + 'pa_sexp_conv.cma' 'meta_conv.cmo' 'pa_meta_conv.cma' " \ + api_intf.ml + .... + +ということだ。大量の ``-I`` フラッグがついている。 +さらに、``-package`` には type_conv や meta_conv を指定しなかったが +sexplib と tiny_json_conv がこれらを必要としていることがそれぞれの META ファイルに +書かれているので、 type_conv と meta_conv のフラッグが自動的に加わっている。 + +OCamlFind は OCaml のライブラリを駆使するものはまず使う必須ツールなので、 +ちょっとややこしいことをする場合は使ったほうがいい。 + +ちなみに OCamlFind は Findlib というライブラリの上に作られたツールなので自分自身の OCamlFind パッケージ名は findlib。なのに OPAM パッケージ名は ocamlfind というちょっと変な名付けになってる。 + +OCamlFind, 便利なんだけど、さらに camlp4 のラッピングをしてコード展開を楽にしてくれるととても嬉しいのだが、 +そんな機能はないのだなあ。 P4 の結果を調べるときには、いちいちコマンドを手打ちしなければならない。 + +パッケージシステム +======================== + +この数年 OCaml界ではパッケージが熱い。 + +Oasis ★★★ +------------------------- + +統一的ビルドインターフェースを提供 + +OCaml のソフトウェアはビルドシステムが自由に選べる。 configure + Make, OCamlBuild, OMake など。 +問題はビルド方法がひとつひとつ違うことだ。ユーザーは一度一度 INSTALL.txt などを読まなければならない。 +Oasis はそんな問題を解決する: OCaml で書かれた setup.ml というファイルを使うのだ。 +``ocaml setup.ml -configure`` で設定、 ``ocaml setup.ml -build`` でビルド、 ``-install`` +でインストールすると言った具合。つまり Oasis による ``setup.ml`` があればビルドシステムが何であろうが +ユーザは ocaml setup.ml からインストール作業ができる。 + +Oasis では ``_oasis`` という設定ファイルに色々書くと自動的に ``oasis setup`` で setup.ml を +作成してくれるのだが、その際、``_oasis`` から OCamlBuild のビルドファイルを自動的に作ってくれたり +OCamlFind の META フィアルを作ってくれたりするようだ。 +Readme や INSTALL.txt を勝手に作ってくれたり、 +ソフトウェアライセンスとかも記述でき、コピーライトファイルを自動的に取ってきたり、 +いろいろ機能はあるみたいなんだけど…私には、ちょっとやりたいことが多すぎて手が回ってない感じのツールだな。 + +私は OMake ユーザーであり、 OMake は Oasis で全くサポートされていないのでビルドファイル生成とかの +恩恵は全く無い。 +まあ _oasis ファイルを書いて oasis setup すると OMake を呼んでくれる setup.ml を +作成することはできる…でもそれだけ。参考までに OMake で使うばあいの ``_oasis``:: + + OASISFormat: 0.2 + Name: spotlib + Version: 2.1.0 + Synopsis: Useful functions for OCaml programming used by @camlspotter + Authors: Jun FURUSE + License: LGPL-2.0 with OCaml linking exception + Plugins: StdFiles (0.2) + BuildType: Custom (0.2) + InstallType: Custom (0.2) + XCustomBuild: yes no | omake --install; PREFIX=$prefix omake + XCustomInstall: PREFIX=$prefix omake install + XCustomUninstall: PREFIX=$prefix omake uninstall + BuildTools: omake + +OMake はサポートされていないので ``XCustomなんちゃら`` を使う。まあこれで setup.ml から omake が呼べるようになる。 +( http://d.hatena.ne.jp/camlspotter/20110603/1307080062 ) +Custom なのでビルドの自動設定はできないが… ``_oasis`` の Library エントリとか妙によくわからないので +書けないなら書けないで…まあ構わないのだ。 + +Oasis パッケージを管理する Oasis DB というモノも作られかけていたが…コケた。 +アップロードがあまりに不親切かつ面倒だったからだ。今はもう OPAM repo だね。 + +OPAM ★★★★★ +------------------------- + +パッケージマネージャとパッケージレポ + +Oasis はパッケージとそのビルドに焦点を当てたツールだったが、 OPAM はどちらかというとパッケージとその配布管理 +に重きをおいたパッケージマネージャ。 OPAM では Oasis は setup.ml を提供するツールとして普通に共存できる。 + +OPAM は Oasis と違ってビルドスクリプトの方には手を出さない。そのかわり ``opam`` ファイルに +ビルドするには、インストールするには、アンインストールには、どんなコマンドを発行するか、を記述する。 +コマンドはシェルで解釈されるので ``ocaml setup.ml`` だろうが configure + make だろうが +``ocamlbuild`` だろうが ``omake`` だろうが何でもかまわない。 +これは Oasis がそのあたり便利にしようとしてコケている事への反省だと思う。 + +さらに、パッケージが別パッケージのどのバージョンに依存しているかも ``opam`` ファイルに記述するのだが +この際のアルゴリズムとして Debian のパッケージと同じアルゴリズムが使われている、まあ枯れていて強力 +ということなのだろう。 + +例として私が書いている opam ファイルはいつもこんな感じ:: + + opam-version: "1" + maintainer: "hoge.hoge@gmail.com" + build: [ + ["ocaml" "setup.ml" "-configure" "--prefix" "%{prefix}%"] + ["ocaml" "setup.ml" "-build"] + ["ocaml" "setup.ml" "-install"] + ] + remove: [ + ["ocaml" "setup.ml" "-uninstall"] + ] + depends: [ "ocamlfind" "spotlib" {>="2.1.0"} "omake" "orakuda"] + ocaml-version: [>= "4.00.0"] + +Oasis でビルド方法を統一してあるので、 ``build`` と ``remove`` ルールはいつも同じ。 +依存情報である ``depends`` と ``ocaml-version`` を書き換えるくらいしかしない。 +というわけでなんだかんだ言って Oasis は使えるところは使えるのである。 + +この ``opam`` ファイルに加え、ソフトウェアの説明を記述した ``descr``、ソフトウェアの tarball +をどこに置いたか、そしてそのチェックサムを記録した ``url`` この三点セットのファイルで一つのパッケージ +情報になる。これを opam-repository のレポに置けば誰もがそこから三点セットをダウンロードして +opam コマンドで OCaml ソフトウェアを簡単にインストールできる。自分で OPAM パッケージ +を作る場合はこの公式レポを fork して変更の pull request を送れば良い。平日なら日本の午前に出せば +夕方には取り込まれる。 + +(もちろん OPAM もソースを使ったソフトの配布システムなので環境が違うとインストールできないという事は +普通にある…万能なソースベースのパッケージシステムなんかないのだ) + +そんなこんなで OCamlFind, Oasis, OPAM の住み分けは(少なくとも私には)こんな感じになってる:: + +* OCamlFind を OMake で使う。最後は ocamlfind install で META ファイル含めてインストール +* Oasis で OMakefile を呼び出す setup.ml を作る +* ソースと setup.ml をレポに上げてバージョンのブランチなりタグを作る +* ブランチもしくはタグに対応する tarball を url に書いて opam, descr と一緒に OPAM レポに pull request +* アップデートリリースのアナウンスは面倒だからしないw opam update したらそこに見つかるだろうから + +GODI ★? +-------------------- + +これまたパッケージシステム。 + +OCamlFind の人が書いた OCaml パッケージシステムのはしり。 +私はほとんど使ってないし使っていたのも随分前のことで、いろいろとストレスを感じた記憶がある。 +パッケージにあるソフトを改造しにくかったような…今は改善されているのではないか…とも思うが、 +Oasis や OPAM との比較は私にはできません。誰か教えてください。 + +コード自動生成 +============== + +CamlIDL ★★ +------------------ + +OCaml と C の間を取り持つ FFI(Foreign Function Interface) の自動生成ツール + +OCaml は C や他言語との橋渡しに C を使う。C関数を OCaml の関数として使うことができるのだが、 +そのままでは普通は使用できない。C関数を OCamlの GC やメモリモデルに沿った形で呼び出す +ラッパ関数(スタブ)から間接的に呼び出す必要がある。 +そのスタブの型安全性は全く保証されていない。正しい記述方法は +http://caml.inria.fr/pub/docs/manual-ocaml-4.00/manual033.html +に記載されているとおりだが、ちょっと間違うとすぐにプログラムがクラッシュする。 +それも GC に関連する問題だと大変だ、間違った関数を呼んでもそこではクラッシュしない… +しばらくたって GC が走ると…ボン!だ。スタブのデバッグは大変だ。 + +CamlIDL は MIDL という C のヘッダにアノテーションを記述することで +C関数を OCaml から呼び出すためのスタブを自動生成するツール。 +一応 OCaml のモジュールを COM コンポネントにする機能も付いているが、こっちは知らない。 + +アノテーションが正確である限り CamlIDL は正しいスタブを作ってくれる。 +むろん、アノテーションを間違うとどうしようもないが、それでも手でスタブを書くよりは +手間は省けるし安全かもしれない。簡単な型の C関数ならかなり楽にスタブを作ってくれる。 + +が、そのアノテーションが抑えられないような物を書こうとすると工夫が必要になる。 +例えば polymorphic variant を使ったサブタイプを入れたいなど… +そういう場合は IDL ファイルに前処理をしたり +生成された OCaml コードに後処理をしたり、まあいろいろとやれないこともない。 +が、まず CamlIDL のチュートリアルから。 + +まあスタブが10個くらいですむなら私は手で書く。ちゃんと OCaml ランタイムのことがわかっていれば +手書きでもそう間違いはおこらないはずだ。スタブが100個とかになると CamlIDL や +自分で頑張ってコード生成器を書くか (LablGtk2 など) 工夫してやることになる。 + +Type_conv, Sexplib, Bin_prot ★★★ +------------------------------------- + +型定義から便利なコードを自動生成するフレームワーク、とその応用 + +代数的データ型を使っているとその代数構造を利用したプログラムコードを +沢山手で書く、大変便利なわけだが、その代数構造から決まりきったコードを記述することが +ままある。例えばプリンタとか:: + + type t = Foo | Bar of int + + let show_t = function + | Foo -> "Foo" + | Bar n -> "Bar of " ^ string_of_int n + + type t' = Poo | Pee of float + + let show_t' = function + | Poo -> "Poo" + | Pee n -> "Pee of " ^ string_of_float n + +上の例でもわかるようにコンストラクタ名や型引数の違いはあるが、``show_t`` も +``show_t'`` も基本的にやってることは同じ。完全にルーチンワークだ。 +こういったルーチンワーク(Boiler plate code)は書きたくない、できればコンパイラに +自動生成させたいというのが人の常で、type_conv はこういった型の代数的構造から自然と決まるコード +の自動生成を支援するための CamlP4 フレームワーク。type_conv では type 宣言が拡張されていて +``with <名前>`` というのをくっつけることができる:: + + type t = Foo | Bar of int with show + + type t' = Poo | Pee of float with show + +こう書くと type_conv は ``show`` という名前で登録されたコード生成モジュールを +呼び出して型定義情報を与える、生成モジュールはやはり P4 で書かれていて例えば +上の ``show_t`` や ``show_t'`` を生成する。もちろん生成モジュール +は誰かが書かねばならない。 まあ、 Haskell の deriving をよりプログラマブルに +倒したものと考えれば当たっているだろう。 + +type_conv でよく使われるコード生成モジュールが sexp と bin_prot。両方共 +OCaml の値の一種のプリンタとパーサを提供しているが sexp が S-式の形で、 +bin_prot が通信に特化した binary の形で出入力を提供する。 +Sexp は 設定ファイルに OCaml の値を直接書き込んだり、読み込んだり、 +人がエディタで変更したりできるので、結構便利。 +また、型 t を sexp_of_t で S-式に変換した後、``Sexp.pp_hum`` で +プリティプリントすることで簡単なデバッグプリントでの OCaml の値のプリントができる。 +(もちろん S-式の形でプリントされるので読みにくいかもしれないが、 +慣れれば結構読めるものである) + +type_conv 以下は Jane Street 謹製なので安心。 + +問題は自分で生成モジュールを作るのは P4 プログラミングを伴うので結構大変ってこと。 +自作が面倒なら sexp の S-式から何とかするのが楽。 +Sexplib はかなりちゃんとドキュメントが書かれている。 + +OCaml-Deriving ★★★ +-------------------------- + +OCaml-deriving は type_conv と同じ目的のやはり CamlP4 でのフレームワーク。 +こちらは ``with hoge`` の代わりに ``deriving hoge`` と書く。js_of_ocaml +で使われている。 Type_conv と OCaml_deriving が共存できるかどうかは、知らない。 + +OCaml-deriving は show がすでにあるのが嬉しいかな。まあ type_conv でも meta_conv +使って ``with conv(ocaml)`` すれば同じ事出来るけどね。 + +Atdgen ★ +------------------- + +Atdgen はこれまた型定義からのコード自動生成ツール。ただし、これは CamlP4 ではなくって +OCaml のコードを読んで、型定義から関数ソースを生成する独立したフィルタプログラム。 +そしてターゲットは JSON に特化しているみたいだ。まあ、 CamlP4 書くの大変だもんね… +これは OCaml でウェブ系の仕事しているアメリカ人たちが使っている様子だ。 + +プログラミング環境 +=============================== + +Tuareg ★★★★★ +--------------------- + +Emacs の OCaml コードインデンタとハイライタ。 + +OCaml コンパイラ付属の OCaml-mode でええやんという人もいるが Tuareg が好きという人もいる。 +どちらがいいのかは、正直よくわからない。特に私は toplevel でコード片を eval したりしない人なので… +Jane Street が Tuareg を使っていて、特に Monad の bind 関係でインデントを整備していたので +そのあたり、もしかしたら Tuareg のほうが使い勝手が良いこともあるかもしれない。 +OCaml-mode も Tuareg もインデントは完璧ではないので気に入らなければ、提案されるインデントは +無視して手で調整する。 C とか Java みたいな硬いインデントポリシーはないのでそこら辺は臨機応変にしよう。 + +繰り返しになるけれども、 Tuareg を使っていても caml-types.el や camldebug.el は普通に使えます。 + +後述する Cheat Sheet によれば、Tuareg ってなんかすごくキーショートカットがある、 +多分1/10も使ってないわ私… + +Vim 関連 +----------------- + +私 Vim 使わないからよくわからないわー。ゴメンナサイ。 + +* ocaml.vim とか omlet.vim とか聞きますね。どちらがいいんでしょうね。 +* ocaml-annot という caml-types.el に相当するもの (http://blog.probsteide.com/getting-started-with-ocaml-and-vim) +* https://github.com/MarcWeber/vim-addon-ocaml +* OCamlSpotter にも一応、 ocamlspot.vim てものがあるけど、私使わないから…直してみてよ + +utop ★★★ +-------------- + +OCaml の標準の REPL である ocaml toplevel はラインエディタ機能もついていないという +ミニマル製品なので rlwrap や Emacs の shell モードの中などで実行することで +エディタ力を強化してやる必要がある。まあこれは Unix 的発想で良いと思うんだけど、 +この頃の若者はそういう寛容さがないから無理を強いられていると感じるのしら。 + +utop は ocaml toplevel を強化したもの。ラインエディット、補完とかカラーつけたりカッコ対応表示したり +できる…使ってみると実際カラフルで全然 Caml っぽくないw が…何気に必要ライブラリすごくないかい? + +私は REPL 使わない派なので使ったことなかったんだけど、補完はなかなか良さそうだ。 + +コンパイラテクノロジ寄りの開発強化ツール +============================================ + +まあ、なんというか分類しにくいんですが、コンパイラのかっちょいい機能を使った +カッチョイイ開発ツール達。 + +OCamlSpotter ★★★★ +------------------------- + +名前の定義を探しだすコード解析ツール + +人が書いた OCaml コードで、この変数の定義はどこか?とか、この型の定義はどこに? +とか検索するのは結構骨が折れる。 grep や tags では polymorphism や let shadowning がある +OCaml ではいくつも候補が出てきてしまい、そういう際にはどれが正しい定義かよくわからなくなってくる。 + +しかし人間にはわからなくてもコンパイラは全てを知っている。OCamlSpotter はコンパイラがソースを +コンパイルした際の結果である cmt ファイルを解析し、ソースコードに現れる名前が、どこで定義されたものかを +解析表示することで、コードを読む際の手間を大幅に短縮するツール。Emacs や Vim からも呼び出すことが +でき、簡単なキーでカーソルにある名前の定義へとジャンプすることができる。 + +OCamlSpotter を利用するにはソースコードを -bin-annot というオプションでコンパイルし、 cmt ファイル +を生成する必要がある。そしてもちろんこの cmt ファイルとソースファイルは消さずに残して置かなければならない。 +ライブラリがインストールされる場合には cmt ファイルも共にインストールする必要がある。 + +これは私が書いたツールなのだが、私の生計は OCaml プログラミングではなくなった今、あまり以前のように +ガンガンとメンテする暇がないのが残念。とりあえず最新の OCaml コンパイラでとりあえず動くものは公開しているが +バグもある(なかなか直らない)。バグは bitbucket の issues +( http://bitbucket.org/camlspotter/ocamlspot )に報告してくれれば直す気も出るし、 +パッチはもっと歓迎。 + +TypeRex ★★★★ +------------------------ + +Emacs 用の OCaml IDE。 + +OCamlSpotter と同じような機能にさらに独自ハイライトや +インデント、リファクタリング(変数名を変更すると同じ変数(同じ名前の変数ではなく、同じ定義を指す変数だけ!を変更してくれる) +も搭載されている。うまく動けば超強力らしい。 + +問題は設計がこりすぎていて、Mac OS X となにか問題があるようで、動かなかったりする。 +TypeRex が動かなかったら OCamlSpotter も試してみてくれい。 + +Spotter も TypeRex も使ってない caml-types.el も使ってないとかいう人は +演習が終わったら OCaml もう使わないほうがいいと思う。 F# とか IDE あるでしょ? + +OCaml API Search ★★★ +----------------------------- + +型式や名前から関数や型定義を探し出す Webツール。 @mzp さん作。 +http://search.ocaml.jp/ + +スタンドアローン GUIツールである OCamlBrowser を Web にしたもの。 +OCamlBrowser を Tcl/Tk が無いのでインストールしていない人には便利。 +ただし、 Stdlib と Extlib しか検索できない。 + +今や OPAM があるので OPAM パッケージを全て対応とかしたら嬉しいんじゃないだろうか。 +そこまで OCamlBrowser/OCaml API Search の検索アルゴリズムがスケールするのか、どうか興味もある。 + +cmigrep ★ +--------------------- + +cmigrep はコンパイラが生成した cmi ファイルを解析して grep 的にパターンに合致する +値や型を探し出すコマンドラインツール。 +OCamlBrowser は GUI で面倒、OCaml API Search はサーチスペースが +どうしても固定されてしまう、という時、 cmigrep だとちょっと取っ掛かりが難しいが、 +網羅的に調べるのに便利といえるかな。 + +コンパイラ内部依存なので、使用するには各コンパイラごとにちょっとした修正が必要。 +私は自分で 4.00.1 に対応させているけど +( https://bitbucket.org/camlspotter/cmigrep-fork )、 +確か誰かが同じ事をして公開しているはずです。 + +OCamlClean ★? +--------------------- + +これはぜーーんぜん使ったこと無いのだが、 PIC で OCaml を動かすという +OCaPIC project の産物。Dead code elimination を行なって +バイトコードプログラムの挙動は同じままにサイズを減らしてくれる。 +(OCaml バイトコードコンパイラは使ってないコードもそのままリンクする。 +バイトコードはバイトコードで最適化はほとんど行わないというポリシーなので。) +js_of_ocaml でもデッドコード消去は行われているはずだけれど、 +これを事前に使うと嬉しいことがあったり、しない?する? +わかりません。なんで書いといた。 + +強化ライブラリ +============================== + +この紹介は開発ツールということで、ライブラリは飛ばすつもりなのだが、 +強化基本ライブラリに関しては例外。 + +OCaml の標準ライブラリはとても貧弱。 +長らく、各人がそれぞれ自分で育てた強化ライブラリを使って仕事をしてきたが、 +さすがにそれではいかんだろうという事で強化された基本ライブラリが幾つか +発表されている。 + +Dev はもっとユーザを束ねて基本ライブラリ拡充運動を一本化して行うべきだったと思う。 +正直この辺で手を抜いていたので OCaml 使えねーというイメージが固定化されてしまったのでは +無いかと思っている… + +Jane Street Core ★★★★ +--------------------------- + +OCaml を使って高頻度金融取引をしている Jane Street Capital が自分達で +使用するプログラムを開発するにあたって作った強化基本ライブラリ。 +OCaml の標準ライブラリに無かったデータ構造が、あ! Core にはある! +これも!これも!という嬉しさがよい。 +多分従業員がこれに費やした時間をお金に換算すると億円単位は行ってると思うので +間違いなく品質は良い。 + +Jane Street 内での仕様を第一に考えて作ってあるので、少し癖があるところもある。 +例えば、関数の引数で混乱を避けるためにラベル付き引数がふんだんに使用されており、 +人によっては過剰かと思うかもしれないし、至るところ Sexplib による S-式エンコーダ +が張り巡らされていて一部それを使うことを強制されているところもある。 +また、ライブラリ全体は巨大な core.cmo というファイルにパッケージされるのだが、 +これをプログラムにリンクすると当然実行ファイルも巨大なものになる。 +(この問題は OCaml コンパイラの問題として認識されていて、多分近い将来解決されると思う) + +私は… Jane Street で働いているときは当然使っていたけど、 +私が公開しているソフトはライブラリが多く、 Core に依存性を持たせると +使ってくれる人がいなくなるだろうと思い意識的に避けている。そのかわり、 +Core で得た経験を基に自分用の小さい基本ライブラリを作っている。 + +他人にリンクされることのないアプリケーションレベルのプログラム開発なら手を出す +価値は十分にある。 + +OCaml Batteries Included ★★★★ +----------------------------------- + +OCaml Batteries Included は Python の Batteries Included から名前を +インスパイヤされた強化基本ライブラリ。 + +私は使ったことがない。理由は Jane Street Core に慣れているから。 +なので違いとかもよくわからない。 + +Core と Batteries の併用は…わからないけどやめておいたほうがいいと思う。 +結構機能的に重複があるし、Core は C言語で書かれた部分もあるから競合しているところがあるかもしれない。 + +Extlib ★★ +----------------------------------- + +Extlib は Batteries Included の基になったより小さい強化基本ライブラリ。 +Batteries をリンクするのは大きすぎて困るが OCaml 標準は足りなさすぎる… +という時に使うと良い。 + +強化パーサージェネレータ +======================== + +Ulex ★★★★ +------------------ + +Unicode aware な Lex。ニホンゴガー言うてる人はどうぞ使ってみてください。 +私は使ったこと無い。 + +Menhir ★★★★★ +------------------- + +強化された OCamlYacc。ほとんど OCamlYacc の上位互換で同じ \*.mly が使えるにも +関わらず、エラーメッセージが判りやすいうえに OCamlYacc では受け付けない形の +パースルールも幾つか拾ってくれる、というわけで良いことしか無い。 Yacc 使うなら +ocamlyacc じゃなくて Menhir。約束だ。 + +テストフレームワーク +======================== + +OUnit ★★★ +------------- + +ユニットテストライブラリ + +テストは簡単には assert でやるもんですが、それが沢山になってくると、どのテストが通ったかとかどれが通ってないとか +調べたくなるもの。OUnit はベタな assert を organized な物にするためのライブラリ。 + +テストの元になってる最小単位は ``test_fun``、要は ``unit -> unit`` でエラーの場合は ``Failure`` 例外を上げる +関数。これを ``(>::)`` で名前をつけて ``test`` にしてやる。複数の ``test`` を ``(>:::)`` +でまとめて一つの大きな ``test`` にしたり、などなど、テストという概念の簡単なコンビネータがある。 +最終的に全てのテストを一つの ``test`` にまとめ上げたら ``perform_test`` 関数で走らせる。 + +OUnit は単にテストをまとめ上げるためだけだから、 QuickCheck 的なランダムテスト自動生成とかは、ない。 + +テストが大量にあってカバレージが気になる人は使うといい。テストが少量とか、100% 通らないと困る、 +という人はあえて使わなくてもいいんじゃないか。 + +OCaml-QuickCheck ★? +-------------------- + +書いてみただけ。試したこと無い… + +基本的に Haskell の QuickCheck を持ってきただけなので type class の辞書飛ばしを +マニュアルでやらないといけない。面倒そうだ。 +https://github.com/camlunity/ocaml-quickcheck +このフォークが 3.12.x の first class module を使っていて +その辞書飛ばしの部分は少し使いやすいそうだ。 +しかし、自動値生成として type_conv なり deriving 使ってないと +大変だと思う。多分そういうの無いよねこれは… + +ドキュメントw +====================== + +Cheat Sheets +----------------------- +http://www.ocamlpro.com/blog/2011/06/03/cheatsheets.html + +OCaml 関連のカンニングペーパー。文法からコンパイラのスイッチ、 Tuareg まで、 +まあ簡単にまとまっていること! diff --git a/bb/ocaml-object.rst b/bb/ocaml-object.rst new file mode 100644 index 0000000..612ddb8 --- /dev/null +++ b/bb/ocaml-object.rst @@ -0,0 +1,127 @@ +================================ +OCaml の object について +================================ + +私は OCaml の オブジェクトシステムそして普通世間一般の オブジェクトシステムについて理解が足りないと +自覚しているので正直こういう文章を書く自信全く無いのですが… + +OCaml のオブジェクトは構造的サブタイピング +============================================== + +OCaml の オブジェクト のサブタイピングは structural subtyping というものです。 +世間一般の nominal なサブタイピングとは常識が異なるので、まずそこから難しいく映るかもしれません。 + +Nominal なサブタイピングの世界 +---------------------------------------------- + +Nominal な世界ではオブジェクトはクラスに属します。まあ大雑把に言うと オブジェクトの型はクラス名。 +そしてオブジェクトの型(クラス名)はその *名前* で区別(identify)されます。だから nominal と言う。 + +クラスはオブジェクトの実装を与えると同時にその型を定義します。 + +クラスは他のクラスを継承でき、サブクラスになります。 + +オブジェクトのサブタイピングは、それが属するクラスの継承関係がそのまま使われます。 +クラスの identification は名前で行われますから、オブジェクトのサブタイピングもクラス名で決まります。 + +OCaml の Structural なサブタイピングの世界 +---------------------------------------------- + +OCaml の Structural な世界ではオブジェクトはクラスに属しません! +オブジェクトの型はクラス名ではありません! +まあ大雑把に言うとオブジェクトの型はそのオブジェクトが持っているメソッドの型の集合です。 +そしてオブジェクトの型(メソッドの型集合)はその集合の *構造* で区別されます。だから structural と言う。 + +クラスはオブジェクトの実装を与えますが、その型を定義するものではありません! +オブジェクトの型の別名を与えるだけです。オブジェクトの型はあくまでもそのメソッド型集合の構造です。 + +クラスは他のクラスを継承でき、サブクラスになります。 + +オブジェクトのサブタイピングはその *構造によってのみ* 決まります。 +サブタイピングにはオブジェクトを生成するのに使用したクラスの継承関係とは *関係ありません。* + + +クラス抜きで考える OCaml のオブジェクト +=============================================================== + +オブジェクト、いやさ、多相レコード +------------------------------------ + +OCaml のオブジェクトのサブタイピングは nominal つまり属クラス名主義的なものではなく、 +structural つまり、その型の構造で決まる、と言う事を繰り返し強調しました。 +実際 OCaml のオブジェクトのサブタイピングはそのクラスシステムとは独立したものです。 +それはクラスの無いオブジェクト (immediate object) を定義できることからもわかります。:: + + object + method m = 1 + method n x = x + 1 + end + (* 型は < m : int; n : int -> int > *) + +実際のところクラス抜きのオブジェクトは多相レコードにほかなりません。 +多相レコードとは、事前の型宣言のいらない型付きレコードのことです。 + +多相レコードの多相性の例を見ましょう:: + + let get_m o = o#m + (* 型は get_m : < m : 'a; .. > -> 'a *) + +この関数は ``o`` というオブジェクト、いやさ、多相レコードを引数に取りそのメソッド、いやさ、 +フィールド ``m`` を取ってきて返す関数です。 ``get_m`` の型の引数部分を見てください。 +``< m : 'a; .. >`` という形になっている。これは何でもいいから ``m`` というフィールドを +持つ多相レコード、を意味する型です。実際、:: + + # get_m (object method m = "hello" end);; + - : string = "hello" + # get_m (object method m = 1 method n x = x + 1 end);; + - : int = 1 + +こんな風に ``get_m`` は ``m`` の型が違ってたり、 ``m`` 以外のフィールドが +存在する多相レコードであっても取り扱うことができます。 + +``get_m`` の引数の型 ``< m : 'a; .. >`` には二つの多相性があります: + +* m の型は何でも良い事を示す型変数 ``'a`` +* m 以外のフィールドが存在してもよいという事を示す ``..`` + +実際 ``..`` は型変数のような挙動を示すのですが、ここに深入りすると帰ってこれなくなるので +これくらいにしましょう。 + +上の例では ``get_m`` が適用されている二つの多相レコードの型は +``< m : string >`` と ``< m : int; n : int -> int >`` ですが、 +これを ``< m : 'a; .. >`` の二つの多相性によって上手く扱っているわけです。 + +あー、なるほど、これが OCaml の構造的サブタイピングな?と思われますか? + +*違います。* + +これは多相レコードの多相型によるもので、サブタイプではないのです。 +例えば、 ``< m : 'a; .. >`` の型変数である +``'a`` が ``int`` に instantiate され、 ``..`` が ``n : int -> int`` に +instantiate されたものが ``< m : int; n : int -> int >`` になります。 +ちょっと ``..`` の部分が構造に関する多相性で特殊ですが、 +これは単に ML の parametric polymorphism の延長に過ぎません。 + +まあ上では違いますと言い切りましたが、この ``..`` の多相性は +OCaml のオブジェクトにおける"サブタイプ感"を醸しだすのに重要な要素であることは確かです。 + +えっじゃあサブタイピングはどこに? +------------------------------------ + +OCaml におけるオブジェクトのサブタイピングとは上の多相レコードにおける +parametric polymorphism では抑えられない型の大小を取り扱います。 +例えば、:: + + object method m = 1 end (* < m : int > *) + object method m = 2 method n x = x + 1 end (* < m : int; n : int -> int > *) + + +手稿で判読可能な部分はここで途切れている…この先は草稿らしい + +この二つのオブジェクト、そして、それは型推論されません。常に手で書く必要があります。 + + + +クラスの継承関係はオブジェクトのサブタイピングに引き継がれますが、 +それはクラス継承関係が宣言されたからというより、サブクラスから生成される オブジェクトの型構造が +親クラスから生成されるオブジェクトの型構造を必ず含む、ような継承しか許されないからです。 diff --git a/bb/ocaml-tools.rst b/bb/ocaml-tools.rst new file mode 100644 index 0000000..d26d923 --- /dev/null +++ b/bb/ocaml-tools.rst @@ -0,0 +1,318 @@ +============================================================= +OCaml 開発環境について ~ OCaml コンパイラソース付属ツール +============================================================= + +2013年12月での関数型言語 OCaml コンパイラ一式をインストールした際に付属する「公式」ツール群の紹介を行う。多岐に渡るので、一つ一つの詳しい説明は行わない。各ツールの細かい情報はそれぞれのドキュメントを参照して欲しい。 + +もし知らないツール名があったらちょっと読んでみて欲しい。もしかしたらあなたの問題を解決するツールがあるかもしれないから。(特に caml-types.el) + +★は重要度。五点満点。 + +外部ツールの紹介はまた今度ね。 + +OCaml コンパイラ +========================== + +現時点での公式最新バージョンは 4.01.0。 +4.00.1 からの変更点は http://d.hatena.ne.jp/camlspotter/20130904/1378277465 にまとめられている。 + +このところ OCaml のマイナーバージョンの変わるリリースは一年に一度位。 +バグフィックスパッチやリリースはより頻繁にある。 + +ocaml toplevel ★★★★ +-------------------------- +OCaml 対話環境。いわゆる REPL(Read-Eval-and-Print Loop)。 +入力は型推論の後 bytecode へとコンパイルされ VM により評価される。 +Bytecode とはいえコンパイルが入るため、インタープリタとは普通呼ばれない。 +(Native code へとコンパイルする ocamlnat という対話環境も存在する。 +ただしまだ「非公式」) + +ocaml toplevel にはラインエディタ機能はない。行の編集や履歴を呼び出したい場合は、 + +* rlwrap : read line wrapper ( http://utopia.knoware.nl/~hlub/rlwrap/#rlwrap ) +* emacs の shell mode 内などでの実行 +* サードパーティー製の強化 toplevel utop を使う + +などして編集能力を強化するのが普通である。 + +Real World OCaml programmer で toplevel を使うか使わないか…は人により違うようだ。 +Toplevel は値のプリンタがあるので、ライブラリをロードして関数のインタラクティブなテストを行う人はいる。 +一方、私は電卓として使うか型システムの挙動を確かめる時以外全く使わない。 + +ocamlc bytecode compiler ★★★★ (-1) +--------------------------------------- +OCaml ソースコードを bytecode へとコンパイルするコンパイラ。 +Bytecode プログラムは native code と比べると遅いが、 +ocamldebug を使ったデバッグが可能。 + +協調スレッドを多用した OCaml プログラムの場合デバッグは ocamldebug を使っても… +という場合が多く、ocamlopt でコンパイルしたコードを gdb でデバッグするのとあまり +変わらないような気がする。デバッグを期待した ocamlc によるコンパイルは最後の手段だと +思われる。 + +ocamlopt native code compiler ★★ +----------------------------------------- +OCaml ソースコードを native code (マシン語)へとコンパイルする。 +Native code がサポートされているアーキテクチャで +OCaml コンパイラソースコードディレクトリで make opt すると作成される。 +実はほとんど使わない。次の ocamlc.opt, ocamlopt.opt を参照のこと。 + +ocamlc.opt ocamlopt.opt ★★★★★ +--------------------------------------- +Native code にコンパイルされた bytecode および native code コンパイラ。 +Native code コンパイルが可能な環境では通常このコンパイラを使う。 + +OCaml コンパイラソースコードで make opt の後に make opt.opt を行うと作成される。 +通常の ocamlc, ocamlopt は bytecode で実行されるが、 +\*.opt コンパイラは native にコンパイルされているため +ocamlc, ocamlopt より実行速度が早い。 +(Bytecode 版コンパイラがひどく遅いわけではないが。) + +ocamlc, ocamlopt 以外のツールにも、.opt の postfix がついた +native code バージョンが存在する。 + +依存抽出: ocamldep ★★★★★ +========================================== +ocamldep は複数の OCaml プログラムファイル間の依存関係を抽出するツール。 +結果は Makefile の依存書式で出力される。通常は、 +ocamldep \*.ml \*.mli > .depend +として依存情報をファイルに書きだし、それを Makefile 等で include .depend する。 + +使い方の例は、 Makefile を使った OCaml ソフトウェアを見れば、 +まず使用されているので、それらを参考に。 + +重要ではあるが、 ocamlbuild や OMake、 OCamlMakefile などを使えば +ocamldep は自動的に呼び出されるのであまり意識することはない。 + +OCaml パーサーツール +================================ + +OCaml では lex-yacc スタイルのパーサ生成器が標準で付属しており、 +このパーサによって OCaml の文法自体も定義されている。 + +ocamllex ★★★★ +--------------------------- +**注意**: Multi-byte char を処理する場合は、 ulex を使うべきである。 + +Lexical analyser。 Lex のスタイルを踏襲しつつ、 +アクション等のコードを OCaml プログラムで記述できる。 +そのため、基本的に lexer (字句解析)や正規表現の知識が有用かつ前提。 +ocamllex は \*.mll というアクション等のコードを OCaml プログラムで記述できる。 +\*.mll の例は OCaml コンパイラソースの parsing/lexer.mll を参考にするといい。 + +ocamlyacc ★★★★ +-------------------------- +**注意**: 上位互換で、強力かつエラーメッセージの読みやすい Menhir を使うべきである。 + +Parser generator。こちらは yacc のスタイルを踏襲し、アクション等のコードを OCaml プログラムで記述できる。 +そのため、 yacc の知識が必要。例えば shift-reduce, reduce-reduce の知識がなければ使いこなせない。 +ocamlyacc は \*.mly という拡張子のファイルを受け取り、 parsing rule を解釈し、 \*.ml へと変換する。 +注意すべき点は、 OCaml コード以外のパートでのコメントは (\* ... \*) ではなく、 /\* ... \*/ であることくらいか。 +\*.mly の例は OCaml コンパイラソースの parsing/parser.mly を参考に。 +なおその場合は完全に shift-reduce 警告を 0 にしている所を味わうこと。 + +ocamllex, ocamlyacc は色々と古臭い部分もあり、イライラすることもあるが、 +ほとんどアップデートもなく、非常に良く枯れており高速に動作する。 +Lex-yacc も使えずに Parsec があーたらどーたらカッコイイとか構成的に書けるとか +つぶやくのは甘えです。 + +ocamlyacc のほぼ上位互換 parser generator として Menhir という外部ツールがある。 +Menhir は ocamlyacc と同じ \*.mly ファイルを受け取る上に、エラーメッセージが読みやすいなど良い点が多い。そのため、現在 OCaml で parser generator を使う場合は Menhir を使うことが推奨されている。 +(ユーザに Menhir をインストールさせるのが面倒だと思われる場合は、 Menhir の新機能を使わず \*.mly を作り、リリース時には ocamlyacc に戻す、ということも可能。) + + +マクロ/文法拡張システム: Camlp4 pre-processor and pretty printer ★★★★ (-1) +============================================================================= +Camlp4 (略称P4) は Pre-Processor and Pretty Printer の4つの P から P4 と呼ばれ、 +自分でパーサーをスクラッチから記述できるだけでなく、 +OCaml コードでのマクロや文法拡張を実現することもできる強力なツール。 +**P4 を使えるか使えないかで OCaml プログラマの生き死にが決まることもある** +(と思わないとやっていられない時もある)。 + +P4 は yacc のような LALRベースではなく、 +LLベースの stream parsing が使われるため、 +ocamlyacc と camlp4 では文法記述法が異なる。 + +P4 では OCaml の文法が stream parsing で再定義されており、 +このパーサーを使って OCaml プログラムを解釈し、 +その結果を OCaml コンパイラに送ることができる +(OCaml 標準の lex-yacc で書かれたパーサーは迂回される)。 +使い方は OCaml コンパイラの -pp オプションを見ること。 + +この P4 の OCaml parsing rule群を **動的** に変更することで、 +OCaml の文法を拡張することができ、 +単純なマクロから、非常に複雑なメタプログラミングまで P4 が活躍する。 +高レベルな OCaml プログラミングでは、P4 を利用した複数の文法拡張をしばしば利用するため、 +複雑ではあるが非常に重要なツールである。 +文法拡張記述には OCaml の通常の文法 (original syntax) と +OCaml 文法拡張を書く際、 ambiguity が少なくなる改良文法 (revised syntax) の二つの文法を +選ぶことができる。これらの文法を使うかどうかに対応して Camlp4 コマンドも camlp4* から始まる複数のコマンド群からなる。 + +CamlP4 は OCaml 3.10 同梱版より完全にリライトされ、細かい部分がかなり変更された。そのため、「3.10以前系」のチュートリアルドキュメントは「3.10以降系」には細かい点では違いが多すぎて役に立たない。そして、P4 について日本語/英語で書かれたウェブ上のドキュメントはほとんどが「以前系」についてである。「以降系」のドキュメントはあまりない。 +基本的なアイデアは以前系も以降系も同じなので 古い P4 のドキュメントを読んで 以降系 P4 の基本的な使用方法を理解することは可能であるが、その際には必ず 3.10系 P4 の working example などを参照して細かな違いを把握する必要がある。既存の P4 で書かれた文法拡張を使うだけの場合は P4 でのパーサルールの書き方などを理解する必要はないとはいえ改善が望まれる。 + +3.10以降系 P4 のチュートリアルとしては Jake Donham の +Reading Camlp4 http://ambassadortothecomputers.blogspot.com/search/label/camlp4 +は素晴らしい記事であり、推薦する。 + +以下は 3.10以降系 Camlp4 を開発した人が書いた情報。残念ながら全く不十分 + +* Camlp4: Major changes : http://nicolaspouillard.fr/camlp4-changes.html +* Using Camlp4: http://brion.inria.fr/gallium/index.php/Using_Camlp4 + +インターネット上の P4 の情報を調べる際は、必ずそれがいつの時期に書かれたものか、つまり 3.10以前か 3.10以降かを確認すること。 + +* 拙著の投げやりな入門: https://bitbucket.org/camlspotter/ocaml-zippy-tutorial-in-japanese/src/a8da8ba783d1c66e4e19e77cc72c15446c8e9f57/camlp4.rst?at=default + +Camlp5 との関係 +------------------- +Camlp4 とは別に Camlp5 というツールが存在するが、これは OCaml に同梱されていない。 + +Camlp5 は 3.10以前系の Camlp4 が引継がれたもので、コードベースとしては 「3.09 までの P4」 および P5 は似ている。 3.10系 P4 はそれらからかなり離れている。 P5 が P4 より数字が多いため、優れているとか、その逆、という関係ではない。 +なお、 P5 は Coq theorem prover でよく使用されている。 + +P4 と P5 が何故ブランチしたか、はさまざまな事情があるがここで語るべきではない。 + +なお P4 は次バージョンから OCaml コンパイラシステム一式からは外されて独立した +アプリケーションとして管理されることになっている。 + +リンク支援: ocamlmktop, ocamlmklib ★★ +================================================= +ocamlmktop および ocamlmklib は外部Cライブラリをリンクした toplevel や +ライブラリを作成する際に補助的に使用するツール。 + +これらのライブラリや toplevel は +OCaml コンパイラ、C コンパイラ、リンカ、アーカイバ を自分で呼び出すことで +作成できるのだが、この煩雑な作業を代行してくれるのが +ocamlmktop と ocamlmklib である。 + +プログラムビルドシステム: ocamlbuild ★★★★ +=============================================== +プログラムビルドシステム。 + +ocamlbuild は簡単な OCaml ソースに対しては ソースファイル名を列挙するだけでモジュール間の依存関係解析からコンパイル、リンクに至るまでを自動的に行なってくれる。そのため Makefile のような既存の外部ビルドシステムにおけるビルドの煩雑さから解放される。 + +複雑なソース、プログラムコードの自動生成や特殊なリンクが必要な場合など、の場合は myocamlbuild.ml という OCaml モジュールで特殊ルールを記述し ocamlbuild に伝える必要がある。このファイルでは ocamlbuild が提供するルール記述用ライブラリを使うことができる。問題はこのライブラリを使うドキュメントがあまり整備されていないこと。(Camlp4 3.10以降系といい、 ocamlbuild といい 3.10 周りで作られたツールはドキュメントが全くなっていない) また、ルール記述が OCaml という汎用言語で書かねばならないためどう見ても Makefile や OMakefile などのビルドに特化した言語に比べ煩雑に見えてしまうことである。もちろん OCaml の利点である型安全性やパターンマッチ、高階関数などによってビルドルールを構成的に書くことができるのだが…もう少し文法拡張などして DSL の風味を付け加えるべきではなかろうか。 + +私は ocamlbuild は使わない。現在のところ OMake を使っている。とは言え、どうやら世の中的には ocamlbuild が標準になりつつあるのでそろそろ手を出さねばならない…と言いつつ一年が過ぎた。 + +ドキュメントシステム: ocamldoc ★★★ +====================================== +OCaml のコードを HTML や LaTeX の形に抽出するためのドキュメントシステム。 +``(** ... *)`` という特殊なドキュメントコメントを使うことで簡単な整形記法や +コード要素に明示的に結び付けられたドキュメントを簡単に書くことができる。 + +OCaml の標準ライブラリリファレンスドキュメントも ocamldoc によって +各 \*.mli ファイルから自動的に生成されている。 +(逆に言えば、ライブラリリファレンスをブラウザでアクセスせずとも \*.mli を +読めば同じ情報が手に入る。) + +エディタ支援 +================================== + +公式ソースコードに付属するエディタ支援は Emacs, XEmacs の物に限られる。 +ソースコードからビルドしている場合、 make install ではこれらの elisp ファイルはインストールされない。 +導入にはソースディレクトリ/emacs/README を読むこと。 + +caml.el ★★★★★ +------------------------ +OCaml プログラムのインデントとハイライトを提供する Caml-mode を提供する。 +外部ツールである tuareg-mode を好む人(含む私)もいる。 + +caml-types.el ★★★★★ +---------------------------- +任意の部分式の型を表示させることで型エラー解消などの作業を効率的に行うためのツール。 + +OCaml はその型推論のおかげでプログラム中に型を書く必要がほとんどない。そのため複雑なコードも簡潔に、かつ型安全に書くことができる。反面、型を明示的に書くことでプログラムが読みやすくなることもある。型が書かれていないため読みにくい他人の書いたコードや、型エラーが発生したがどうも何がおかしいのかわからない、といったことが起こり易くもなる。 caml-types.el を使えば OCaml コードの部分式の型を例えば明示されていなくともコンパイルの結果から表示させることができる。 **caml-types.el を使っているかいないかで OCaml プログラマの生産性は数倍変わるので生き死にに関係する。** + +OCaml コンパイラ(ocamlc, ocamlopt)に -annot オプションを付けて \*.ml, \*.mli ファイルをコンパイルすると \*.annot というファイルができる。この \*.annot ファイルにはソースコード上の場所をキーとして、そこにある式の型などの情報が記録されている。 +caml-types.el はこのファイルを走査し、部分式の型を Emacs のメッセージバッファに表示する。 + +caml-types.el は caml.el と独立しており、 tuareg-mode と一緒に使うこともできる。 + +VIM ユーザは外部ツール ocaml-annot ( https://github.com/avsm/ocaml-annot ) などを使っているようである。 + +コード検索 +================================== + +OCamlBrowser ★★ +----------------------- + +型式や名前から関数や型定義を探し出す GUIツール。 + +例えば ('a \* 'b) list を扱う関数って何がありますかねぇと思ったら +('a \* 'b) list と入れて Type で検索するとそれらしい型を持つ関数が +ずらっと表示される。 +length って名前の関数はどんな型に定義されているのか知りたければ +length と入れて Name で検索。そんな感じ。 + +OCaml のスタンドアローン Hoogle と言えば Haskell の人には判りやすいだろうが +Hoogle より歴史は古い。 +今は懐かしき Tcl/Tk を使用しているので入っていない環境も多いだろう。 + +これのWeb 版とも言える OCaml API Search (http://search.ocaml.jp )を使う +という手もあるが、ocamlbrowser はスタンドアローンなのでローカルに +インストールされたライブラリも探すことができる点は便利。 + +私は…使わないなー。どんな型に関する関数がどのモジュールで定義されているか +だいたい頭に入っているから対応する \*.mli ファイルをエディタで開いて +使うべき関数名や型コンストラクタを確認するくらいですんでしまう。 + +OCamlBrowser が依存している LablTk ライブラリは次バージョンから OCaml システム一式からは +外されて独立したライブラリとなる。そのため OCamlBrowser も次バージョンからは「付属ツール」 +とは言えなくなる。 + +ほとんど使用されないツール +==================================== + +バイトコードデバッガ ocamldebug ★★ +------------------------------------------ + +ごくたまに利用される程度である。 + +ocamldebug は OCaml の byte code プログラムのためのデバッガ。 +ocamldebug を使うためには各バイトコードオブジェクトファイル \*.cmo を +ocamlc にデバッグフラグ -g を付けてコンパイルする必要がある。 + +ocamldebug では一旦進めたデバッグステップを巻き戻すことができるという、ちょっと変わった機能がある。とは言え… printf デバッグか、 gdb を使った native code プログラムのデバッグの方が判りやすい場合が多い。どうしてもプログラム挙動がわからない場合、念のために使われることが多い。これは ocamldebug が非力だからというのではなく、やはり静的に型付けされた関数型プログラムではキャストの誤りや NULL エラーが起こることがなく、あまりデバッグを必要としないから、というのが大きい。 + +私は使わない…協調スレッドなどの実行順が判りにくいライブラリを使う場合デバッガではプログラムの実行を **人間** が追えないからだ。デバッガは追えていているのだが。 + +caml-debug.el ★ +---------------------- +ocamldebug を Emacs で使うための elisp。 +現在実行中のソースコードの場所などを Emacs 中に表示できる。 + +バイトコードプロファイラ ocamlprof と ocamlcp ★ +--------------------------------------------------------- + +ほとんど利用されない。 + +ocamlprof は byte code プログラムのためのプロファイラ。 +ocamlprof を利用するためには各バイトコードオブジェクトファイルは +ocamlcp という ocamlc のラッパを用いてコンパイルされていなければならない。 + +ocamlcp でコンパイルされた byte code 実行ファイルを実行すると +ocamlprof.dump というファイルが作成される。 +これを ocamlprof コマンドを使って関数などの使用回数を抽出、 +元のソースファイル内にコメントとして書きだす。 + +ocamlprof は呼び出された回数しかプロファイルしないのでほとんど利用されない。 + +OCaml プログラムで真面目にプロファイルを取る場合は、通常 +ocamlopt に -p オプションを付けて native code でのプロファイルを取り、 +そのアプトプットを gprof で可視化するのが普通である。 + +マニアックなツール +================== + +ディレクトリ名がついている場合、それはインストールされないツールである。 OCaml をビルドするとその場所に実行ファイルができる。 + +./expunge + ライブラリ中のモジュールを外部から見えなくするためのツール。A というモジュールがライブラリにリンクされていれば、このライブラリを使うと外部から A という名前でこのモジュールにアクセスすることができる。 A を expunge すると、それができなくなる。コンパイラ屋さんくらいしか使わないツール。 +ocamlobjinfo + オブジェクトファイルやライブラリ \*.cm\* ファイルの環境依存情報を見ることができる。OCaml ではオブジェクトファイル間の整合性は md5sum で管理されているので \*.cmi の整合性が合わない!と言われ、これはコンパイラおかしいだろう!と感極まった場合に使うと良いかもしれない。 +tools/dumpobj + \*.cmo ファイルをダンプして VM opcode を眺めることができる +tools/read_cmt + OCaml 4.00.0 より -bin-annot オプションにより生成されるバイナリアノテーションファイル \*.cmt をダンプしたり、 \*.annot ファイルに変換することのできるツール。まあ ocamlspot を使えってこった diff --git a/bb/ocaml_c_interface.rst b/bb/ocaml_c_interface.rst new file mode 100644 index 0000000..bdf12ef --- /dev/null +++ b/bb/ocaml_c_interface.rst @@ -0,0 +1,58 @@ +============================= +OCaml C interface の注意事項 +============================= + +一言で言うと、「マニュアル http://caml.inria.fr/pub/docs/manual-ocaml/manual033.html の通りにやれ、それ以外のことをするな」に尽きます。 + +C interface がバグっている場合起こること +========================================= + +OCaml の GC タイミングにより、プログラムの全く関係ない所で +プログラムがクラッシュします。これは解析、テストが大変に難しい。 + +対処療法として OCaml のテストプログラムから C関数を何百万回も呼び出し、その都度 +GC を強制的に掛ければ問題の早期発見ができる、かも、しれませんが、 +されないこともあるわけです。そもそも何百万回も呼び出すには +コストがかかりすぎる場合もある。 + +ですから兎に角バクを入れない、つまりマニュアルに沿って書く、 +ことが必要になります。 + + +落とし穴集 +=================== + +兎に角マニュアル 19.5 を完全理解しないうちは OCaml-C interface を書いてはダメ。 + +Simple interface +---------------- + +* Simple interface で使える関数は 19.4.4 の Simple interface にある + +* Rule 2 Local variables of type value must be declared with one of the CAMLlocal macros. Arrays of values are declared with CAMLlocalN. These macros must be used at the beginning of the function, **not in a nested block**. 配列の初期化などでついローカルブロックを使ってから ``CAMLlocaln`` を宣言したくなるが、してはいけない。 + +* Simple interface で確保した ``value`` に対して ``Field(b, n) = v`` と書いてはいけない。 ``Store_field(b, n, v)`` と書く。 + +Low-level interface +----------------------- + +* Low-level interface で使える関数は 19.4.4 の Low-level interface にある + +* Low-level function で確保した ``value`` は、次の allocation が起こる前に必ず正しい値でフィールドが埋まっていなければならない。例えば、 ``caml_alloc_small`` した場合次の allocation が起こる前にフィールドを初期化せねばならない。 一方、Simple interface の ``caml_alloc`` はフィールドは 0 クリアされているので問題ない。 + +* Low-level function で確保した ``value`` のフィールド初期化は ``Field(b, n) = v`` で行う。 ``Store_field(b, n, v)`` を使ってはいけない。既に初期化されているフィールドを変更する場合には ``caml_modify(&Field(b, n), v)`` を使う。 + +CAMLparam と CAMLlocal +---------------------------- + +``CAMLparam`` と ``CAMLlocal`` で受け取った/作った ``value`` は +simple でも low-level でもアクセスしてよいが、一度どちらかで始めたら +混ぜてはいけない。 + +えっ難しい… +------------------------------- + +じゃあ、 simple interface だけ使う。 + +* ``caml_alloc_small``, ``caml_alloc_shr`` は使わない +* ``Field(b, n) = v`` と ``caml_modify(&Field(b,n), v)`` は使わない。 ``Store_field(b, n, v)`` を使う。 diff --git a/bb/ocaml_i18n.md b/bb/ocaml_i18n.md new file mode 100644 index 0000000..93052b2 --- /dev/null +++ b/bb/ocaml_i18n.md @@ -0,0 +1,328 @@ +OCaml プログラムで欧文以外の文字を使う方法 +================================================= + +OCaml ソースコードの文字コード +================================ + +OCaml のソースコード(ml, mli, mly, mll などの拡張子を持つファイル)の文字コードは +リファレンスマニュアルには特に(恐らく)明記されていなません。 +しかし OCaml コンパイラのソースコードを見ると欧文の文字コードである[ISO-8859-1](http://en.wikipedia.org/wiki/ISO/IEC_8859-1)を +**暗に仮定**していることがわかります。 +このため、 OCaml ではソースコードにそれ以外の文字コードを指定するための pragma の +ようなものはありません。 + +ISO-8859-1 を仮定している OCaml ですが、ソースコードの文字コードとして EUC-JP や UTF-8 +を採用し、日本語や非アルファベット文字などを文字列リテラルやコメントに直接書き込むことは +普通におこなわれています。この文書ではこの仮定と、実際について説明します。 + +まずはじめに結論 +-------------------------------- + +* 「ASCII部」のエンコードが ISO-8859-1 と同一で、それ以外の文字は 0x80 以上のバイト符号からのみなる EUC や UTF-8 のような文字コードでは文字列やコメント中に ASCII範囲外の文字列を書き込むことが可能。ただし、文字列処理には注意が必要。 +* JIS (ISO-2022-JP他)などは、アルファベットを含むエスケープシーケンスのため使用不可能 +* Shift_JIS は「[だめ文字問題](http://ja.wikipedia.org/wiki/Shift_JIS#2.E3.83.90.E3.82.A4.E3.83.88.E7.9B.AE.E3.81.8C5C.E7.AD.89.E3.81.AB.E3.81.AA.E3.82.8A.E3.81.86.E3.82.8B.E3.81.93.E3.81.A8.E3.81.AB.E3.82.88.E3.82.8B.E5.95.8F.E9.A1.8C)」があるため使うことは不可能 +* UTF-16, UTF-32 は ASCII部が ISO-8859-1 と一致しないので使用不可能 + +識別子に使用できる文字種 +-------------------------------- + +OCaml では識別子の名前に使うアルファベットとして ASCII の範囲にあるもの ('a'-'z', 'A'-'Z') +だけでなく ISO-8859-1 に登場するすべてのアルファベット('é' などのアクセント記号のついた +文字など)が使用可能です。OCaml プログラムでは識別子の一文字目が小文字であるか、大文字であるかで +その識別子の種類が決まりますが、この ASCII 範囲外のアルファベットにもこのルールが適用されます。 +ただし、この ASCII 範囲外のアルファベットの識別子としての使用は最近の OCaml のバージョン +(4.01.0 から)では推奨されないむね警告が出ます(以下は ISO-8859-1 環境での例。EUC や UTF-8 では再現しません): + +ISO-8859-1 環境: +```ocaml +let café = "coffee" (* 警告 3 が出る *) +``` + +ですから現実的には、OCaml の識別子として使用できる文字は ASCII に限られている +とみなしてよいでしょう。日本語の文字や特殊記号などを識別子として使うことはできません。 + +特定の文字コードでの非ASCII文字列のバイト列には、ISO-8859-1 として無理やり解釈すると +ISO-8859-1 のアクセントつきアルファベットとして解釈できるものがあります。 +(EUC-JP における"珈琲"など。)このような文字コードと文字列を使用すれば、ISO-8859-1 に +含まれない文字であっても無理やり OCaml の識別子として解釈させることは可能です。 +が、冗談以上のものではありません。 + +文字型(`char`)に使用できる文字種 +-------------------------------- + +`char` は ISO-8859-1 の各文字を表すためのデータ型で、データ幅は 1バイトです。 +`Char`モジュールの `lowercase`, `uppercase`, `escaped` も +`char` が ISO-8859-1 の各文字を表しているとして動作します。 + +ISO-8859-1 環境: +```ocaml +let e' = 'é' +``` + +もちろん、文字コードの仮定を無視して `char` を単なる 0 から 255 までの1バイト幅のための +データとして使うことには何の問題もありません。 + +ISO-8859-1 環境: +```ocaml +let c240 = char_of_int 240 +let () = print_int (int_of_char c240) +``` + +ISO-8859-1 以外の文字コードを使ってコードを書いている場合でも、 ISO-8859-1 と一致する +部分(EUC や UTF-8 であればASCIIの範囲)の文字については `char` を使用することができなくはありません。 +ただし、 EUC や UTF-8 などのマルチバイト文字を含むような文字コードではマルチバイトとなる +文字を表記することができませんから、これらのコードで統一的に文字を扱うばあい、 `char` は +あまり役に立たないと思われます。 + +EUC もしくは UTF-8 環境: +```ocaml +let ko = 'こ' (* Error: Syntax error *) +``` + +文字列(`string`)に使用できる文字種 +-------------------------------- + +`string` は ISO-8859-1 の文字列を表すためのバイト列を表すデータ型です。 +`string`内の各バイトが ISO-8859-1 の文字を一つ表します。 +`String` モジュールの `escaped`, `uppercase`, `lowercase`, `capitalize`, +`uncapitalize` といった関数も ISO-8859-1 の符号列として `string` を処理します。 + +ISO-8859-1 環境: +```ocaml +let coffee = "café" +let () = print_endline (String.uppercase coffee) (* CAFÉ と出力される *) +``` + +もちろん、文字コードの仮定を無視して `string` を単なるバイト列のデータとして +使うことも可能です。 + +ソースコードを EUC や UTF-8 とみなした場合、これらの文字コードの文字列を +`string`リテラルに直接埋め込むことは可能で、実際に良く行なわれています。 +OCaml はこれらのバイト列を ISO-8859-1 として解釈しますが、間違って文字列のパースに +影響を及ぼすような文字エンコードは無いため問題はおこりません。 + +EUC もしくは UTF-8 環境: +```ocaml +let hello = "こんにちは" +let () = print_endline hello (* 標準出力に こんにちは と出力される *) +``` + +ただし、上記の大文字小文字変換などの ISO-8859-1 を仮定する関数を使うと意図しない結果となります。 +また、`substring` など文字列の位置や長さを取る関数をマルチバイト文字を含む文字列の処理をする場合 +には ISO-8859-1 としての文字長(つまりバイト数)と使用している文字コードでの文字数との間の変換に +注意が必要です。 + +EUC もしくは UTF-8 環境: +```ocaml +let hello = "hello こんにちは" +let () = print_endline (String.uppercase hello) (* こんにちは が ISO-8859-1 として大文字に変換されしまい、 + 出力は HELLO Á\223Â\223Á\253Á\241Á\257 + となってしまう *) +``` + +なお、 Shift_JIS は「[だめ文字問題](http://ja.wikipedia.org/wiki/Shift_JIS#2.E3.83.90.E3.82.A4.E3.83.88.E7.9B.AE.E3.81.8C5C.E7.AD.89.E3.81.AB.E3.81.AA.E3.82.8A.E3.81.86.E3.82.8B.E3.81.93.E3.81.A8.E3.81.AB.E3.82.88.E3.82.8B.E5.95.8F.E9.A1.8C)」 +があるため文字列中であっても使用は不可能です。 + +コメントに使用できる文字種 +-------------------------------- + +OCaml はコメント `(* .. *)` の文字列も内部では ISO-8859-1 として解釈しています。 +文字列と同じく、プログラムの文字コードに EUC や UTF-8 を選択した場合、コメント中に +これらの文字列を書くことが可能です。 + +EUC もしくは UTF-8 環境: +```ocaml +(* コメントを日本語で書いてみたぞ *) +``` + +Shift_JIS はやはり「だめ文字問題」があるためコメントに日本語を書くとトラブルの元になります。 + +OCaml toplevel(REPL) での問題 +================================ + +OCaml toplevel (REPL) は、対話的にプログラムテキストを受け取り、結果を表示する以外は、 +基本的に OCaml コンパイラと内部は同じです。ですから toplevel 中での文字コードの使い方も +上記と変わりません。ただし、いくつか注意点があります。 + +正しい端末設定を使用する +-------------------------------- + +OCaml を使う以前の問題ですが、使用したいと思っている文字コードと実際の端末の文字コードは +一致していなければいけません。自分は UTF-8 を使用しているつもりでも、端末は Shift_JIS +であったという場合、意味不明な挙動に苦しめられることになるでしょう。 + +ISO-8859-1 で特殊文字とみなされる文字はエスケープして表示される +----------------------------------------------------- + +「OCaml で文字化けした」と言われる問題は全てこれだと思って間違いありません。 + +UTF-8 環境の toplevel: +```ocaml +# "こんにちは";; +- : string = "\343\129\223ん\343\129\253\343\129\241\343\129\257" +# +``` + +こんな感じに「文字化け」します。(正確にどのような出力になるかは端末に依存します。) + +これは OCaml が文字列をあくまで ISO-8859-1 として処理しているからです。 +例えば 0x83 というバイトは ISO-8859-1 で表示する文字を持ちません、 +そのため OCaml はこのバイトをエスケープして `"\203"` という文字列として表示します。 +(203 は 0x83 の 8進数です。)この ISO-8859-1 を仮定したエスケープのため、 +ISO-8859-1 以外の文字コードを使用している場合、 0x80 以上のバイトを含む文字は +OCaml toplevel では「文字化け」して表示されてしまいます。 + +これは入力側で問題が発生しているわけではなく、純粋に toplevel の文字列の出力方法 +によるものです。Toplevel の文字列出力を使わず直接標準出力に同じ文字列を書き出すと +EUC や UTF-8 の文字列は内部では正確に保持されており、壊れているわけではないことが +わかります。 + +EUC もしくは UTF-8 環境: +```ocaml +# print_endline "こんにちは";; +こんにちは +- : unit = () +# +``` + +Toplevel (REPL) での文字化けを防ぐ +----------------------------------------------------- + +内部的には壊れているわけではない EUC や UTF-8 の文字列ですが、出力が狂ってしまうのは不便です。 +これは OCaml の `string` プリンタを変更することで回避することができます: + +EUC もしくは UTF-8 環境: +```ocaml +# let print_non_escaped_string ppf = Format.fprintf ppf "\"%s\"";; +val print_non_escaped_string : Format.formatter -> string -> unit = +# #install_printer print_non_escaped_string;; +# "こんにちは";; +- : string = "こんにちは" +``` + +ここでは +文字列をエスケープせずに標準出力に出力する関数、 `print_non_escaped_string` を定義し、 +それを `#install_printer` ディレクティヴによって `string` 型のプリンタに指定しています。 +これにより文字列を ISO-8859-1 とみなしたエスケープが行なわれなくなります。 + +Toplevel で日本語を含んだ文字列などを多用する場合は、毎回この内容を打ち込むのをさけるために、 +次の内容を OCaml toplevel が起動時に実行するファイルである .ocamlinit に +書き込んでおくとよいでしょう: + +```ocaml +let print_non_escaped_string ppf = Format.fprintf ppf "\"%s\"";; +#install_printer print_non_escaped_string;; +``` + +(OCaml toplevel は .ocamlinit ファイルをまずカレントディレクトリで探し、 +なければ $HOME/.ocamlinit を探します。`ocaml -init ` で指定することも可能です。) + +なお、このプリンタ、 `print_non_escaped_string` は簡易的なものであって、 +EUC や UTF-8 で本当にエスケープするべき文字のエスケープは全く行いません。きちんとした +エスケープを行いたい場合はそれなりの関数を書く必要があります。 +私は興味が無いので知りません。 + +OCaml で ISO-8859-1 以外の文字列データを処理する +============================================== + +前節では、 OCaml のプログラミングソースには ISO-8859-1 が仮定されていること、 +しかし、 EUC や UTF-8 の文字コードを選択することは、文字列を扱う関数の挙動は別として +問題ない、ということを見ました。 + +ここからは、実際にこれらの文字コードを選択してどのようにマルチバイト文字を含む文字列を +処理するかについて議論します。 + +方法1: 特にライブラリなどを使わず済ませる +------------------------------------- + +複雑な文字列処理を行わない場合、`string`型のデータをそのまま気にせず +`String` モジュールの関数で処理しても問題無い場合が多々あります。 +ISO-8859-1、EUC、UTF-8 の表現はASCIIの範囲内に限っては同じですから +マルチバイト文字部分を直接いじらない処理の場合は上手くいく事が多いのです。 +例えば、次のような `,` で区切ってあるデータを切り出して列挙要素を取り出す +プログラムはこの方法で十分な場合があります: + +```ocaml +let split_by_comma s = + let rec loop pos = + try + let pos' = String.index_from s pos ',' in + String.sub s pos (pos' - pos) + :: loop (pos'+1) + with + | _ -> [ String.sub s pos (String.length s - pos)] + in + loop 0 + +let () = + let tokens = split_by_comma " 名前 , クラス, 体重,身長 \n" in + List.iter (fun t -> + (* もちろん String.trim は「全角スペース」には対処できません *) + Printf.printf "\"%s\"\n" (String.trim t)) tokens +``` + +`String` モジュールの関数群は文字コードとしてあくまでも ISO-8859-1 を +前提としています。ですから、大文字小文字の判定や変換を伴なうものは誤動作しますから使えません。 +また、`length`、 `index` や `sub` のような関数は EUC や UTF-8 での文字数では +動作せず、ISO-8859-1 として見た時の文字数、つまりバイト数を使用することに注意を払う +必要があります。 + +方法2: 自分でマルチバイト部分の処理を書く +------------------------------------- + +EUC や UTF-8 であっても、文字の意味的処理(大文字、小文字であるかなど)を行わず、 +単に文字の切出しくらいの処理であれば、データ型を `string` のままにして、 +自力で文字カウントのスキャンを書くことは可能ですし、選択肢として考えるべきでしょう。 + +これが大掛りになってくると、選択した文字コードとして正しいバイト列持つ `string` を +内部表現とする抽象データ型を作るなど、ライブラリ化が始まります。 + +```ocaml +module UTF8 : sig + + type t (** UTF-8 文字列 *) + type c = int (** Unicode 文字コードポイント *) + + exception Invalid + + val of_bytes : string -> t + (** バイト列を UTF-8 とみなして検査を行う。不正なバイト列を含む場合、 + Invalid 例外を発生する *) + + val to_bytes : t -> string + (** [t] のバイト列を返す *) + + val get_char : t -> int -> c + (** [get_char t n] は [n] 文字目のコードポイントを返す。 O(n) *) + + val length : t -> int + (** [length t] は [t] の文字数を返す *) + + val bytes : t -> int + (** [bytes t] は [t] のバイト数。 O(n) *) + +end = struct + + type t = string (* 内部は string *) + + let is_valid s = ... (* 自分で書いてね *) + + let of_bytes s = if is_valid s then s else raise Invalid + + let to_bytes t = t + + let get_char t n = ... (* 自分で書いてね *) + + let length t = ... (* 自分で書いてね *) + + let bytes = String.length +end +``` + +このような形になってしまって、特に可変バイト長の文字コードでの文字の取扱いを +効率的に行いたいとなると…自作でライブラリを構築するよりはサードパーティの +ライブラリを使う方が簡単になってきます。 + +方法3: Unicode ライブラリを使用する +--------------------------------------- + diff --git a/bb/one-data-type-one-module.rst b/bb/one-data-type-one-module.rst new file mode 100644 index 0000000..eab401f --- /dev/null +++ b/bb/one-data-type-one-module.rst @@ -0,0 +1,180 @@ +======================================== +[OCaml] 1モジュール1データ型主義 +======================================== + +OCaml の「1モジュール1データ型スタイル」。このプログラミングスタイルは21世紀に入ってからモジュールを多用する OCaml コミュニティで流行りだしたもので私も採用しています。源流は SML 方面にあると聞きましたが…私自信は確認していません。要出典です。 + +「1モジュール1データ型スタイル」の意味するところは簡単です: + +* データ型一つ一つにモジュールを一つ一つ作る。 +* モジュール名は型の意味する名前をつける。人なら Person。 +* モジュール内で定義するデータ型は常に t という抽象的な名前にする。 +* t を主対象とする操作はモジュール内で定義する。 + +ただし、全ての OCaml プログラムはこのように書かれるべし、というものでもありません。 +例えば、 OCaml のコンパイラのソースコードはこのスタイルでは全く書かれていません。 + +「1モジュール1データ型スタイル」の利点は: + +* モジュール名に型名が既に入っているので関数名に型名を書かなくて済む +* 様々なデータ型で似たような機能の関数、コンストラクタ名、レコードフィールド名に対し名前付けを統一化できる: Process.to_string, Person.to_string, Process.kill, Person.kill など +* module system を使った名前空間操作が可能になる module P = Person, open Person など +* モジュールが自然と Functor に適用しやすい形になる + + +例: 社員と製品に見る「1モジュール1データ型スタイル」 +======================================================== + +暇なんで社員でも管理してみましょう:: + + type employee = { + employee_name : string; + age : int; + salary : int; + employee_id : int + } + + let employee_to_string t = Printf.sprintf "name=%s age=%d salary=%d id=%d" t.employee_name t.age t.salary t.employee_id + + let john = { employee_name = "John"; age = 42; salary = 5000; employee_id = 5963 } + +普通にはこんな感じになります。name, id ではなく employee_name, employee_id になっているのは他の id を持つ製品レコードと名前が被るからですね:: + + type product = { + product_name : string; + price : int; + product_id : int + } + + let product_to_string t = Printf.sprintf "name=%s price=%d id=%d" t.product_name t.price t.product_id + + let cheese = { product_name = "cheese"; price = 10; product_id = 4989 } + +さて、ここで問題は + +* 一々 employee employee, product product 書くの面倒くさい +* 統一感がない。salary も employee_salary、 price も product_price にすべき + +確かに、これでは r.employee_name, r.product_id, employee_to_string, product_to_string と書くことになります。OCaml のレコードは単相ですし、値は overloading が無いので名前を被せることができないのですね。 + +このコード、「1モジュール1データ型スタイル」で書きかえると次のようになります。:: + + module Employee = struct + + type t = { + name : string; + age : int; + salary : int; + id : int + } + + let to_string t = Printf.sprintf "name=%s age=%d salary=%d id=%d" t.name t.age t.salary t.id + let john = { name = "John"; age = 42; salary = 5000; id = 5963 } + + + end + + module Product = struct + + type t = { + name : string; + price : int; + id : int + } + + let to_string t = Printf.sprintf "name=%s price=%d id=%d" t.name t.price t.id + + let cheese = { name = "cheese"; price = 10; id = 4989 } + + end + +綺麗になりました。employee_name や product_id が name や id に。関数は employee_to_string と price_to_string が共に to_string に。これらのモジュールの外では r.Employee.name とか r.Product.id、Employee.to_string や Product.to_string と書いてそれぞれ呼び出すことになります。 + +ん?それでは r.employee_name, r.product_id, employee_to_string, product_to_string と何も変わらんではないか。いやいや。このスタイルの利点は名前空間をモジュールでコントロールしてプログラムを短く書けることにあります。 + +* open で名前空間を開き、モジュール名記述を省略する。ローカルコンテクストでのみ開くこともできる +* module alias (別名) でモジュールの短縮名を定義し、open しないまでもキーストロークを減らすことができる + +まず open: open Employee すれば Employee. は必要なくなりますし、 open Product とすれば Product. と書くこともなくなります。素晴らしい:: + + open Employee (* 以降、 r.name, r.id, to_string は Employee.t の操作の事になる *) + +いや待て、そもそも employee と product は同じモジュールで定義されている、つまり混ぜて使う事を想定されている。open Employee か open Product は同時には使えないではないか。うむ、たしかに。open を二つ並べると、後の open の名前の方が優先されます。前の open の効力は無くなる。しかしそういう場合は local open を使えばよい。名前空間の操作がローカルに調整できます。:: + + let fire e = + let open Employee in (* fire 関数中のみ、Employee を書かなくてよくなる *) + ... + ;; + +いやそれでももしある関数が Employee.t も Product.t も同時に使う場合は?確かにこの場合は難しい。両方 open すると混乱しますからお勧めしません。どちらかを開けてもうひとつはモジュール名を書くか…または、module alias を使って少しでもタイプ数を減らすこともできます。:: + + module E = Employee + module P = Product + + let add_sales_record employee prods = + ... employee.E.id ... + ... List.fold_left (fun st p -> p.P.price + st) + ... + +Module alias もローカルに宣言できます:: + + let add_sales_record employee prods = + let module E = Employee in + let module P = Product in + ... employee.E.id ... + ... List.fold_left (fun st p -> p.P.price + st) + ... + +他の方法はありますか? +======================================================== + +OCaml には overloading も無いし、レコードは単相だから、どうしても名前が被る場合は明示的に区別してあげなきゃいけません。面倒ですか?まあオブジェクト使う方法もありますけど:: + + class employee name age salary id = object + method name = name + method age = age + method salary = salary + method id = id + + method to_string = Printf.sprintf "name=%s age=%d salary=%d id=%d" name age salary id + end + + let john = new employee "John" 42 5000 5963 + + class product name price id = object + method name = name + method price = price + method id = id + + method to_string t = Printf.sprintf "name=%s price=%d id=%d" name price id + end + + let cheese = new product "cheese" 10 4989 + +こう書けば x#id は x が employee でも product でも使えます。それどころか id というメソッドがあるオブジェクトに対し全て使えます。Structural subtyping 素晴らしいですね。では、このオブジェクトによる名前の被せ方、お勧めかというとあまりお勧めしません。まず既にタイプ数多すぎますよね…まあ、CamlP4 を使えば field の部分は減らせそうですが…型も複雑になります。上の様な簡単な例ならまだ良いのですが、複雑なことを行うとどこかで破綻する。これらの名前の問題を解決したいためだけに class を導入するのはどうかと思います。(どう破綻するのか…は、書くと長くなるので勘弁してください) この例では単相レコードを多相レコードであるオブジェクトに移す例でした。バリアントの場合は多相バリアントにすることで同じようにコンストラクタ名を被せることが可能です。この場合はクラスと異なり破綻しにくいですが、やはり structural subtyping で型が読みにくくなります。多相レコードとしてのオブジェクトや多相バリアントを導入するか、どうか。絶対にするなとは言いませんが、よくよく導入して得られる overloading の利点に対し、不利な点が上回らないか、検討することが肝要です。 + +Haskell なら? Type-class ですか?確かに type-class 強力ですし、 to_string に関しては Show a を作るべきですけど、 Nameable a とか WithID a とかいうクラスこういう時わざわざ作りますか?普通は作らないですね。レコードフィールドやコンストラクタ名を被せる場合、あまり流行ってはいないですが、上で説明した方法と同様の効果を狙って import (qualified).. as .. 前提でモジュールを設計するのではないでしょうか。 + + +t という名前で functor に突っ込みやすくなる +======================================================== + +「1モジュール1データ型スタイル」のもう一つのポイントは、型名を t にする、です。 +型の意味するところは既にモジュール名に語らせていますから、型名は無味乾燥などんな名前で良いのです。 +ではなぜどんな名前でもよくなく、t か。これは ML functor を使う上での慣習との相性です。 + +Set.Make や Hashtbl.Make に代表される ML functor は モジュールを受け取りモジュールを返す関数(のようなもの)。ML module system における強力なプログラム再利用装置です。 + +* 普段は { Employee.name = "John"; age = 42; salary = 0 } とか Person.to_string と書くが、もし他のモジュールとのバッティングがなければ open Person すれば Person と一々書く必要がなくなる。また、例えば kill_person にすべきか person_kill にすべきかという永遠の問ともおさらばすることができる。とにかくデータ名をモジュールに担当させ、その内部からはデータ名をことごとく省けばよろしい。 +* 共通の同じ型名や関数名が種々のモジュールに使われることになり functor に放り込みやすくなる。例えば:: + + module type Printable = sig + type t + val to_string : t -> string + end + + module F(A : Printable) = struct ... end + +こんな感じに定義しておけば上記 Person や他の t と to_string : t -> string を持つモジュールは Printable インターフェースを持つことになり、functor F に放り込むことができる + +このスタイルにも不利、というか難しい点はないわけでななく、データモジュール内にデータモジュールを入れ子で作ってしまった場合 t の名前がかぶるので別名を用意しなければならない、とか、再帰データに関するデータモジュールは一度再帰型を普通に定義してからデータモジュールを複数つくるか、それとも再帰モジュールで一気に作るか、とかあるのだが、試してみれば自ずとわかるのでまあやってみ。 diff --git a/bb/opam_release.md b/bb/opam_release.md new file mode 100644 index 0000000..4a130a0 --- /dev/null +++ b/bb/opam_release.md @@ -0,0 +1,110 @@ +OPAM でリリース +===================== + +理論はわかる: + +* まずリリースすべきコードを書く +* コードをネットで入手できる所に置く +* opam ファイルを書く +* opam ファイルを opam repo に載せてもらうべく pull request を出す +* Approved! +* 後で何か細かい問題が出て来るので対処 + +実践が難しい。まだどうやるのが良いのか判っていない。ので考える。 + +まずリリースすべきコードを書く +========================== + +これは簡単。コードを書け。 + +コードをネットで入手できる所に置く +============================= + +手元にある opam-repository を調べた。全 2279 URLs(パッケージ毎ではなく、バージョン毎に一つ。) + +* 956 github +* 309 forge.ocamlcore.org +* 140 bitbucket +* 630 janestreet.com +* それ以外は janestreet 以外の個人やプロジェクトのホームページ + +このほとんどが tarball で配布されている。 + +git repo を指定での配布は非推奨、なぜなら、`hogehoge.git` などとする git repo での指定は +HEAD を指すことになるはずなので、ナマモノという理解。 + +自分のホームページなどで tar ball を公開している場合は tar ball を作ればよい。おわり。 + +github なり forge なり bitbucket なりで公開する場合、tag を打てばその +revision のファイルが tar ball で手に入るようになっているので、tag を使うことになる。 +(forge も tag 打てますよな?) +ただし **この tag は打ち直しがある事を覚悟すべき。** これ重要。 + +なぜ tag 打ち直しが必要かというと、 tag 打ってこのリリースはこの revision ですよ? +と一旦固定しても、opam repo に pull request を出してからの自動チェックで通らないことがあるから。 +その際にはソースを修正して新しい revision に同じ tag を打ち直すことになる。 +Tag をちょんちょんと打ち直すのが良い事かどうかわからないが、リリース時にはどうしても +opam repo の pull req での問題露呈が多いし、pull req が通るまでは誰もその tag +の付いた revision のコードを opam install しないであろうと思われるので構わない事にしている。 + +opam ファイルを書く +======================= + +ソフトウェアを OPAM を通して配布するには次の三つのファイルがまず必要: + +* `descr`: ソフトの説明 +* `opam`: ソフトのインストール方法および依存情報 +* `url`: ソフトの入手先とチェックサム + +各ファイルの書き方は本家のドキュメントを読めばよろしいのでここでは説明しない。 + +URL +--------------- + +`url` ファイルには tar ball の入手先である URL を書く事になっている。 +これはもし github や bitbucket 等を使っていて、レポジトリ名と tag の打ち方が +ソフトの名前とバージョンに一致しているのならば、自動生成できるはず。例えば +hogehoge のバージョン 1.2.3 なら私の場合は + +``` +https://bitbucket.org/camlspotter/hogehoge/get/1.2.3.tar.gz +``` + +になる。ので**自動化的に作成できる**はずだ。なので、しよう。 + +ただ bitbucket も github も両方使い出すと何か面倒そうだね… + +Checksum +--------------------- + +`url` ファイルには更に checksum を書く事になっている。これは tar ball の md5sum。 +これは github や bitbucket などで公開している場合、当該 tar ball を入手して +md5sum を取らなければならない。これは tar ball の URL から**自動化的に作成できる**はずだ。 +なので、しよう。 + +私は checksum 抜きの `url` ファイルから tar ball の URL を得て、その checksum +を wget+md5sum で計算して checksum 欄を追加するとういことをしている。 + +どこに保存するか +-------------------- + +最終的に opam ファイルは opam-repository を clone してそこに置いた上で +pull req を出して本家に採用してもらうのだけれども、だからと言って opam ファイルを +opam-repository のローカルコピーで管理するというのはどうか、と思ってはいる。 + +各 opam ファイルはソフトウェアに固有のものなのだから、当然そのソフトウェアのレポジトリ +にあるべきでは…と思って置いているのだが、これはこれでむずかしい。 + +* Tag を打つリリースのリビジョンに、それと一致する opam ファイルを置くことがむずかしい。 + ソース自体は変っていないのに opam ファイルを変更して push した後 tag を打ち直すというのは + どうも変だ。 +* opam ファイルメンテナンスを行っているとしばしば複数リリースに対して opam ファイルを + 一括して変えたいという欲求がおこる。各リリースごとにそのリリースの opam のみがあるというのは + 不便だという経験がある。そこで、 opam-release というブランチを作って今迄のリリースの + opam ファイルを集めた物を作って運用してみたが、これはこれで opam ファイル変更の際に + このブランチに移る必要があり、便利とはいえなかった。 +* opam-repository に置かれた `opam` ファイルは他人が変更する可能性がある。 + opam の記法が微妙に変ったりすると opam-repository 全体の `opam` ファイルが + 記法に併せて変更されることがある。この外側からの変更をソフトウェア本体のレポに反映させる事が + むずかしい。 + (`descr` と `url` が第三者に変更されることは無いと思われる。) diff --git a/bb/pack.md b/bb/pack.md new file mode 100644 index 0000000..8778eee --- /dev/null +++ b/bb/pack.md @@ -0,0 +1,56 @@ +packed module の中とオリジナルは違う +====================================== + +```ocaml +(* x.ml *) +type t = Foo +``` + +`P` というモジュールにパックする事前提でコンパイルします: + +```shell +$ ocamlc -for-pack P x.ml +``` + +`x.cmo` を `p.cmo` にパック。 `P.X` というモジュールを含む `p.cmo` を作ります: + +```shell +$ ocamlc -pack -o p.cmo x.cmo +``` + +さて、 `X.t` と `P.X.t` は同じようで違うというのが本稿の話: + +```ocaml +(* y.ml *) +let ts = [ X.Foo; P.X.Foo ] (* X.t と P.X.t は違うという型エラー *) +``` + +あいやー。 + +Pack というと + +```ocaml +module P : sig + module X : sig + type t = X.t = Foo + end +end = struct + module X = X +end +``` + +という物を期待していたのですが、どうも + +```ocaml +module P : sig + module X : sig + type t = Foo (* 元の X.t との関係は見えない *) + end +end = struct + module X = X +end +``` + +という扱いみたい。 + +「一旦 pack したら pack したモジュールを介して使え、直接さわるな」って事でいいんですかね。 diff --git a/bb/pattern_match.md b/bb/pattern_match.md new file mode 100644 index 0000000..71510fe --- /dev/null +++ b/bb/pattern_match.md @@ -0,0 +1,43 @@ +パターンマッチ +======================================= + +`let _ =` は使わない +====================================== + +関数を部分適用して、そのまま何もしないのは、大抵の場合引数が足りず間違っているコード: + + let _ = List.iter f (* あれー、何も起こらないぞ? *) + +このようなコードを `let _ =` で受けてしまうとコンパイラは何も言わずにコードを通してしまう(まあそらそうだ)。 +トップレベルでは `let _ =` ではなく `let () =` と書くべき: + + let () = List.iter f (* 型エラーになる *) + +`let` を伴わないトップレベルの関数呼び出しも同じ理由で推奨しない: + + List.iter f;; (* あれー、何も起こらないぞ? *) + +なお、 sequence で部分適用の式を書いてしまった場合は警告が出る: + + List.iter f; prerr_endline "hello world" (* Warning 5: this function application is partial *) + +当然だが Warning 5 はエラー扱いにすべき。 + +本当に本当に本当に式の結果を無視したい時は `ignore` を使う: + + let () = ignore (List.iter f) (* ほんとに意味はないけど俺はわかってやっているんだ!! *) + +`fun x -> match x with` は `function` にしろ +======================================================== + +`let f x = match x with` でも同様。まあ同じといえば同じだけれども文化的に `function` 推奨。 + +もし可能ならば、パターンマッチは短いケースから書け +==================================================== + +その方が読みやすい。 ``| None -> ...`` と ``| Some x -> ... x ...`` ならば +大抵 ``None`` の ... の方が短いので ``None`` から書く。 +大抵である。あなたが今まさに書こうとしている関数はもちろん例外だ。 + +もちろん書けない場合もある。安易な入れ替えは死を招く。 + diff --git a/bb/ppx.md b/bb/ppx.md new file mode 100644 index 0000000..d3a5a70 --- /dev/null +++ b/bb/ppx.md @@ -0,0 +1,188 @@ +ocamlc -ppx で遊ぶ +======================== + +こないだは OCaml 4.02.0 の新機能を概観したので今回はその内の一つ、 +ppx を試してみるのだ。日本一の CamlP4 作家と自他共に認める私だが、 +どーも CamlP4 は黒轢死となる予定が強まったので、次の動きを見極める +必要があるのだ + +4.02.0 +======================== + +ppx で遊ぶには OCaml 4.02.0 が必要なのであるが、 OPAM がある我々には +インストールの手間とかはほとんどない: + + $ opam switch -a | grep 4.02 + -- -- 4.02.0+trunk latest 4.02 trunk snapshot + +ほうほう成程、`4.02.0+trunk` いうのがあるのな: + + $ opam switch 4.02.0+trunk + +で待っとれば入れてくれる。はー長生きはするものじゃて。 + + $ eval `opam config env` + +ほい用意できた。 + +どうパースされているか +======================== + +さて、今回は + + (* x.ml *) + let x = {id|\(^o^)/|id} + let () = prerr_endline x + +というコードを弄ってみよう。これどないなってるんですか? + + $ ocamlc -dparsetree x.ml + [ + structure_item (x.ml[1,0+0]..[1,0+23]) + Pstr_value Nonrec + [ + + pattern (x.ml[1,0+4]..[1,0+5]) + Ppat_var "x" (x.ml[1,0+4]..[1,0+5]) + expression (x.ml[1,0+8]..[1,0+23]) + Pexp_constant Const_string ("\\(^o^)/",Some "id") + ] + ] + ... + +はーなるほど、 `Const_string` に `string option` が新しくついたんやね。ほいでちゃんと `\` は `'\\'` になっておる。素晴しい。 `-dparsetree` のプリンタはあいかわらず正しく括弧つけるのサボっておりあかんちんである。 + +ppx てどう動くのか +======================== + +じゃまあこの `Const_string ("\\(^o^)/",Some "id")` に手を加えてみたいわけであるが、その前に、 `-ppx` がどう動くのか、判っていないので調べる。 + + $ ocamlc -ppx foobar x.ml + sh: foobar: command not found + File "x.ml", line 1: + Error: Error while running external preprocessor + Command line: foobar '' '/var/folders/5h/2_0729sx3kg863fqlm17mfn80000gn/T/camlppx2bcc71' + +ということで、 `-ppx command` と書くと、 `command ` てな感じにプログラムが呼び出されて `command` は `` から `` を生成すればよいようだ。 + + #!/bin/sh + # x.sh + cp $1 $2 + cp $1 /tmp/copy.ml + +というのを作って、 `ocamlc -ppx 'sh x.sh' x.ml` て起動すると、 `/tmp/copy.ml` に ppx で指定されるプログラムが受け取るコードを得ることができる。で、見てみた: + + $ cat /tmp/copy.ml + Caml1999M016バイナリー.....バイナリー + +あーなんかバイナリなので、 `x.ml` をパースした結果を単に `output_value` で書き出しているようである。 `Caml1999` というのはまあ知ってる奴には OCaml コンパイラのソースの `utils/config.ml` にあるマジックキーワードの一つなので、まあそこを参照すると: + + let exec_magic_number = "Caml1999X008" + ... + and ast_impl_magic_number = "Caml1999M016" + and ast_intf_magic_number = "Caml1999N015" + ... + +とあるので、ああ、単に `output_value` したんじゃなくて、その前に、`.ml` 由来か `.mli` 由来を示すヘッダを付けるわけだとわかる。これは `.ml` から来たから `ast_impl_magic_number` で、これを受け取ったプログラムはやはりこのヘッダを付けて別の AST を返せばいいんだろう。 + +続くバイナリ部分はさすがにコードを読まないとわからない。 +コンパイラの `driver/pparse.ml` に該当コードがあり、 + + Location.input_name := input_value ic; + let ast = input_value ic in + +とあるので、値が二つあって、一つめはソースファイルの名前、二つめは +どうやら `.ml` なら `Parsetree.structure`、 `.mli` なら `Parsetree.signature` +の AST が入っている。え? 4.02.0+trunk のソース? `opam switch 4.02.0+trunk` している間に `~/.opam/4.02.0+trunk/build/ocaml` から回収せよ。コンパイル終ったら `ocaml` ディレクトリ消えてしまう素敵バグがあるからコピーしとけ。 + +初めての、何もしないフィルタ +============================== + +ほいじゃ、簡単にフィルタを書いてみましょうか: + + (* filter.ml *) + let infile = Sys.argv.(1) + let outfile = Sys.argv.(2) + + let ic = open_in_bin infile + let oc = open_out_bin outfile + + open Parsetree + + let filter f = + let header = + let buf = "Caml1999M016" in + let len = String.length buf in + assert (input ic buf 0 len = len); + buf + in + Location.input_name := input_value ic; + let v = input_value ic in + close_in ic; + + let v = f header v in + + output_string oc header; + output_value oc Location.input_name; + output_value oc v; + close_out oc + + let () = + filter (fun _header v -> v) + +適当に書いた。ただ読んで、ただ書くだけ。 `filter` に与える関数を変更すれば何かできる: + + $ opam install ocamlfind + $ ocamlfind ocamlc -package compiler-libs.common -linkpkg -o filter filter.ml + + $ ocamlc -ppx ./filter x.ml + +で、`x.ml` はまずパースされ、その結果のバイナリ AST が `./filter` に渡され、その結果、今のところ、入力と同じだけど、それがまた OCaml コンパイラに差し戻されて、コンパイル終了となる。 + +何か文字をいじってみる +============================== + +Parsetree を弄るなら Ast_mapper。あれ? 4.01.0 では class 使っていたけれど、fixed point もらうデカいレコードに変っているね。まあいいや。 + + let infile = Sys.argv.(1) + let outfile = Sys.argv.(2) + + open Parsetree + open Asttypes + open Ast_mapper + + let my_mapper = { default_mapper with + expr = (fun mapper -> function + | ( { pexp_desc = Pexp_constant (Const_string (s, Some "id")) } as e) -> + { e with pexp_desc = Pexp_constant (Const_string (s ^ s, None)) } + | e -> default_mapper.expr mapper e) + } + + let () = apply ~source:infile ~target:outfile my_mapper + +`{id|xxx|id}` という文字列を見付けたら `"xxxxxx"` にするという簡単なものである。ついでに Ast_mapper には上で書いたよなフィルタの実装がもうあった汗。まったくカンタンだ。ああ、まあ super である `default_mapper.expr` 呼ばずに self である `mapper.expr` をデフォルとケースで使うと無限ループするから注意な。 + +気をとりなおしてフィルタのコンパイル: + + $ ocamlfind ocamlc -package compiler-libs.common -linkpkg -o filter filter.ml + +このフィルタを使って `x.ml` をコンパイルしてみる + + $ ocamlc -ppx ./filter x.ml + $ ./a.out + \(^o^)/\(^o^)/ + +二倍。ほいできた。 + +4.02.0+trunk の `ocaml` は `-ppx` を無視するので `ocamlc` を使うこと。これはリリースでは直っている。 + +まとめ +=================================== + +* -ppx で AST 書き換え。これが標準になるので OCaml でコンパイラでなんたらという若者はおさえておくこと +* Ast_mapper に肝は用意されている +* `ocaml` では動かないので `ocamlc` で予習。 + +以上 + + \ No newline at end of file diff --git a/bb/random_memo.rst b/bb/random_memo.rst new file mode 100644 index 0000000..d58bee3 --- /dev/null +++ b/bb/random_memo.rst @@ -0,0 +1,104 @@ +======================================= +Random memo of OCaml programming +======================================= + +完全に順不同で思いついた事を書き連ねます + +現在内容を t/*.md に移行中です。 + +.. contents:: + :local: + + + + + +カスタム printf の作り方 +=================================== + +Printf 系関数におけるフォーマット文字列の型付けは特殊で、 +そのため普通にフォーマット文字列を受け取り内部でそれを使って +``printf`` を呼び出すような関数は一見不可能のように見える:: + + (* 引数列が何個か fmt で決まるので書けないよー *) + let failwithf fmt 引数列 = failwith (Printf.sprintf fmt 引数列) + +C のように ... が使えて、かつ varargs とか面倒なことなしに、次のように書ければいいのに、それも無理:: + + let failwithf fmt ... = failwith (Printf.sprintf fmt ...) + +しかし! OCaml は関数型言語だった! このような場合、継続スタイルの ``k*printf`` 関数をつかうとよい。 +``k`` は Keizoku の K。嘘です。 Kontinuation の K:: + + let failwithf fmt = Printf.ksprintf failwith fmt + +``fmt`` の引数による η-expansion は多相性のため、必須。 + +単に ``string -> t`` な関数に Printf 的なインターフェースをその場その場で持たせるのは +もっと簡単:: + + ksprintf f "hello %d" 42 + +これだけ。よく使うので ``sprintf`` と ``ksprintf`` は、 +私はいつもモジュール名 ``Printf`` 無しで使えるようにしている。 +k は継続の k なんだけど、そんな事考えずに ``string -> t`` の関数を +よろしく ``format -> ... -> t`` に拡張してくれる高階関数と覚えておくのが良い。 + + + + + + +η expansion を簡単に書く +=============================================== + +η expansion がわからない人はそれが何か、それが何の役に立つか勉強してください。 +Value polymorphsim とか eta expansion で検索な。 + +次のようなコードがあったとして:: + + let f = very very long code so long and lots_of lines + +Value polymorphism restriction で十分に多相性が出ない場合、η expansion します:: + + let f x = very very long code so long and lots_of lines x + +でもこれ面倒臭い。仮引数と実引数の距離が離れているととても面倒臭い。 +これをより簡単にする方法:: + + let f x = x |> very very long code so long and lots_of lines + +``(|>)`` は:: + + external (|>) : 'a -> ('a -> 'b) -> 'b = "%revapply" + +で定義。 + +ただ、これにも限界があって:: + + let f = a >>= b + +のような ``|>`` と強さが同じもしくは弱い演算子が存在すると、:: + + let f x = x |> a >>= b + +は:: + + let f x = (x |> a) >>= b + +のようにパースされ、上手くいかない。そこで、:: + + let f x = (|>) x & a >>= b + +って手もある。 ``&`` は ``%apply`` である。(Haskell や F# のように記号を使いたい)を参照。 + +ちゃんとやりたければ CamlP4 を使って:: + + let f = eta ..... + +が ``let f = fun x -> x |> (.....)`` に展開されるようなマクロを書けば良い。 +どうやって書くか?そりゃあんたに任せる。 + + + + diff --git a/bb/recursion.md b/bb/recursion.md new file mode 100644 index 0000000..e4cc4f1 --- /dev/null +++ b/bb/recursion.md @@ -0,0 +1,40 @@ +再帰で気を付けるべき事 +=========================================================================== + +標準ライブラリの再帰関数を使ったら Stack overflow 出た +====================================================== + +標準ライブラリの再帰関数はほとんどが末尾再帰呼び出しになっていないので大きいデータを食わせると +stack が溢れる。比較的小さいデータの場合、非末尾呼び出しのほうがスピードが早いため、そうなっている。 + +Jane Street Core や OCaml batteries included などの末尾再帰版を使うか自分で末尾再帰版を定義する。 + +再帰やループで `(^)` など「コピーを伴う連結」は使わない +=========================================================================== + +`s ^ s'` は + +> `s` と `s'` の長さを合計した長さの文字列をアロケートして、 +> そこに `s` と `s'` の内容をコピーする + +という操作。再帰やループで文字列をどんどん連結する場合、文字列が長くなればなるほど +どんどん遅くなる。文字列に限らず状態を変更していく再帰やループではコピーコストに +注意しなければならない。 + +文字列の場合は `Buffer` を使う。こいつは文字列の後ろに文字列を連結していくまさにバッファ +の機能を提供するが、内部で文字列のコピーをできるだけ少なくなるようよろしくやってくれる。 + +鶏口となるも牛尾となるなかれ: `xs @ [x]` したら負け +=========================================================================== + +リストの後ろに長さ1のリストを連結するコードはまずおかしいと思って良い。 +俺のコードここで `xs @ [x]` してるけどおかしいとは思わないよ? +ならば理由を説明できるはずだ。説明できないのならそれはおかしいのだ。 + +`xs @ [x]` は `xs` の長さに比例した時間がかかる。これを再帰やループの中で +状態リストを長くしていく操作として使っているならお前はもう死んでいる。 +`x :: xs` と `xs @ [x]` の違いは初心者が思っているより深刻だ。 + +どうしてもリストの後ろに要素を一つ一つ足したいという場合は再帰やループ中では +その逆リストを保持し、 `x :: rev_xs` として更新していく。最後に `List.rev rev_xs` だ。 +こんなふうに変数名に `rev` と付けておくと親切かもしれない。 diff --git a/bb/side_effect.md b/bb/side_effect.md new file mode 100644 index 0000000..82e825b --- /dev/null +++ b/bb/side_effect.md @@ -0,0 +1,32 @@ +副作用 +==================================================================== + +副作用を起こす式を関数引数に直接書かない。 `let` で受ける +==================================================================== + +OCaml の引数評価順は未定義。そして実際には i386 では外から(右から)行われるため、 +直感とずれる場合があり、ハマる: + +```ocaml +(* i386 *) +# let f x y = x + y;; +val f : int -> int -> int = +# f (prerr_endline "hello"; 1) (prerr_endline "world"; 2);; +world <------------ あれっ、world が先… +hello +- : int = 3 +``` + +副作用を起こす式を二つ以上関数に食わせる場合は副作用順をはっきりさせるために let で結果を受けたほうが良い:: + +``` +let one = prerr_endline "hello"; 1 in +let two = prerr_endline "world"; 2 in +f one two +``` + +二つ以上の引数に副作用を起こす式を指摘する lint が必要だと思われる。(難しいが) + +愚直に `let` で結果を受けていればあまりこの問題には遭遇しないが、 +それでも私はこの手のバグに年に一度くらいハマる。 +ポイントフリー教徒はより頻繁にハマるであろう。 diff --git a/bb/tclass.md b/bb/tclass.md new file mode 100644 index 0000000..04fef18 --- /dev/null +++ b/bb/tclass.md @@ -0,0 +1,245 @@ +Type Class Implementation for Dummies +======================================== + +関数型言語で話題の型クラスの実装方法。Dummies 向け。マトモに理解したい人はは論文を読むこと。 + +読み方: 型クラス宣言周りのコードは基本 Haskell 風だが、残りは適当に OCaml に切り換える。理由は: + +* 変換後の世界は型クラスないのに型クラスある言語で書くと混乱する +* Haskell は let多相が導入されるところが判りにくいのでヘボい +* Haskell のレコードはヘボい +* Haskell はコンスタントリテラルまで多重定義されているので例が面倒臭すぎる +* 辞書アクセスは plus dict とかではなく明示的に dict.plus と書きたい + +混乱して読めない人はお帰り。 + +非常に簡単な型クラスしか扱わない: + +* single parameter type class +* type class 宣言時のインスタンスコードの定義はなし。次のようなやつ: + +```haskell +class Ord a where + (>) :: a -> a -> Bool + (==) :: a -> a -> Bool + (>=) x y = x > y || x == y -- こういうのは単純化のためパス +``` + +コンパイル +------------------------------- + +型クラスのあるものを直接マシンコードにしたりしない。 +型クラスのない、型のついたプログラムにコンパイルする。 +だからプログラム変換とも言う。 + +どの型のものをどの型にコンパイルするか +------------------------------------------ + +まずこれを抑えておく。これが抑えられていると何となくこのコードはこれに変換されるから、 +とアタリがつく。 + +`=>` は `->` に変換する +------------------------------ + +`C a => t` という型を持つものは `C a -> t` という関数に変換する。 + +`C a => D a` という依存のある型クラスは `C a -> D a` という関数に変換。 + +クラス `C a` はレコード型 `C a` へと変換 +--------------------------------------------------------- + +型クラス宣言 + +```haskell +class Num a where + plus :: a -> a -> a + minus :: a -> a -> a + fst :: a -> b -> a -- b は Num a に出て来ない +``` + +は以下のような辞書のデータ型宣言に変換する: + +```ocaml +(* ocaml *) +type 'a c = { + plus : 'a -> 'a -> 'a; + minus : 'a -> 'a -> 'a; + fst : 'b . 'a -> 'b -> 'a; (* 'b はここで全称化される *) +} +``` + +ここではレコード型で説明するが、クラスや第一級モジュールでも構わない。 +上の `fst` の場合のためにメンバが多相型を持てるレコードっぽいデータ構造であることが必要。 + +インスタンスは辞書レコードに変換 +--------------------------------------------------------- + +依存のないインスタンス宣言は辞書レコードに変換する。 + +```haskell +instance Num Int where + plus = plus_int + minus = minus_int + fst a b = a + +instance Num Float where + plus = plus_float + minus = minus_float + fst a b = plus_float a 1.2 +``` + +は + +```ocaml +(* ocaml *) +let dict_num_int = { + plus = plus_int; + minus = minus_int; + fst = (fun a b -> a); +} + +(* ocaml *) +let dict_num_float = { + plus = plus_float; + minus = minus_float; + fst = (fun a b -> plus_float a 1.2); +} +``` + +というコードになる。 `dict_num_int` は他とダブらない `Num Int` から作ったユニークな名前。 + +依存のあるクラスのインスタンス宣言は依存部の辞書を貰ってレコードを返す関数になる。 + +```haskell +instance Num a => C a where + f :: t[a] -- なんか内部に a が出て来る型 + f = ... +``` + +```ocaml +(* ocaml *) +let dict_num_a_arrow_c_a dict_num_a = { (* 頑張って Num a => C a からユニークな名前を作る *) + f = ... (* dict_num_a が使われる *) +} +``` + +`dict_num_a` の内部での使われ方は後述の Derived overloading と同じ。 + + +辞書のディスパッチ +-------------------------------------------------------- + +外側の `let` 多相で型が全称化されていない `C t =>` を持つ identifier +には辞書をディスパッチする: + +```ocaml +(* ocaml *) +(* plus :: Num a => a -> a -> a *) +plus 1.2 3.4 +``` + +は `plus` の型は `Num float => float -> float -> float` なので、 +`Num float` に対応する辞書をディスパッチする。 + +```ocaml +dict_num_float.plus 1.2 3.4 +``` + +上の `dict_num_float` の定義を見てこのコードがちゃんと `plus_float 1.2 3.4` と同じ結果を返すことをかくにん、よかった<3 + +辞書作成はネームスペースにある `dict_*` を組合せて作る。 +Prolog を動かすような感じ。作ることができなければ型エラー。 +作る方法が複数あれば ambiguous なのでやはり型エラー。 + +Derived overloading +-------------------------------------------------------- + +外側の `let` 多相により `C a =>` 部が全称化されているような identifier +には具体的辞書をその場で与えることができない。その代り、外側の `let` から +ディスパッチされたものを使う。 + +外側の `let` 多相側では内部の全称化された `C a =>` を持つ identifier +の辞書を外部から貰うために、型は `C a =>` を持たなければいけない。 +複数あるばあいは `(C a, D b) => ...` とかになる。 + +この `C a =>` の導入に対応して、外部からディスパッチされた `C a` に関する +辞書を受け取るためにλ抽象を入れる: + +```haskell +-- plus :: Num a => a -> a -> a +let double x = plus x x -- plus の型 Num a => a -> a -> a の a は let で全称化されている +-- だから double :: Num a => a -> a という型になる +``` + +```ocaml +(* ocaml *) +let double dict_num_a (* 外から辞書をもらう *) x = dict_num_a.plus (* 外から貰った辞書を使う *) x x +``` + +最近の Haskell: OutsideIn(X) +---------------------------- + +この `C a =>` の `a` を全称化する `let`多相でとにかく `C a =>` をくっつけて +λ抽象を入れるという方式は、`let` がネストする場合無駄なディスパッチコードを作ったりするので +このごろの Haskell では `C a =>` の a が全称化できる `let` は明示することになっている。 +(他にも理由はあるが本稿では関係ない) + +```ocaml +let quad x = + let double x = plus x x in + double x x +``` + +これを真面目にやると + +```ocaml +let quad dict_num_a x = + let (* double : num 'a -> 'a -> 'a *) + double dict_num_a' x = plus dict_num_a' x x + in + double dict_num_a x x +``` + +というコードになるが + +```ocaml +let quad dict_num_a x = + let (* double : num '_a -> '_a -> '_a '_a はここでは全称化されていない意味 *) + double x = plus dict_num_a' x x + in + double x x +``` + +このように全称化をサボることで dispatch コードを消せる。これが困る場合は +全称化することを明示する: + +```ocaml +let quad x = + let double : Num 'a => 'a -> 'a (* ここで辞書ほしいんですよ *) + double x = plus x x + in + print_int (double 1 2); (* だってここで複数の型で使うので *) + print_float (double 1.2 2.3); + double x x +``` + +このコードはこうなる: + +```ocaml +let quad dict_num_a x = + let double dict_num_a' x = plus dict_num_a' x x + in + print_int (double dict_num_int 1 2); + print_float (double dict_num_float 1.2 2.3); + double dict_num_a x x +``` + +おわり +-------------------------------------------------------- + +こんな感じで基本的なコンパイル自体は難しくはない。 + +* 最適化は dispatch 部分の partial evaluation を行えばよい。というかしないと凄く遅くなるので、非明示な最適化を嫌う OCaml は長らくこういうのを採用しない。 +* Multi dependent なクラス `(C a, D b) => E a b` は `(C a, D b) -> E a b` に変換するだけ +* Multi parameter class になると型推論が undecidable になるのでその辺はそういう論文読む + diff --git a/bb/types.mli b/bb/types.mli new file mode 100644 index 0000000..7b574b0 --- /dev/null +++ b/bb/types.mli @@ -0,0 +1,271 @@ +(* OCaml 4.00.0 辺りの types.mli の読み解き。間違っている可能性がある。 + 現在の OCaml のものとは違っている可能性がある。 +*) + +open Asttypes + +(* Type expressions for the core language *) + +type type_expr = + { mutable desc: type_desc; + mutable level: int; + mutable id: int } + +(* + desc: + 型の内容。 + + level: + 型が生成された let polymorphism level。 + Unification によって小さい方にまとめられていく。 + + Quantify された型の level は generic_level。 + Generalization の際、型の level が現在の polymorphism current_level より + 大きければ、 generalize される。同じもしくは小さい level を持っている場合、 + generalize されないどころかサブノードの検査も行われない。 + つまり、 generalize する際には、型の level はサブノードの level より + 同じか、大きくなければ正確な generalization は行われないことになる。 + ただし、通常の HM の型アルゴリズムを舐めている際にはそんなことは起こらない。 + 新しい型を既存の型から作るとき、既存の型の level は常に current_level + と同じか小さいはずだ。大きければそれ以前の let polymorphism で + generalize されて generic_level になっているからである。 + + level は通常 0以上の整数だが、型ノードにマークをつけるため、 + 負値を使うことがある。 pivot_level, mark_type を参照。当然、 + 付けたマークは戻さないと generalization でオカシイことになる。 + + id: + Unification backtrack のために使われる。 + id が違っても同じ型の場合がある + 違う型の場合必ず id は異なるため、 hash として使われる。 + このフィールドの存在のため、 { ty with desc = ... } のような + ことをしてはいけない。必ず Btype.new*ty* を使って新しい id をもらう +*) + +and type_desc = + Tvar of string option + (* 型変数。名前をつけることができる。 名前は print 時に使用される。 + 名前が無い場合は print 時に 'a から順に付けられる + + 型変数の同異は pointer equality によってなされる。 + 同じ名前がついていてもアドレスが異なれば実際には違う型変数であるし、 + プリントアウトの際にも数字の postfix が入る。 + *) + | Tarrow of label * type_expr * type_expr * commutable + (* t -> t。 [is_optional label] の際には引数の型は + 必ず option 型になっている。 + + commutable について + + and commutable = + Cok + | Cunknown + | Clink of commutable ref + + let f g = g ~x:1 ~y:true in f (fun ~y:_ ~x:_ -> ());; + + は型エラーになるのだが、これは g の Tarrow が Cunknown だから。 + 現在の OCaml では外からくる型不明の関数にかんして引数順入れ替えのコンパイルを + おこなわない、つまり + + let f g = g ~x:1 ~y:true in f (fun ~x ~y -> (fun ~y:_ ~x:_ -> ()) ~y ~x);; + + というコードに変換しない。 多分これをやると abstraction が入るので + 一般的には副作用のタイミングがおかしくなってしまう + + let g = fun ~y:_ ~x:_ -> () in + let f = g ~x:1 ~y:true in f + + これは前もって順序がわかっており、 Cok なので型エラーにならない。 + 順序の入れ替えは g の定義ではなく g の呼び出し側で行われる: + + let g = fun ~y:_ ~x:_ -> () in + let f = g ~y:true ~x:1 in f + + こんな感じだと思われる(仔細未確認)。OCaml は引数の評価順は未定義なので + 問題ないはず + *) + | Ttuple of type_expr list + (* タプル *) + | Tconstr of Path.t * type_expr list * abbrev_memo ref + (* Data type。 + + abbrev_memo はこの型の alias のキャッシュ。 + + 例えば type 'a t = 'a * 'a で、 int t という型が + あった時、 int t の Tconstr が int * int と同じであることを覚えておく + ための情報がここに入る。 + + Tconstr から + 何か変換を行なって別の Tconstr を作る際には + 前の型とは関係なくなってしまうから abbrev_memo は別の + 新しい空のキャッシュを使わなければならない。 + + Head alias の expansion は、 + try_expand_once <--- なんだろう書きかけだが、覚えていない + + *) + | Tobject of type_expr * (Path.t * type_expr list) option ref + (* [Tobject (field_type, nm)] + + [!nm] が [Some ..] の場合はクラス名付きのオブジェクトの型。 + [Some (p, ty :: tyl)] の場合、 + [(tyl) p] という型である + + [ty] は何か? + Printer では [ty] は generalize されているかどうかの判定に使われている + See [Printtyp.tree_of_typobject] + + [nm] が新たに [Some] を introduce するケースは、とても見つけにくいが、 + [Btype.set_name] そしてそれを呼ぶ [Ctype.set_object_name] である。 + [set_object_name] の [rv] がこの [ty] になる。 + + let set_object_name id rv params ty = + + この [rv] は常に [Ctype.row_variable ty] であるので、row variable + であるらしい。これは OCaml の型にはプリントされないのと合致する。 + + [Some (p, [])] というのはありえない状態 :-( だと思われる + + [!nm] が [None] の場合は無名オブジェクト型。 [field_type] + には [Tfield] や [Tnil] (おそらく [Tlink]も?) 入っている。 + (ここに [Tarrow] とかは入らない *) + *) + + | Tfield of string * field_kind * type_expr * type_expr + (* [Tfield (メソッド名, 種, メソッドの型, 残りのメソッド情報)] + + 最後の [type_expr] はリストの cons の様な働きをする。 + Ctype.flatten_fields で普通のリストに展開してくれる。 + 最後は Tvar や Tunivar だと open なオブジェクト型、 + Tnil だと closed。これは Tobject での [!nm = Some (p, ty :: tyl)] + の [ty] と row variable であるようである。 + + が、なんと Tconstr が来ることがあるようだが + これは… (Printtyp.tree_of_typfields) + Tconstr が来ると、プリンタでは Otyp_object の最後が Some false になる。 + そしてこれは < _..> とプリントされるようなので、 non generalized raw var を指すようだ: + + let f x o = o#m + x + let g = f 1 + + とすると val g : < x : int; _..> -> int + + と表示されるので、これ以上知りたい場合はこの場合の最後の型を調べるべきである。 + *) + + | Tnil + (* メソッドがもう無い、閉じたオブジェクト型であることを示す *) + + | Tlink of type_expr + (* 高速 unification のためのリンク。 Tlink はある型(普通は変数) + が別の型と unify されたなれのはてである。 Unify された相手は + Tlink の中に入っている。 + + Btype.repr でこのリンク Tlink (と Tfield) をすっ飛ばすことができる + *) + | Tsubst of type_expr (* for copying *) + (* 型を変更する際、一時的に desc を [Tsubst 新型変数] に置き換えて + 作業する。新型変数の desc に新しい型の desc を代入する。 + 再帰的な作業中に Tsubst を見た場合は、これは既に別のところで作業の + 終わった shared node であることがわかるので同じ作業を行わず + Tsubst の中の結果だけをもらうことになる。 + + Tsubst に置き換えた際には後で元にもどさなければいけない。 + これは Btype.save_desc と Btype.cleanup_types を使う。 + *) + | Tvariant of row_desc + (* Polymorphic variant の型 *) + | Tunivar of string option + (* 'a . t の 'a。 Quantify されると内部の 'a も Tunivar になるようである *) + | Tpoly of type_expr * type_expr list + (* 'a . t *) + | Tpackage of Path.t * Longident.t list * type_expr list + (* モジュールのパッケージ型。 module_type ではなく Path.t + が入っているように nominal である + + Longident.t list * type_expr list は with type ... である + *) + + +and row_desc = + { row_fields: (label * row_field) list; + row_more: type_expr; + row_bound: unit; (* kept for compatibility *) + row_closed: bool; + row_fixed: bool; + row_name: (Path.t * type_expr list) option } + + (* row_fields: フィールド + row_more: フィールド追加 (他の型を元に row_type を作る) + row_bound: 意味なし + row_closed: 閉じているかどうか + closed = true だと + Rpresent のみの場合: [ ... ] + 他のがある [< .. ] + closed = false だと [> .. ] + row_fixed: 型検査終了後は意味なしのはず + row_name: 名前付き poly variant type + *) + +and row_field = + Rpresent of type_expr option + | Reither of bool * type_expr list * bool * row_field option ref + (* 1st true denotes a constant constructor *) + (* 2nd true denotes a tag in a pattern matching, and + is erased later *) + | Rabsent + + (* Rpresent 存在する option は引数があるか、ないか。 + `A とか `A of int など + + Reither (b, [ty1;..;tyn], ...) + + [ (function `A -> 1); + (function `A x -> x + 1); + (function `A x -> int_of_float x + 1)] + + という式を型推論させると、 + + ([< `A of & int & float ] -> int) list + + という型が出て来るが、これ。つまり、 `A は無引数かつ int かつ float を取る + という不思議な型である。ちなみに上記リストから何かを取り出しても関数として使えない。 + + [b] が true だと無引数が存在: `A of (*無*) & int & float と of の直後に & が表示される + [b] が false だと無引数は存在しない: `A of int & float と of の直後に & は表示されない + + Reither の第三、第四引数は型検査後は重要ではない一時的なもののようだ + + Rabsent 存在しない: 型検査後は出てこないはず (Printtyp の対応も適当) + *) + +and abbrev_memo = + Mnil + | Mcons of private_flag * Path.t * type_expr * type_expr * abbrev_memo + | Mlink of abbrev_memo ref + + (* 型 alias のキャッシュ。 + + Mnil は空。現在の alias 情報はこれ以上ないことを意味する + Mlink は別のキャッシュを継承する時に使う + Mcons は + *) + +and field_kind = + Fvar of field_kind option ref + | Fpresent + | Fabsent + + (* Row polymorphism + + Fpresent : 存在する + Fabsent : 存在してはいけない + Fvar None : 変数 + Fvar (Some ..) : Unification により代入されてしまった変数 + *) + +and commutable = + Cok + | Cunknown + | Clink of commutable ref diff --git a/bb/warn.ml b/bb/warn.ml new file mode 100644 index 0000000..eac08a4 --- /dev/null +++ b/bb/warn.ml @@ -0,0 +1,14 @@ +(*) this is a comment *) + +( *);; + +true & false;; + +type t = Foo | Bar;; + +let f = function + | Foo -> print_string "1" + | _ -> print_string "other than 1" + + + diff --git a/bb/warning.md b/bb/warning.md new file mode 100644 index 0000000..6214ee4 --- /dev/null +++ b/bb/warning.md @@ -0,0 +1,157 @@ +警告から見る正しい OCaml コーディング +==================================== + +OCaml 4.02.1 には 49種類の警告があります。その定義は OCaml コンパイラのソース +`utils/warnings.mli` で見ることができます: + +```ocaml +type t = + | Comment_start (* 1 *) + | Comment_not_end (* 2 *) + | Deprecated of string (* 3 *) + | Fragile_match of string (* 4 *) + | Partial_application (* 5 *) + | Labels_omitted (* 6 *) + | Method_override of string list (* 7 *) + | Partial_match of string (* 8 *) + | Non_closed_record_pattern of string (* 9 *) + | Statement_type (* 10 *) + | Unused_match (* 11 *) + | Unused_pat (* 12 *) + | Instance_variable_override of string list (* 13 *) + | Illegal_backslash (* 14 *) + | Implicit_public_methods of string list (* 15 *) + | Unerasable_optional_argument (* 16 *) + | Undeclared_virtual_method of string (* 17 *) + | Not_principal of string (* 18 *) + | Without_principality of string (* 19 *) + | Unused_argument (* 20 *) + | Nonreturning_statement (* 21 *) + | Preprocessor of string (* 22 *) + | Useless_record_with (* 23 *) + | Bad_module_name of string (* 24 *) + | All_clauses_guarded (* 25 *) + | Unused_var of string (* 26 *) + | Unused_var_strict of string (* 27 *) + | Wildcard_arg_to_constant_constr (* 28 *) + | Eol_in_string (* 29 *) + | Duplicate_definitions of string * string * string * string (*30 *) + | Multiple_definition of string * string * string (* 31 *) + | Unused_value_declaration of string (* 32 *) + | Unused_open of string (* 33 *) + | Unused_type_declaration of string (* 34 *) + | Unused_for_index of string (* 35 *) + | Unused_ancestor of string (* 36 *) + | Unused_constructor of string * bool * bool (* 37 *) + | Unused_extension of string * bool * bool (* 38 *) + | Unused_rec_flag (* 39 *) + | Name_out_of_scope of string * string list * bool (* 40 *) + | Ambiguous_name of string list * string list * bool (* 41 *) + | Disambiguated_name of string (* 42 *) + | Nonoptional_label of string (* 43 *) + | Open_shadow_identifier of string * string (* 44 *) + | Open_shadow_label_constructor of string * string (* 45 *) + | Bad_env_variable of string * string (* 46 *) + | Attribute_payload of string * string (* 47 *) + | Eliminated_optional_arguments of string list (* 48 *) + | No_cmi_file of string (* 49 *) +``` + +この文書はこれらの警告を解説することを通して、 +どういった OCaml プログラムがまずいのか、どう書くべきか、を探っていきます。 + +Warning 1, 2: 掛算記号とコメント +------------------------------------------------------- + +### Warning 1: this is the start of a comment. + +```ocaml +# (*) this is a comment *) +Characters 0-3: + (*) this is a comment *) + ^^^ +Warning 1: this is the start of a comment. +``` + +演算子を関数として使う場合には括弧でその演算子を囲います。例えば、足し算を行う関数は +二項演算子 `+` を括弧で囲んで `(+)` と書けます: + +``` +# 1 + 2;; +- : int = 3 +# (+) 1 2;; +- : int = 3 +``` + +ですが、`(*)` は掛算を意味しません。OCaml のコメントは `(*` と `*)` で囲まれたテキストです。 +ですから `(*)` はコメントの始まりを意味します。しかし、二項演算子 `*` を関数として使おうとして +`(*)` と書く人が後を絶ちません。 `*` を関数として使いたい場合は `(` と `*` の間にスペースを +入れる必要があります。 + +``` +# 2 * 3;; +- : int 6 +# ( *) 2 3 +Characters 2-4: + ( *) 2 3;; + ^^ +Warning 2: this is not the end of a comment. +- : int = 6 +``` + +### Warning 2: this is not the end of a comment. + +今度は括弧を閉じる方でも警告が出ました。 + +### どうすべきか + +`*` を関数として使いたい場合は `( * )` と書くべきですね。 + + +Warning 3: 推奨されない値 +------------------------------------------------------- + +``` +# true & false;; + ^ +Warning 3: deprecated: Pervasives.& +Use (&&) instead. +- : bool = false +``` + +これは後方互換性のために残してはあるが、使用を勧められない関数や値を使ったときに出る警告です。 +この `(&)` という関数は `pervasives.mli` ファイルに次のようにアトリビュート付きで +宣言されています: + +``` +external ( & ) : bool -> bool -> bool = "%sequand" + [@@ocaml.deprecated "Use (&&) instead."] +(** @deprecated {!Pervasives.( && )} should be used instead. *) +``` + +この `[@@ocaml.deprecated ...]` というアトリビュートがある値を使うとこの警告が +出るわけですね。この場合は `(&)` ではなく `(&&)` を使うべきとのメッセージが表示されます。 + +### どうすべきか + +着本的に deprecated な関数や値は使わないようにしましょう。通常は何を代りにつかうべきか +警告に表示されているはずです。 + + +Warning 4: パターンマッチが将来の型の拡張に脆弱かもしれない +-------------------------------------------------------- + +``` +type t = Foo | Bar;; + +let f = function + | Foo -> print_string "1" + | _ -> print_string "other than 1" + +(* Warning 4: this pattern-matching is fragile. + It will remain exhaustive when constructors are added to type t. *) +``` + +デフォルトではオンになっていない警告が出て来ました。この警告を見るには、例えば、 +`ocamlc -w A x.ml` など全ての警告表示をオンにして下さい。 + diff --git a/bb/why-p4-sucks.rst b/bb/why-p4-sucks.rst new file mode 100644 index 0000000..52d5dc5 --- /dev/null +++ b/bb/why-p4-sucks.rst @@ -0,0 +1,19 @@ +================================= +なぜ CamlP4 はダメなのか +================================= + +これは Twitter で 「P4 の代わりが欲しいよ」と呟いたら多分 OCamlPro の中の人から + +「じゃあどこがイヤなのか教えてよ!代わり作るのに役立つから!」 + +と言われたので、140文字じゃあ無理だなあと思って英語で書きだそうと思ったけどまあ +アドベントカレンダーの季節だしまあ日本語で書いてからあとで英語にしてもええかなあと思った。 + +Revised syntax がダメ +================================== + +CamlP4 には悪名高い Revised syntax というものがある。この名前からして、お、OCaml のオリジナルの +文法はダメで、この Revised を使うのが通なのかなという幻想を抱かせるがそういう事は全くない。 +Revised syntax は見るべきところはあるが結局 P4 の外では誰も使っていない。 + +