そろそろ Jane Street Core について語ろう、かな #0: Core とは
Core とは Jane Street が開発、使用、オープンソースとして公開している OCaml 基本ライブラリ。この Core についてちょっと書こうかな。
ちなみに、ここに書いてあるのは一ユーザとしての意見。間違っても会社の見解ではありません。念のため。
発端
まず、このエントリの発端は、Caml-list に出現した「Why don't you use batteries?」という長ーいスレッド(今長さ57)。Batteries included というのは、OCaml の「標準ライブラリ」が余りに貧弱なので、それを拡充しようというものです。古くは extlib という 3rd party ライブラリがあって、それを取り込んでさらに拡張しています。(Batteries included も 3rd party。念のため。)
OCaml 貧弱標準ライブラリ
まず、その、OCaml の「標準ライブラリ」がどれだけ貧弱か。これはちょっと OCaml やった人ならすぐ判るでしょう。個々の関数は比較的よくテストされていて信用できるのですが、数が足りないのです。例えば、
(* filter_map : ('a -> 'b option) -> 'a list -> 'b list *) let rec filter_map f lst = List.fold_right (fun x acc -> (* tail rec にするのは各自の演習 *) match f x with | Some v -> v :: acc | None -> acc) lst [] ;; (* protect : (unit -> 'a) -> ~finally:(unit -> unit) -> 'a *) let protect f ~finally = try let res = f () in finally (); res with | e -> finally (); res ;;
とかが無いわけ。ある程度 OCaml 書いたことのある人なら必ず一回はそれらしいことを、別の名前でかもしれないけど、書いたことがあるはずです。書いたこと無い人は、まだ修行が足りませんな。
Batteries included、作ったけど余り使ってくれない、、、
で、イライラするから Batteries included という次世代標準ライブラリを作ったけど、あんまり使ってくれてないみたい。何で?という Batteries の人の質問が Caml-list に載ったんですよ。いろいろ回答が来ていて、アホな物を取り除いて、幾つかうなづかせられる物をあげておきますと、
もう俺専用ライブラリ作っちゃったよ、だからイラネ。
OCaml プログラミング経験が長くなってくると、大体皆、この標準ライブラリの貧弱さのために、自前の標準関数ライブラリを書くわけです。で、もうそれがあるから、特に他人の書いたものなんかいらない、と言う意見。これは私もそうだったから良く判ります。そもそも OCaml チームが全然「標準ライブラリ」を整備してこなかったし、あまりに長い間貧弱なままだったから、古参の OCaml プログラマはまず自分のライブラリセットを持っている。もう手遅れって訳。
OCaml チームの怠慢を追求したいところですが、彼らは研究所の研究員な訳で、判りきった標準ライブラリを書いて、それをメンテしていく、というのは業務としてはかなり優先順位は低いし、そこまでまわせるマンパワーも金も無かったのです。そんなことに時間を費やすのなら、型システムとか、コード生成とか、ランタイムとか、彼らにしか出来ない仕事で、かつ業績になる事をやってもらったほうが OCaml としては良かったはずだし、実際そうしてきた。で、周辺の OCaml ユーザーは自分で自前のライブラリを書いてきた。ある意味持ちつ持たれつだったので、そこんとこは理解していただきたいと思います。
元の標準ライブラリの嫌な部分がそのまま残っている。
いちいち取り上げてもしょうがないんだけど、関数の引数の順番とかが全然統一されて無いのですよ。Set.add と Hashtbl.add の型とか:
val Set.add : elt -> t -> t val Hashtbl.add : ('a, 'b) Hashtbl.t -> 'a -> 'b -> unit
なんでコンテナの位置が違うのよー!! Batteries はこれを踏襲しているそうです。もちろん Batteries の責任じゃないんですが。
どれぐらいちゃんとしたライブラリなのか、信頼できない。
Batteries の議論でよく見かけたのは、
「Batteries にこれこれこういう機能が入って無いじゃん。俺自分で書いたし」
「じゃあ、 Batteries に入れるからソース頂戴よ!!」
オープンソースな感じですね。別にこのこと自体はいいんですが、で、集めるだけ集めて、だれがメンテしてるの?どれぐらいバグがあるの?それが判らないと怖くて使えない。その辺りのテストとかレビューがどうなっているのでしょうか、私は良く知りません。良く考えられているとよいのですが。
OCaml の「標準ライブラリ」は、数はそろってなかったけど、関数の質は結構良かったと思います。やっぱり OCaml ユーザーなら全員持っていて、使うわけですから、かなり枯れていた。いや、 tail recursive じゃない関数とかありますよ。でもそれは皆当然判って使ってた。(Tail-rec な List.fold_right が欲しかったら List.fold_left と rev 使えや、って感じ。)
私が Batteries は使わない理由: Core
私は Jane Street という外資系金融会社で、OCaml を使ったシステム開発チームの一員として働いています。
ふーん、OCaml ですか、いいですね、でも一部のチームだけでしょ、せいぜいポジション管理とか、そういう time-critical じゃ無い一部分のプロジェクトとかでしょ。他は C とか Java とかで書いているのでしょう
とよく言われますが、違います。いいえ、そういうの全部ひっくるめて大部分(>80%)のシステムを OCaml で開発しています。大部分を出来れば全部に近づけようと日々仕事してます。
そんな Jane Street で開発したライブラリが Core。これも Batteries included と同じで、 OCaml の「標準ライブラリ」を補強するために作られた強化版標準ライブラリです。ですから、機能的には別にすごいものは入っていません。XML をいじったり、HTTP で通信したり、GUI を書くための物ではありません。そういうのは誰かが開発したオープンソースのものを使ったり、Core の外で自前で作ったりしています。あくまでも Core は強化標準ライブラリで、上にあげた filter_map とか、 protect とか、何で無いのー?という物を集中的に網羅しています。この Core はオープンソースとして公開されていますから、私は個人的なプログラミングにも使っています。
Core を使う理由
慣れてる
ま、当然ですけど、お仕事で使ってるから、慣れてるってのがまずありますよね。これは個人的すぎますね、すいません。
Core の機能と特徴
えっともしかすると現在公開されている Core と、私が職場で使っている Core に少しバージョン上の違いがあって、説明が正しく無いかもしれません。その場合は、次のバージョンで多分そうなると思ってください。
何でこれが無いの!?が有る!!
そりゃ全部じゃないですけど、よほどましです。List.filter_map とか、protect がありますよ。まーこれは .mli を覗いてみてください。目新しいものとしては、bag.mli, hash_set.mli, heap.mli, memo.mli, monad.mli, pMap.mli (多相マップ), pSet.mli(多相セット), time.mli, month.mli とかが有りますね。
Labeled interface
これは好き嫌いが分かれますが、、、Labeled argument を使ったインターフェースを採用しています:
module List : sig val fold_left : f:('a -> 'b -> 'a) -> init:'a -> 'b t -> 'a end
Label が付いていると適用順序を自由にできるから便利ですよ:
List.fold_left list ~init:[] ~f:(fun acc x -> 長ーいコード。何十行も )
短い部分は先に書いてしまえます。
Not_found より option
元々、Not_found を飛ばす関数は、例外じゃなくて、 option タイプを使うインターフェースになっています:
module Hashtbl : sig val find : ('a, 'b) t -> 'a -> 'b option val find_exn : ('a, 'b) t -> 'a -> 'b (* 例外 Not_found を飛ばす *)
これはあまりに Not_found を飛ばしやすくしすぎると、例外処理がおろそかになりがちになるからです。
それに、実は option で match する方がコードが綺麗に書けることが多い:
try let res = Hashtbl.find_exn t key in あったときの処理 with | Not_found -> (* この Not_found は Hashtbl.find からなの?もしかしたらその後かもしれない *) なかったときの処理
より、
match Hashtbl.find t key with | Some res -> あったときの処理 | None -> なかった時の処理
の方がいいでしょ。もちろん場合によりますから使い分けが肝心ですけど。
ちなみに現在公開されている Core では List.assoc がまだ option じゃなくて Not_found を投げたりします。ちょっとこの辺は移行期の部分があります。
Tail recursive
OCaml の「標準ライブラリ」は List.map や List.fold_right が tail recursive じゃないって知ってました? Core では tail-rec になっています。
モジュールが定義しているデータ型は第一引数に
Set.add とかも第一引数は set になります。List.assoc は余りに有名すぎるので、相変わらずですけど。
Core こんな感じです。
はい、とういわけで急いで Core を見てきました。ちょっと始めはとっつきにくいかもしれないけれど、使い始めるとじわじわと良さが判って来ると思います。「標準ライブラリ」, extlib, Batteries と試したけどどうも、、、という方は考えてみてください。
それからこれも注意して欲しいんですが、Core は大きいです。何にでも使っていいってもんじゃないです。例えば、ある程度小さいソフトを書いて、オープンソースで公開しますね!となったときに、Core を入れてくださいって書いてあったら、それだけでかなりの人がインストールをためらうでしょ?そういう時は「標準ライブラリ」を使いましょう。(てことになるから、もうちょっと「標準ライブラリ」には頑張って欲しい!んですけど、これはもうしょうがない)
これから時々気が向いたら Core のも少し詳しい使い方を書いていくね。
オチ
いやー、まー、Coq で証明してあるわけでも無いから、ある日バグがあって会社が潰れて Core 終了、とかなっちゃうかもしれないけど!!