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

Random memo of OCaml programming

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

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.

トップレベルで let _ = は使わない

Structureトップレベル(REPLの事ではない。実装ファイル *.ml 中での structure 内のトップレベルのこと) で関数を部分適用して、そのまま何もしないのは、大抵の場合引数が足りず間違っているコード:

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 推奨。

Constructor を curry 化したい

できない

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

CamlP4 で各コンストラクタに対応する curried form な関数を定義することはできる。 http://www.digipedia.pl/usenet/thread/14273/5152/ 現在も動作するかは不明。 type-conv を使えば簡単に自作できる。

標準ライブラリの再帰関数を使ったら 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 で結果を受けていればあまりこの問題には遭遇しないが、 それでも私はこの手のバグに年に一度くらいハマる。 ポイントフリー教徒はより頻繁にハマるであろう。

raise Exit, raise Not_found でコードが読みやすくなるなら使う

例外による再帰関数からの大域脱出は OCaml ではランタイムのペナルティはほとんどないので、 try with を書いてそれでもコードが読みやすければ使って構わない。

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

カスタム 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 は多相性のため、必須。

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

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 :: xsl @ [xs] の違いは初心者が思っているより深刻だ。

どうしてもリストの後ろに要素を一つ一つ足したいという場合は再帰やループ中では その逆リストを保持し、 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 などの協調スレッドを使えばよいのだ。

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

今のところ Thread は使わない

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

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

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

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

OCaml で並行計算したい奴は fork するとか OCamlMPI 使うとかする。

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

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

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 -> ('a -> 'b) -> 'b = "%apply"

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

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

ocamlopt を使いましょうね

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

OCaml を使うなら Unix で

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

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

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

長い 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 をとにかくナンカスルのね。
    何行でも書けばいいさ
    ...
    何行でも書けばいいさ
  )
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.