Source

ocaml-zippy-tutorial-in-japanese / random_memo.rst

Random memo of OCaml programming

完全に順不同で思いついた事を書き連ねます

OCaml を使うなら Unix で

要するに Linux とか Mac OS X とかを使って。 一言で言うと Windows では使わない。時間の無駄。理由は、

  • Cygwin, Mingw, MS native の三種類ありそれぞれ使える機能、出来たコードのライセンス、スピード特性が違う
  • Windows というとコンパイラバイナリパッケージに頼る人が続出し、そしてあまり出来が良くない。OCaml できる人はコンパイラは自分でビルドするのでバイナリパッケージ固有の問題はできる人には共有されていないというか他人事
  • どの種類を使ってもコンパイラ Cygwin をビルドツールとして使う。Cygwin は頑張っているが、まあ、 Unix の真似事に過ぎないのでバッドノウハウは沢山ある
  • 外部ライブラリのインストール、アンインストールが一々面倒
  • そういうわけでユーザーが少ない。上記三種類のせいで、少ないユーザーが更に分断されている。何かあっても誰も助けてくれない。

もちろんこの問題はほとんどが Windows は Unix 系プログラムを持ってきて作業するにはあまりにも辛すぎる、 という事に起因する。OCaml がダメだというわけではない。普通に Windows で OCaml のプログラム開発してビジネスしているところはある。あるが、それは顧客が金払ってくれるからで… 趣味の範囲で、中級以上の作業(初級とは ocaml トップレベルとか簡単な ocamlc 単発使用とかそんなレベル)はとても面倒。まあやめておいたほうが良い。

ocamlopt を使いましょうね

ocamlc でコンパイルして OCaml 遅いと言わない…約束だ。

ocaml トップレベルをそのまま使わない

OCaml の REPL(歴史的に OCaml のトップレベルと呼ぶ) コマンド ocaml は 複雑な履歴操作などを提供しない。そういう物が欲しければ、普通欲しいと思うのだが、 他のツールを組み合わせて使う: rlwrapledit 、 emacs の shell-mode なりで wrap するのだ。

こういうプログラムを組み合わせる発想は Unix世界ではごく当たり前なので 大したことではないと思っていたのだが、 GUI 主体で育った若者はどうも発想自体思いつかないようだ。

ocaml トップレベルは使わない

INRIA や Jane Street でトップレベルを使って、電卓とか型推論の確認以外、 何かしている人は見たことがない。 規模の大きいプログラムを書くとき、私はトップレベルは使わない。 トップレベルにいろいろロードしてテストするならテストプログラムを書く。 本当に自分の書いているコードがロードされたトップレベルが欲しければ、 既にリンクされたカスタムトップレベルを作る。

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 withfunction にしろ

let f x = match x with でも同様。まあ同じといえば同じだけれども文化的に function 推奨。

Variant constructor を curry 化したい

できない。 fun x -> Some x のように手で書くこと。

なぜ OCaml のコンストラクタ(variant constructor)は partial application できないのか https://groups.google.com/forum/?fromgroups=#!topic/fa.caml/IOlkCcVBg5Q

CamlP4 で各コンストラクタに対応する curried form な関数を定義することはできる。 https://github.com/janestreet/variantslib

なお、コンストラクタをそのまま curry 化された関数にすることは P4 では不可能。 コンストラクタの arity (引数の数)を調べる必要があり、型を知らない P4 にはこれは 不可能である(内部で型推論を呼び出すという変態的なことを行わない限り)。 コンパイラを改造するほうが早いと思われる。

標準ライブラリの再帰関数を使ったら Stack overflow 出た

標準ライブラリの再帰関数はほとんどが末尾再帰呼び出しになっていないので大きいデータを食わせると stack が溢れる。比較的小さいデータの場合、非末尾呼び出しのほうがスピードが早いため、そうなっている。

Jane Street Core や OCaml batteries included などの末尾再帰版を使うか自分で末尾再帰版を定義する。

副作用を起こす式を関数引数に直接書かない。 let で受ける

OCaml の引数評価順は未定義。そして実際には i386 では外から(右から)行われるため、 直感とずれる場合があり、ハマる:

