そろそろ 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 を使う理由

慣れてる

ま、当然ですけど、お仕事で使ってるから、慣れてるってのがまずありますよね。これは個人的すぎますね、すいません。

信頼性

Jane Street では、実際に、この Core を使ったプログラムを動かしてトレードを行っています。ですので、もし Core に変なバグがあったら、、、多分すぐに倒産します。ですから、 Core のメンテ、テスト、コードレビュー、すごく時間を掛けてます*1。社命(とおまけに私の生活)がかかっているといっても良いでしょう。皆さんにとってはどれぐらいの credibility になるか判りませんが… もちろん、Core もオープンソースとして使う限りは no warranty ですけれど、それは「標準ライブラリ」、Batteries included も同じ。

Core の機能と特徴

えっともしかすると現在公開されている Core と、私が職場で使っている Core に少しバージョン上の違いがあって、説明が正しく無いかもしれません。その場合は、次のバージョンで多分そうなると思ってください。

使い方

インストールして

open Core.Std

さえすれば、普通の OCaml の「標準ライブラリ」と同じような名前空間で 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 終了、とかなっちゃうかもしれないけど!!

*1:これを知ってると、OCamlチームは「標準ライブラリ」に手を抜いている、ふざんけんな、とは言えません。コストが掛けられないのです。