(* i386 *)
# let f x y =  x + y;;
val f : int -> int -> int = <fun>
# 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 で結果を受けていればあまりこの問題には遭遇しないが、 それでも私はこの手のバグに年に一度くらいハマる。 ポイントフリー教徒はより頻繁にハマるであろう。

例外の効率: 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 例外の静的型検査の研究はあるが採用されていない。

とはいえ例外に頼りすぎるな

CR jfuruse: 前エントリとの整合性を考えること

例外に頼った再帰からの脱出を使わずとも同等の再帰関数を書くことは (初心者には難しいかもしれないが)全く可能である。例外に頼りすぎるのはよくない。:

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 を書き続けるならどこかで努力をして後者に切り替えるべきである。

カスタム 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

これだけ。よく使うので sprintfksprintf は、 私はいつもモジュール名 Printf 無しで使えるようにしている。 k は継続の k なんだけど、そんな事考えずに string -> t の関数を よろしく format -> ... -> t に拡張してくれる高階関数と覚えておくのが良い。

特殊なリテラルを作りたい

CamlP4 で quotation 使う。 <:hoge<自由に書ける>> 。 CamlP4 でのレクサ拡張は他の文法拡張と重ねることができないので、うまくいかない。 Quotation は <:x<>> と最低でも 6 文字必要なので悲しい。

もし可能ならば、パターンマッチは短いケースから書け

その方が読みやすい。 | None -> ...| Some x -> ... x ... ならば 大抵 None の方が短いので None から書く。 大抵である。あなたが今まさに書こうとしている関数はもちろん例外だ。

もちろん書けない場合もある。安易な入れ替えは死を招く。

OCAMLRUNPARAM='b' でバックトレースを表示する

OCAMLRUNPARAM 環境変数に b をセットすると uncaught exception でプログラムが終了する時に 例外のバックトレースが表示される。 assert false で死んだ場所は判ったが、どうやってそこにプログラム実行 が到達したかわからない場合に便利、というか常に b を入れとけ。わかったか。

Physical comparison (==) (!=) で泣く位なら始めから締めだす

自分が (=), (<>)(==), (!=) の違いが判らないとか、同僚が判らない場合は もうさっさと (==)(!=) は潰したほうがいい。Jane Street Core のように:

(* base.ml *)
let phys_equal = (==)
let (==) _ _ = `Consider_using_phys_equal
let (!=) _ _ = `Consider_using_phys_equal

open Base すると (==)(!=) の型は 'a -> 'b -> [> `Consider_using_phys_equal] になる。 そこで:

if "hello" == "hello" then "equal" else "different";;

などと書くと型エラーに Consider_using_phys_equal と出てくるので、あれ?なんだこれは?とわかる。 本当に physical comparison を使いたい時は phys_equal "hello" "hello" と書く。 これを structural comparison (=) と間違って使った場合はもちろん救えない。

open Base を全 \*.ml ファイルで強制しなければならないが、 これは grep などで調べれば機械的にチェックできる。

ログは flush を忘れるな

stderr といえども flush されない関数があるので注意。特に、

  • \*_string は改行が入っていても flush しない! (\*_newline, \*_endline は flush する)
  • Printf.\*printf%! で明示しない限り改行でも flush しない!

Printf デバッグする時はこれに注意してないと「あれー表示されないのに実行されてるー?おかちいな」ということになる

さらに C とリンクする場合。C 側で fflush() しても OCaml の stdout/stderr は flush されない。 これは OCaml 自身が stdout/stderr の独自バッファを持っているからである。独自バッファに溜まっている文字列は C は関知しないので当然 fflush() しても無駄である。

文字列は '\0' では止まらない

OCaml の NULL文字は '\0' ではなく '\000' と書くのだが、これは OCaml では文字列の 終端を意味しない:

# print_string "hello\000world";;
hello^@world- : unit = ()

hello だけでなく \000world も端末に出力されている。

OCaml での文字列の長さとは常にアロケートされた長さ。この長さは値の内部表現ヘッダに 書いてあるのでなので O(1) で取ってこれる。C の``strlen`` のように '\0' を探して 旅に出たりしない。

標準ライブラリの *read 関数などアロケートされた文字列バッファに書き込む関数は 戻り値の示す長さの文字列をバッファに書き込むが、書き込むだけで終端処理はされない。 バッファの長さより短い場合は後ろにゴミが当然だけど残っている。

再帰やループで (^) など「コピーを伴う連結」は使わない

s ^ s'

ss' の長さを合計した長さの文字列をアロケートして、 そこに ss' の内容をコピーする

という操作。再帰やループで文字列をどんどん連結する場合、文字列が長くなればなるほど どんどん遅くなる。文字列に限らず状態を変更していく再帰やループではコピーコストに 注意しなければならない。

文字列の場合は Buffer を使う。こいつは文字列の後ろに文字列を連結していくまさにバッファ の機能を提供するが、内部で文字列のコピーをできるだけ少なくなるようよろしくやってくれる。

鶏口となるも牛尾となるなかれ: xs @ [x] したら負け

リストの後ろに長さ1のリストを連結するコードはまずおかしいと思って良い。 俺のコードここで xs @ [x] してるけどおかしいとは思わないよ? ならば理由を説明できるはずだ。説明できないのならそれはおかしいのだ。

xs @ [x]xs の長さに比例した時間がかかる。これを再帰やループの中で 状態リストを長くしていく操作として使っているならお前はもう死んでいる。 x :: xsxs @ [x] の違いは初心者が思っているより深刻だ。

どうしてもリストの後ろに要素を一つ一つ足したいという場合は再帰やループ中では その逆リストを保持し、 x :: rev_xs として更新していく。最後に List.rev rev_xs だ。 こんなふうに変数名に rev と付けておくと親切かもしれない。

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 を使わない場合 メモリの無駄になるので、特にコード自動生成の場合に、 同じ文字列が大量に作られることがあり、注意。

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 などの協調スレッドを使えばよいのだ。

今のところ Thread は使わない

使っても嬉しいことは何もない。

  • OCaml は impure なのでスレッドから共有される mutable data structure は mutex で守る必要があるが、それが完璧にできる奴は地球上にいない。出来たように見える?一週間問題なく走り続けた後、君のサーバは謎の segfault で死ぬだろう。そのデバッグは一ヶ月かかる。
  • OCaml の GC は並行対応していないのでマルチコアでスレッドを動かしてもスレッドは並列にほとんど動かない
  • 以上の理由から誰も使わない

OCaml を作った Xavier Leroy は Linux pthread の作者。 それくらいなので、まあ真面目に動くんだよ、ちゃんと mutex 使えば。

並行計算(Concurrent computing)を行いたいなら Lwt や Async などの協調スレッドライブラリを使う。

OCaml で並列計算(Parallel computing)したい奴は fork するとか OCamlMPI 使うとかする。

Thread のコンテクストスイッチ

明示的に yield したほうが身のためらしい。

Record のラベルは差し支えなければ型名と同じにする

場合によるが、ラベル名と型名が同じだと書きやすく覚えやすい。例えば下のコード:

(* Like "soumu1234" *)
type employee_id = {
  prefix : string;
  number : int;
}

type employee = {
  name   : string;
  salary : int;
  id     : employee_id;
}

は、次のほうが読みやすい:

(* Like "soumu1234" *)
type id = {
  prefix : string;
  number : int;
}

type employee = {
  name   : string;
  salary : int;
  id     : id;
}

employee_id を id に変えた。逆に、ラベルを employee_id に変えるのは:

(* Like "soumu1234" *)
type employee_id = {
  prefix : string;
  number : int;
}

type employee = {
  name       : string;
  salary     : int;
  employee_id : employee_id;
}

…これは、長すぎて良くない。 id というラベルや型名が、 他のデータ型でも使われていてあまりに一般的だというのなら モジュールを使って名前空間を独立させれば良い(1モジュール1型主義):

module Employee = struct

  (* Like "soumu1234" *)
  type id = {
    prefix : string;
    number : int;
  }

  type t = {
    name   : string;
    salary : int;
    id     : id;
  }

end

Haskell や F# のように記号を使いたい

どうぞご随意に、自分で定義できますよね?私らは用意しません。 というのが OCaml スタイル。

ただし、OCaml では記号の結合方向や強さは主にその位置文字目の記号によって固定されており、 変更することができない。 これを使えない、と見るか、 妙な結合方向や強さを持つ記号を大量生産されないようにしているか (どこかに記載されている結合方向と強さを調べないと 人間が理解以前にパースさえできないコードってどうなんですかね?)、 と見るか、だが、兎に角現実として固定されいているのでまず それを列挙しておこう。

下にいくほど強くなる

  • right :=
  • right ||
  • right &&&
  • left =, <, >, |, &, $ で始まるものと !=
  • right @^ で始まるもの
  • right ::
  • left +- で始まるもの
  • left ** を除く *, /, % で始まるもの
  • right **
  • prefix !=, ~, ? を除く !, ~, ? で始まるもの

もし記号をふんだんに使った他人には判りにくいライブラリを作りたければ、 この表を良く良く見て、結合方向と強さにあった記号を選んで 少なくともあなた自身には便利なものを作らなければいけない。 幾つかよく使われる物を挙げておく:

Monadic bind >>=
Monadic bind >>= はそのまま使えば良い。 Jane Street では >>| を fmap として使っている。
Pipe |>

F# の |> はそのまま使えば良い。ただしパフォーマンスのために "%revapply" を使う:

external (|>) : 'a -> ('a -> 'b) -> 'b = "%revapply"
Haskell's $

Haskell の $ は右結合で結合力は最も弱い0。 OCaml では $ は左結合なので使えない。 これは右結合 & をお薦めする。Logical AND は logical OR が || なため、皆 && を使い、 & は使わないので使ってしまって良い。 |> と同じくパフォーマンスのために "%apply" を使う:

external (&) : ('a -> 'b) -> 'a -> 'b = "%apply"

残念ながら |>& より強いので f & g x |> hf & (g x |> h) とパースされてしまう。 見た目的には |> の方が分断感あるので (f & g x) |> h となって欲しいところ。 このためかどうか、 ITPL な人は |> の代わりに @@ を使っているが、 今度は、 >>= との強さ関係が (Haskell の $>>= とは)逆になる。痛し痒しである。 まあ、 (f & g x) |> hg x |> f |> h と書けば問題ない。

OCaml 4.01.0 から @@%apply として採用された。

Functional composition

関数合成 (Haskell の .) は右結合でかなり強くなければいけない。 ** を 使うのが良いと思う。当然、数値計算の ** と合成の ** を同時に使うことはできないので そこは場合によってよく使う方を選ぶことになる。私は数値の ** はほとんど使わないので let power = ( ** ) としている。

このところ ( *< ) がいいかなあと思っている。 (f *< g) x = f (g x) その逆は (f *> g) x = g (f x) 。どうだろう。

Others
ITPL の人は !%Printf.sprintf に使っている。 % は format 文の意か。私もこれを採用した。 Format.eprintf!!% にした。

長い module type を \*.ml\*.mli で繰り返すのが面倒なら \*_intf.ml を書く

モジュール型とそれに関連するコードを書いていて、モジュール型が長くなると \*.ml ファイルにも \*.mli ファイルにもそのモジュール型を書かなければならなくなり、 とても面倒になる。そういった場合はモジュール型の定義だけを外に出してしまうとよい。

例えば、 \*_intf.ml というファイルに。 \* というモジュールに関するインターフェースを 定義する、というくらいの意味の名前。ここにはモジュール型の定義のみを書くことにすれば 隠蔽の必要もないので \*_intf.mli を書く必要もない。

ラベル付き引数の使い方 (非オプション)

別に好きに使っていいんだけど、次のような時に便利

同じ型の引数を複数取る関数で、その引数の意味を型上にドキュメントとして区別したい

例えば String.blit の型を覚えるのは大変だし、間違ったらかなりヤバイので 型にラベルを付けて嫌でもユーザーに認識させる:

(* ラベルなし String にある *)
val blit :     string ->         int ->     string ->         int ->     int -> unit

(* ラベルあり。 StringLabels に定義されている *)
val blit : src:string -> src_pos:int -> dst:string -> dst_pos:int -> len:int -> unit
引数の順序を自由に変えてコードを読みやすくしたい場合

基本的に短く書ける引数を先に書いたほうがプログラムは読みやすい。 List.map などのように高階関数引数はダラダラっと長く、最後に一言リスト引数が書いてある と、えっと map の対象になるリストはドコドコドコドコ?となる。ならば入れ替えて 始めに書けると嬉しい。慣例上、高階関数のラベルを f にする:

List.map (fun x ->
    何行でも書けばいいさ
    ...                        えーっと map の対象はどこ?
    何行でも書けばいいさ
  ) xs                         <- あっ、こんなところに

(* ラベル付き引数で読みやすくした ListLabels.map *)
ListLabels.map xs ~f:(fun x ->     <- えーと、xs をとにかくナンカスルのね。
    何行でも書けばいいさ
    ...
    何行でも書けばいいさ
  )

オプショナル引数の使い方

オプションラベル付き引数とかオプショナル引数とか言われてる奴。

概要

? から始まるラベル付き引数はオプショナル。省略できる。 これは GUI ライブラリのような沢山の引数を持ち、かつそのほとんどの規定値(デフォルト)が決まっている、 つまり、オプションラベルが無ければ t option の型を持つ引数が多い場合にとても便利。

省略できると言っても、OCaml は関数型言語であるので、引数が規定値なので省略されているのか、 それとも partial application で適用されていないのか、区別に難点がある。 そこで OCaml では、もし、型の上でオプションラベル付き引数より後に来るラベルの無い引数が 与えられていた場合、その前に来るオプションラベル付き引数は省略され規定値が使われている と見なすことにしている。文章で書くと面倒だな。:

val f : int -> ?foo:bool -> bar:string -> 'a list -> unit

という関数があったとすると、:

f 3 []

の型は…なんですか? bar:string -> unit だ。 'a list というラベルのない引数が 与えられているので ?foo:bool は省略されているとみなされる。 bar:string は ラベルが付いているがオプショナルではないので残る。一方:

f 3 ~bar:"hello"

では 'a list が与えられていないので ?foo:bool は省略されておらず、まだ引数が 与えられていないと考える。なのでこの式の型は ?foo:bool -> 'a list -> unit

うっ、難しい?そりゃそうだ、わざと難しい例を出したんだから。

ラベル付き引数の記法

普通に見るのはこんな定義:

let f x ?(foo=false) ~bar alist = ...

でも、これは次の略記法:

let f x ?foo:(foo=false) ~bar:bar alist = ...

この記法だと束縛される変数名を変えることができる:

let f x ?foo:(x=false) ~bar:y alist = ...

とは言え、普通はラベル名と変数名を同じにする略記を使うのがほとんどだろう。 でもこれを知ってないと内部で使用されていないラベル付き引数を警告なしで書くことができない:

let f x ?foo:_ ~bar:_ alist = ...

デフォルトの無いオプショナル引数

オプショナル引数は ?(<ラベル>=<デフォルト>) と、規定値を書くことが多いが、この規程値 を書かないこともできる:

let f x ?foo ~bar alist = match foo with
  | None -> ...
  | Some y -> x + y

この際、オプショナル引数の定義内での型は τ option になる(上だと int option)。 引数が省略された場合は None~foo:3 と書かれた場合は Some 3 が束縛される。 関数自体の型での引数の型と、関数定義内部での引数の型が異なるのに注目。 上の関数の型では ?foo:int になっているが、 foo は定義内部では int option になる。

オプショナル引数を持つ関数を受け取る高階関数の扱い

f ~l:e というラベル付き引数の関数適用、これをコンテクストが不明な場合 コンパイラはどう考えるかという問題。

二つの可能性がある。

  • l という普通の(オプショナルでない)ラベル引数を持つ関数 f の適用
  • ?l というオプショナルなラベル引数を持つ関数 f の適用

この二つの場合は f に要求される型が異なる。そのため:

let h f e = f ~l:e

という関数には二つの異なる型付けが考えられる。これは主型付けが失われることを 意味するので ML の型システムとして大変によろしくない。 元凶はオプショナル引数の適用に、普通のラベル引数の適用と同じシンボルを 使ってしまっていることなのだが、まあそれはそれで便利である。

OCaml ではこの問題を、文脈上ではっきりしないラベル付き引数の 非オプショナル/オプショナルは、全部非オプショナルに倒すということで 解決している。すなはち、上の式では ~l は非オプショナルである:

let h f e = f ~l:e;;
val h : (l:'a -> 'b) -> 'a -> 'b = <fun>   (* l の前に ? がついていない *)

ではこのような定義で l はオプショナルなんだけど、という際にはどうするか。 コンパイラにその旨ヒントの文脈を与える:

let h (f : (?l:'a -> 'b)) e = f ~l:e;;
val h : (?l:'a -> 'b) -> 'a -> 'b = <fun>

もしくは、 ~l を使わずに ?l で明示する:

let h f e = f ?l:(Some e);;
val h : (?l:'a -> 'b) -> 'a -> 'b = <fun>

まあこういうことからも OCaml は型推論の完全性を目指しておりーとかは 寝言だという事がわかるだろう。

η 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 |> (.....) に展開されるようなマクロを書けば良い。 どうやって書くか?そりゃあんたに任せる。

bool とか抽象的すぎる時は使わない

例えば、関数が bool を返す。でそれが関数操作の成功か失敗かを表しているらしい。 どっちだ?知るかよ!! true は成功を意味するのか、それともエラー存在を意味するのか 書いているお前にしかわからないし、書いたお前もすぐに忘れる。なんで:

type result = Success | Failure

にしないのか。

極論するとわけわかんない抽象的な値を返される位なら元から返り値は unit にしてエラーは exception で上げてもらうほうがよほどマシかも知れぬ。

わざわざ result 型を定義するのが面倒なら OCaml には polymorphic variant があるから `Success`Failure を返せば良い。 `Success `Failure が長いんなら `Ok`NG でエエやろちょっとは頭使って 他人にやさしいコード書けやヴォケ。

右か左かを表すのに bool とか使う奴は死ねってことだ。わかるな?

OCamlYacc オワコン。Menhir を使え

http://caml.inria.fr/mantis/view.php?id=1703 曰く:

Since menhir is a much better alternative to ocamlyacc, we now recommend that all users switch to menhir. At any rate, we do not spend any time on new features for ocamlyacc.

List の ; と tuple の ,

List, Array, Record などの要素区切りの ; はとても奇妙に見えるかもしれない。 OCaml を使っている人でもあまり意識していないのだが、実は一貫性がある。

  • A ; B は B ; A と書いても型は変わらない
  • A , B は B , A と書くと型が変わる場合がある

例:

print_string "hello"; print_string "world"
{ a = 42; b = "hello" }
[ 1; 2; 3 ]
[| 1; 2; 3 |]

これらの要素を入れ替えても型は変わらない。もちろん意味は変わる場合がある。

例:

(1, "hello")
(int, float) Hashtbl.t

これらの要素を入れ替えると型が変わってしまう。

オブジェクトファイルのリンク順

OCaml のオブジェクトファイル(.cmo, .cmx)を並べる順番には意味がある。 順番を間違えると:

Reference to undefined global Hogehoge

などと言われるので注意。

a.ml:

let x = 1

b.ml:

let y = A.x

c.ml:

let z = B.y

というソースがあったとする。 a.ml, b.ml, c.ml の順に分割コンパイルする。これは問題ない:

$ ocamlc -c a.ml
$ ocamlc -c b.ml
$ ocamlc -c c.ml

さて、これをリンクする場合、依存関係の順にリンクしなければいけない:

$ ocamlc -o a.out a.cmo b.cmo c.cmo    # a.out 実行ファイルへとリンク

これを間違えると Reference to undefined global Hogehoge というエラーが出る:

$ ocamlc -o a.out b.cmo a.cmo c.cmo
File "_none_", line 1:
Error: Error while linking b.cmo:
Reference to undefined global `A'

OCaml でのモジュール毎の分割コンパイルと、そのリンクは、モジュール群のソースが、 連結されて一つの巨大な OCaml プログラムソースになったものをコンパイルする 作業を分割したもの、と考えると判りやすい。 b.cmo, a.cmo, c.cmo の順番での リンクは、 b.ml, a.ml, c.ml をこの順番でつなぎあわせたものをコンパイルするのと 同じで、b.ml の部分では a.ml のモジュール A は未定義。だからエラーになる:

module B = struct

    let y = A.x

end

module A = struct

    let x = 1

end

module C = struct

    let z = B.y

end

この上記のプログラムがコンパイルエラーになるのと同じである。

これは cma ライブラリを作る際の落とし穴にもなる ocamlc -o lib.cma b.cmo a.cmo とした場合、 lib.cma はエラーもなく作成される。その後、この lib.cma を使って例えば c.cmo とリンクし、実行ファイルを作ろうとすると、そこで初めてエラーとしてレポートされる:

$ ocamlc -a -o lib.cma b.cmo a.cmo       # lib.cma アーカイブ作成。エラー無し
$ ocamlc -o a.out lib.cma c.cmo          # a.out 実行ファイルへとリンク(失敗する)
File "_none_", line 1:
Error: Error while linking lib.cma(B):
Reference to undefined global `A'

上記の lib.cma は A というモジュールに依存した B モジュールと、それと独立した A モジュールを含むアーカイブになっている。大変に気持ち悪いがこのようなことができる:

$ ocamlc -o a.out a.cmo lib.cma c.cmo

この実行ファイルには A というモジュールが二回リンクされている。lib.cma 内部の B が使う A は lib.cma 内の A ではなく、 lib.cma の前に並べた a.cmo になる。同名モジュールが二回出てくることは OCaml のソース上ではあまりにわけが分からないので禁止されているが、リンカ上では OCaml の普通の値が同名の変数に束縛された場合 shadowing されるように先出のモジュールは後出のものに shadowing される。気持ち悪いがそういう挙動である。

このような問題を避けるにはモジュールを依存順に並べてリンクすればよいのだが、Makefile などの場合は手でモジュールリストの順番を調整する必要がある。 OMake などではこの依存関係を自動解析してくれるのでほとんど気にする必要はない…ただしモジュール間に渡る副作用の依存関係が存在していない限りにおいて、である。

なんで OCaml には Haskell の様な関数型マリオ機能がないのか

OCaml はその昔から関数型言語のくせに関数合成の記号すらない。(|>)(@@) が入ったのも つい最近である。なぜ、無いのか。

これは明文化されていないが、この言語と長く付き合うとだいたい判る。 OCaml は関数型配管工的なものはできるだけ提供しない、という思想が根底にあるとしか思えない。

何故便利な配管記号がないのか、何故自由に演算子の結合則を変えられないのか… なぜなら、それを許すとプログラムが読みにくくなるから、と中の人が考えているからだ、と思われる。 アルファベットで書かれた普通の関数は命名がしっかりしていれば、大抵意味はわかるし、 Googleability も高い。逆に読み方の判らない記号はそれが何を意味しているのか 調べるのはなかなか難しい。 OCaml はその先代の Caml-light の時代から教育で使われてきたこともあり、難しい記号はできるだけ 避けるようになったものと推測する。

Haskell がとっつきにくさの理由の一つに記号が難しすぎる、事を挙げても誰も否定できないだろう。 もちろん配管工記号があれば便利だし、それを使えばプログラムは短く読みやすくなる(こともある)。 ただしそれはその記号に慣れている人だけだ。OCaml はそれを気にして記号は数値演算以外は ほとんど使わないことにした。もちろん配管工記号がなければプログラムは長くなる、 が、それは別にいいじゃないか馬鹿っぽくても誰でも読めるのならば。 これは記号を組み合わせる関数型ギミックの好きな人には耐え難いことかもしれないが OCaml の根底思想なので、嫌なら OCaml の制限上で上手く演算子をやりくりするか、別言語をやるしか無い。

もちろん OCaml でプログラムを書いている人間も Haskell を触ったり F# を触ったりしていると 配管工記号が欲しくなってくる。周りからせっつかれて、最近ようやく (|>)(@@) が 入ったのはそういう事情による。

とにかく Syntax error が出るんです!

よく聞いてみると mli で関数を定義していた。 人に質問をする時は、他人にも状況を再現できるような質問をするように心がけよう。

Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.