one限量で複数の変数を宣言する
Alloyの言語仕様をゴリゴリ読み進めていた。 非常に勉強になります。
Software Abstractions (1st ed., p.288):
So although a quantified constraint with multiple declarations may be regarded, for some quantifiers, as a shorthand for nested constraints, each with one declaration, this is not in general true. Thus
all x:X, y:Y | Fis short for
all x:X | all y:Y | Fbut
one x:X, y:Y | Fis not short for
one x:X | all y:Y | F
なるほど。しかしoneという限量子は自分が知っている一階述語論理には出てこなかったので、馴染みがない。実際のところどう違うんだろう。
次のAlloyコードを基本として、ちょっと考える。
sig X {r : set Y} sig Y {} fact { #X=2 and #Y=2 } run {}
sig宣言で、それぞれ2つの要素をもつ集合XとYを与える。 シグネチャX はフィールド r をもち、型は set Y と宣言されている。これは X と Y の間に2項関係 r がありますよというのと同等だ。 Alloy Analyzer で Execute すると、実際に図で確認できる。
次のファクトを考えよう。factはモデルに常に成り立つ制約を追加する。
fact {one x:X, y:Y | x->y in r}
ここでは x->yは 単に (x,y) と同等だ。 結局、このファクトは 関係rの要素になる(x,y)の組み合わせはただ1つだと述べている。
一方、上の制約のかわりに
fact {one x:X | one y:Y | x->y in r}
と書いた場合はどうだろうか。真理値表で確認してみるのもいいが、せっかくなので Alloy Analyzer に表示させてしまおう。
たしかに、内側のone y:Y|x->y in r が偽になるには、x2->y1, x2->y2がrに入っていても良いわけか。
x | y | x->y in r | one y:Y | x->y in r |
---|---|---|---|
x1 | y1 | t | t |
y2 | f | f | |
x2 | y1 | t | f |
y2 | t | f |
OCaml toplevel on Android : マジカルなラクダをAndroidで飼おう
Androidで動作するOCamlインタプリタ OCaml toplevel on Android を Android Market に公開しました。 OCamlのトップレベル(インタプリタ)をAndroid上で操作できます。 enjoy!
仕組み
OCamlトップレベルはネイティブ実行されます(OCamlバイトコード+libcamlrun.a)が、AndroidのアプリはJava VM(dalvikvm)上で動作するため、両者のブリッジが必要でした。 いくつかの方法があり、一部は先日書きましたが、今回の OCaml toplevel on Androidはこのなかのどれでもない、最もイージーな方法を使っています…。
それは 「あるスレッドでocamlトップレベルをバイトコード実行し、標準入出力を介してAndroidアプリのメインスレッドと通信する」という方法です。
stdoutを入力として、stdinを出力としてJava側で開くという荒技が必要でしたが、特段むずかしいわけではなかったです。概要は以前ここに書きました。 この方法は他のLinux CUIアプリをAndroidにポーティングする時にも使えると思います。そんな機会は無さそうですが。
OCamlクロスコンパイラ
O'Caml on Androidや O'Caml for iOSはクロスコンパイラです。しかしクロスコンパイラといっても、コンパイラそれ自体はバイトコード実行できるので、実質ストレートなコンパイラと変わることはありません。 必要な条件は
- コンパイラを実行するためのバイトコードインタプリタ(ocamlrun)は、ホスト環境でビルドされている
- OCamlコンパイラのソースツリーは ターゲット環境向け (ARCH=arm, SYSTEM=linux) で それらしく configure されている
というだけです。 あとはうまくbytecomp をブートストラップできれば、それを使ってasmcomp, asmrun がビルドできます (ということを、O'Caml for iOSを調べて知ったのですが)。
ターゲット環境にホスト環境の構成が混ざるとまずいのですが、ocamlrunの実行には、(すくなくともコンパイル処理に関しては)環境依存の部分がないはずなので、そのようなことは起こらないです。 …たぶん 追記:これはウソ。たとえば 64bit MacOSX では 32bitでocamlrunをビルドする必要がある。64ビット環境で動作するocamlrunで 32ビット向けのコードを吐くocamloptをコンパイルした場合(多分)、正しいバイナリを出力しない。 。
Android固有のコードは書いてない
今パッチを読んでみて思い出したのですが、 OCaml on Androidでは全く処理系を修正していないです(ネイティブコンパイラ(asmcomp)もインタプリタ(byterun)も、ランタイム(asmrun)も)。 びっくり。 OCaml 3.12 から armeabi がサポートされたので、Androidネイティブのバイナリをそのまま生成できるんですね。 ただ、ライブラリの otherlibs/unix で、Androidには tcdrain がないのでこけていたので、そこを修正した、それだけ。
(じゃ、O'Caml for iOS はなぜasmcomp周りを修正しているのか?気になりますが、調べていません)
苦労したところはむしろMakefile周りでした。 OCamlはコンパイルやリンク時にgccを呼ぶので、そこをターゲット環境向けにカスタマイズしなければならない。しかしAndroidのツールチェインはJNI向けにカスタマイズされた複雑なMakefileの集合体になっていて、普通のgccとは勝手が違う。 手探りな所も多く、面倒だったのです。
pa_monad を通して camlp4 のコマンドラインを思い出す
pa_monad は OCamlで モナドを使いやすくするための構文拡張だ. ついこの前 id:camlspotter さんが pa_monad の拡張を書いてくださった.ありがたや. 早速使ってみようと思い,コンパイルした.hg clone https://bitbucket.org/camlspotter/pa_monad_custom でダウンロードしたあとは make一発です.
ただ,ちょっと ocamlc から試そうと思ったら,俺 camlp4 のコマンドラインオプションを全然覚えてないよ,ということに気づいた. いつも忘れるので書いておく.
ocamlc -pp 'camlp4of pa_monad.cmo' a.ml
ocamlc/ocamloptのオプション -pp に続けてプリプロセッサのコマンドラインを書く.
しかしそもそも camlp4of とはなんだろう? camlp4 には camlp4o, camlp4of, …などの種類があり,素人には分かりにくい.
結論からいえば,普通は camlp4of を使っていれば良い. ここで o は Orignal syntax の O で, f は 全ての(full)構文を使うということだ. 万が一, camlp4拡張を自分で書くときには camlp4oof, camlp4orf を使えばいい. 細かい違いについては後で書く.
ちゃんと調べたら簡単だった…
ocamlfind から camlp4 を利用 : ocamljs の場合
ただ ocamljs の JavaScript 構文拡張など,多数の依存ライブラリを含む場合,
camlp4of -I /opt/local/lib/ocaml/site-lib/ulex -I /opt/local/lib/ocaml/site-lib/jslib ulexing.cma jslib.cma syntax_inline.cmo
などと,-I で参照するライブラリを指定して,依存ライブラリを 全て渡してやらないといけない. こうなると面倒なのでocamlfindを使う.(omakeはどう使うんだろう…)
幸い ocamljs は ocamlfindjs というカスタマイズされたocamlfindがついてくるので, JavaScript の構文拡張を使う場合は
ocamlfindjs ocamljs -syntax camlp4of -package jslib.inline, -c a.ml
とするだけで良い. ここで jslib.inline は jslib パッケージの一部で, ocamlfind の site-lib 下の jslib/META に詳しい内容が書いてある.
ocamlfindjs でなく ocamlfind の場合も
ocamlfind ocamlc -syntax camlp4of -package パッケージ名 -c a.ml
などとすればいい.
pa_monad の ocamlfind化
pa_monad を ocamlfind 化してみよう. まず META という名前のファイルを次の内容で準備する:
name = "pa_monad" archive(syntax,preprocessor) = "pa_monad.cmo"
この METAを pa_monad.cmo と同じ位置に置き,
sudo ocamlfind install pa_monad META pa_monad.cmo
とすれば, /opt/local/lib/ocaml/site-lib/pa_monad 以下にふたつのファイルがコピーされる.あとは,
ocamlfindjs ocamljs -syntax camlp4of -package pa_monad,jslib.inline -c a.ml
などとすればいい.
ここで jslib.inline と pa_monad の両方を指定している.複数の構文拡張を共存させられるのが camlp4 のすごいところだ.
camlp4, camlp4o, camlp4of, camlp4oof, …
camlp4o* …どう違うのか.
このあたりUndocumented気味なので正直自分も正確に把握していないのだが, 公式Wiki? を読んで,次のようになっているのではないかと思った.
camlp4(o(f|of|rf)?)?
- camlp4 単体だと何もparseしない. camlp4 -parser o … などと他のオプションで細かくparse/printするときのみ使う.
- camlp4o 拡張構文なし
- camlp4of 拡張構文(ストリームやリスト内包表記など)あり.
- camlp4oof 拡張構文あり. さらにメタ構文 (<:str_item< ... >> とか) が書けるようになる. <:???< ... >> の中身は original syntax.
- camlp4orf 拡張構文あり. さらにメタ構文が書けるようになる. <:???< ... >> の中身は revised syntax.
正直,ただの camlp4of でリスト内包表記ができるようになることを今まで知らなかった… [i * 2 | i <- [1;2;3]] みたいに書けます.
camlp4rf?
- camlp4r revised syntax.
- camlp4rf revised syntax と 拡張構文.
camlp4 -parser p -parser op -printer a とか, -filter とか,まだまだ知らないオプションは多い…
OCamlベースのAndroidアプリに向けて
O'Caml on Android というパッチを作っている. 柔軟かつ信頼性の高いOCamlでAndroidのアプリを書くのが目標だ. ここ2ヶ月ほどocamljs (OCamlからJavaScriptへのコンパイラ) を使っていて、OCamlの信頼感と柔軟性にとても満足したので,ではAndroidでもOCamlだ、とばかりに開発を再開している.
iPhoneやiPadのObjective-Cと違い,AndroidではJavaの型の扱いやJVMとの相互作用が必要でありなかなか厄介だ. しかしiPhoneではいくつかの開発 事例もあるのでAndroidでもそれなりに有効ではないかと信じている.がんばりたい.
OCamlを使う利点
もはや自分の中では言い古した感があるのだけど,念のため列挙してみる.
信頼性
OCamlには null値がない.このため NullPointerExceptionが起こらない.さらに,代数的データ型が使えるため パターンマッチによる場合分けの 網羅性検査 がある. Javaにも型システムがあるけれど, 全ての参照型でnull値が許される点と,nullチェックの抜け漏れが容易に起こり得る点でよろしくない.
OCamlを経由することの欠点
欠点がないわけではない.
マルチスレッドの扱い
OCamlのGCは並行でない. もしAndroidがマルチコア化される時代が来れば,パフォーマンス上の問題になる.またOCamlのコードを同時に実行できるスレッドは1つだけであり,途中でOCamlスレッドのコンテキストスイッチが発生しないケースがあるかもしれない(過去の日記参照;ほとんどないと思うけどAndroidで調べてない).
OCamlとJavaの連携: 4通りの方法
AndroidアプリはJavaベースなので, Javaから OCaml を実行するには次の2つの軸から4通りが考えられる:
- JVM or JNI ?
- ocamlc or ocamlopt ?
私がつくっているO'Caml on Android (名前を付けたい…)は、Android NDKを使うので (B-1) か (B-2) だ. 一方,Cadmium を使った OCaml on Androidは JVM上でOCamlバイトコードを解釈実行するので(A-1)だ.
JNIを使った OCaml-Java連携: 3つのレベル
JNIを経由したOCamlとJavaの連携には,洗練度により,次の3つのレベルがある:
下の項目は上の項目に依存しているので,順に解説する.
原始的な方法
JNI経由でOCamlを呼ぶには,まず JNIで呼び出す関数をエクスポートした共有ライブラリ(AndroidはLinuxベースなので .so)を作成し,Java側で System.loadLibrary() でロードする.(OCaml-Nagoyaのwikiにも書いてみた)
そこで まず私は OCaml on Android という, .so を生成できるコンパイラを準備した. ビルドには Android NDKが必要である.
あまり細かいことはできないが,例えば OCamlのトップレベルを Android アプリとして動かせるようになる(なった).
camljava を使う
camljava は OCaml のモジュール Jni を提供しており,ここから JNIの関数をだいたい叩ける.しかし camljava を Android で使うにはいくつかの障壁がある(O'jacareも同じ問題がある)
- camljava は main関数が OCaml側にあるのが前提で,そのままでは使えない
- メモリ管理系の関数が欠落
- DeleteLocalRef がない. 多くのクラスやオブジェクトを参照すると local reference tableがいっぱいになる
- Androidのバグ.私がレポートしたものは
- 親インタフェースのメソッドを子インタフェース経由でGetMethodIDできない
- http://code.google.com/p/android/issues/detail?id=13771
- O'jacareが生成するコードが例外で止まる
- コンストラクタをCallNonvirtualVoidMethodで呼ぶと例外(NewObjectで呼べばOK)
- http://code.google.com/p/android/issues/detail?id=13832
- このためコールバック用スタブ(Callbackクラス)の初期化に失敗する
- 親インタフェースのメソッドを子インタフェース経由でGetMethodIDできない
現在パッチを書いている.手元で大部分の修正はできているが,メモリ管理周りはまだ自信がない.
O'Jacareを使う
O'Jacare は, camljava をベースに作られている.Javaのクラスを OCamlのクラスとして扱えるのが利点だが, Javaはメソッド名のオーバーロードを許すがOCamlでは許さないといった差異があるため, スタブの生成には IDLファイルを準備する必要がある.
Java のクラスについて宣言した .idl ファイルを ojacare コマンドに与えると, OCaml 側のスタブと Java側のスタブが生成される.OCamlのクラスとして, Javaのクラスを扱える. OCaml側でこのクラスを inherit すれば,Javaのクラス/インタフェースを拡張/実装でき,コールバックできる
しかし,ざっくり Android のクラスを ojacare にかけてみたところ,次の問題をみつけた.(私の備忘録として…)
- (マニュアルに記載あり)Java側の参照を開放しない.
- 型システムの制限
- メソッド名の問題 (軽症).
- O'jacareビルド時の camlp4の非互換性.
- camljavaと同様の問題.
- クラスへの参照をキャッシュしてしまうがメソッド終了時に参照が失われる…
- 多くのクラスをIDLに含めると,起動時に local reference tableの上限(たった512!)を越えて findClass等をするので落ちる
- O'jacare の問題.
- (修正済み) src/check/type.ml で Ishort -> Cbyte となっているのは Ishort -> Cshort
- int が camlint にマップされてしまう. R.layout.main などは 扱えない
型システムの制限はやや厳しい.また,メモリ管理周りも改善しないと使えない.
現状
Haskell厨を6年やってる俺がOCamlを仕事で2ヶ月使ってみた
Haskell Advent Calendar jp 2010のためのエントリです(17日目).
6日目の id:camlspotterさんの 経験15年のOCaml ユーザーが Haskell を仕事で半年使ってみた に対するカウンター(になってるかどうか分からないですが)みたいな感じです.
近くて遠い隣人:HaskellとOCaml
OCamlはHaskellと違って副作用があり,更にHM型推論をもつためプログラマは本質的な部分の記述に注力しつつ,コードのチューニングもできる. つまり働くHaskellプログラマがシリアスなソフトウェアを書く時に使えるほとんど唯一の選択肢だ.しかし,同じ静的型付けの関数型言語でありながら,OCamlとHaskellの見た目はかなり異なる.
この記事では, HaskellプログラマがOCamlを使い始めると,どういうトラップにハマるかを書く. なかでも,主に構文的な側面について書く.eager evaluationや型システムについては(皆さんはちゃんと考えてお使いになる思うので)(ほとんど)扱わない. しかし,たかが構文と侮るなかれ,OCamlのヘンテコ構文はHaskellianに狙い撃ちで牙を剥くように設計されている.
とくに,id:camlspotterさんの記事で
と仰っている部分について,汚く見えることは慣れの問題で済むのだけれど,問題は OCamlではキーワードは沢山準備しているくせに 記号(;)(=)が濫用されている ことで,これはOCaml初心者にとってイライラの元になる.
カンマ
致命的なトラップに触れる前に,まずカンマにまつわるブービートラップを2つ紹介する.
OCamlにおけるカンマとは,他言語ユーザがOCamlを触りはじめて,うっかり
# List.fold_left (+) 0 [1,2,3];; ^^^^^ Error: This expression has type int * int * int but an expression was expected of type int
などとやってしまうことからも分かるように,初心者チェッカーの一つである. OCamlのリストリテラルのセパレータは `;' なので, Haskellの [1,2,3] に相当するリストは [1;2;3] と書くのが正しい.さらに,OCamlのタプル e1,e2,..,en は外側のカッコが不要であるので, [1,2,3] と書くと3項組の要素を1つだけもつリストに解釈される.
タプル型は int * string * bool のように書く. Haskellianはたまに (int, string, bool) などと書いてコンパイラに怒られて悲しくなる. 唇を噛み締めつつ甘受しよう. 余談だが Coqでは型も値であるので nat * nat と (nat,nat) の両方が構文的に正しいのでやはり時々ひっかかる.
もう一つ注意しておきたいのは,カンマの結合の強さである. Haskellianは注意しないとたまに変な型エラーを出して5分くらい悩むことになる.人工的な例で恐縮だが,Haskellで書いた関数の対
(\x -> x+1, \x -> x-1)
と,OCamlで書く似たような式
(fun x -> x+1, fun x -> x-1)
は,意味も型も全く異なる別の式を表現している.OCamlはカンマが中置の演算子であるために
fun x->(x+1, fun x->x-1)
と解釈されているのだ. 嗚呼. 心が落ち着いたらこう書こう:
(fun x -> x+1), fun x -> x-1
追記 コメント欄でmametterさんによれば確かに ( (fun x -> x+1), (fun x -> x-1) ) の方がいいですね。
セミコロンNG集
タプルの間違いは型エラーでキャッチできる.しかしセミコロンに関する誤りはもっと深刻だ.セミコロンは次の式レベルの構文で使われる:
- 逐次実行の式セパレータ print_newline(); a:=1
- リスト式の要素のセパレータ [1;3;5;] ['a';'b';'c']
- レコード式のフィールドのセパレータ {name="keigo"; age=28; sex=Male}
深刻なのは1.で,他の2つでハマっても型エラーで検出できる.
逐次実行セミコロンの罠
OCamlのセミコロンで問題なのは分岐構文との相対的な優先度だ.
どちらも分岐構文であるはずなのだがこの一貫性の無さは何だ.
まず,関数型以外のどんな言語にも良くある例なので引っかかりにくいかもしれないが
if false then print_endline "Hello,"; print_endline "World!"
と書くと, World! と表示されてしまい悲しいことになる(C言語系のプロジェクトであれば,if文には必ずブレースを忘れず if(..){..} を書くようコーディングルールで定めているところもあるだろう).
一方,
try func () with _ -> (); (*例外を無視*) print_endline "end."
は,func()が例外をraiseしない限り end. と表示してくれない(私もmatchでは気をつけていたつもりだったのだが,tryでひっかかってしまった).
Haskell では,逐次実行のセミコロンは do構文を伴うため,このような誤りは起こらない. 一方,OCaml ではカッコ '(' ')' か begin .. end で囲まなければならない. しかしHaskellianはカッコをあまり好まずコンビネータの組み合わせと $ でプログラムを書く傾向がある.つまり,Haskellと同じノリでOCamlを書くとこのような罠にはまる.なまじ似た言語であるために,この違いはHaskellianにとってきわめて深刻なのだ(おおげさ).
一応,正解を書く.
if false then ( print_endline "Hello,"; print_endline "World!" )
(try func () with _ -> ()); print_endline "end."
このような罠が仕掛けられたパターンマッチ構文は
- match .. with p1 -> e1 | ..
- try .. with ex1 -> e1 | ..
- function p1 -> e1 | ..
の3種類がある.これらを書く時はくれぐれも変なインデントやワンライナーを使わないように気をつけたい.
余計なセミコロンの罠
新人からやる気を削ぐのはどうでもいい非本質的な慣習的な規則に振り回されるときだ. くわしく言うと,OCamlの基本的な構文エラーから脱出できないときだ.
次のコードは,実際に私が書いたコードだ:
(追記 id:mzpさんより==は=ではないかと指摘を頂いたがここは==で正しいつもりだ(参考:remove_assq)。 しかし確かに Haskellian は =と==を間違えがちだろう(私も何度かやった)。それと /=の代わりは != ではなく <>だ。気をつけたい(…私はハマった).)
let link (a:t) (b:t) ?(link_dir=Both) () = if List.exists (fun (x,_) ->x==b) a.linked || List.exists (fun (x,_) ->a==x) b.linked then failwith "already linked" else a.linked <- (b,link_dir)::a.linked; b.linked <- (a,link_dir)::b.linked; update_funs a b; let unlink (a:t) (b:t) = b.linked <- List.remove_assq a b.linked; a.linked <- List.remove_assq b a.linked; update_funs a b (* 316行目 *)
かなり初期に書いたコードなので今思えば existsは mem_assq で置き換えるべきだとかワンライナー止めれとか感じるがががそれは置いといて,このコード片を含む.mlをコンパイルすると次のエラーが出る(camlp4を使っている場合):
File "scroller.ml", line 316, characters 16-17: Parse error: "in" expected after [binding] (in [expr]) Preprocessor error
さてどこが誤っている??
賢明な諸氏ならすぐにお気づきかもしれないが, linkの定義の最後に余分な ; があるために,次の行と定義が結合されてしまっている.しかしここで問題なのは,エラーとして報告されている行番号と誤りが含まれる行が遠いということだ.
let _ = print_string "Hello,"; let _ = print_endline "World!"
は,
let _ = print_string "Hello,"; let _ = print_endline "World!"
と解釈され,最後の let に in が無いと言って怒るのだ.ちなみに上記のコードをコンパイルすると,存在しない5行目でError: Syntax errorと言われる.この例ならまだ良いかもしれないが,より長い関数でこれが起こると,初心者OCamlプログラマが誤りを発見するためにより長い時間を必要とする.
セミコロンは多くの言語で「いくら余分に書いても大丈夫」なものとして扱われているので(Haskellでも do {;;;return();;;} という式が許される),OCamlがこのような罠を張っているのは悲しいことだ.
1年前のOCaml Meetingで,五十嵐先生は「ML型推論の光と影」という講演のなかで,Hindley-Milner型推論(Haskellでも使われている)が時に誤りがある箇所から遠い場所をエラーとして報告する,というお話をされた.しかしより基本的な部分で,OCaml初心者は似たような状況に直面するわけだ.
追記 unit型の値を返す関数では,末尾に () と書きそれ以上行継続しないようにしておけば,この問題で困ることはなくなる.トップレベルのletとletの間に必ず ;; を入れておくのも手だ。
意味が異なるセミコロン,意味が異なるイコールの罠
あまり頻繁に遭遇する状況ではないが,もう一つセミコロンにまつわる問題をお伝えしておく.追記 id:KeisukeNakano さんによる指摘、do_something : unit -> bool でなく do_something : unit -> unit が適切ですね。修正しました!
type r1={do_something : unit -> unit; x : int} let x=1 let o = {do_something=fun _ -> print_endline "done."; x=1}
File "a.ml", line 3, characters 8-57: Error: Some record field labels are undefined: x
何で? xは定義しているよ? …これは
let o = {do_something=(fun _ -> print_endline "done."); x=1}
が正しい.リストでも同じ問題がおきるが,これはレコード値のフィールド定義のイコールと,比較のイコールで意味がことなるのも関係している.
これは基本的にフィールドのセパレータのセミコロンと逐次実行のセパレータのセミコロンが衝突しているのがよろしくないように感じるけれど,さらにイコールの衝突が関係しているので話はおもしろい深刻だ!!
上記の事情から,ちょっと変わったパターンの怒られ方もありうる:
- 追記 xが定義されていないとき,Error: Unbound value x と怒られる(このパターンが多いはずだ). コンパイラがxをフィールド名でなくvalueとして解釈しているので本当に混乱してしまう. 最初にこれに遭遇したとき,funにカッコをつけるのを思いつくまでに2,3分くらいかかった.
- do_somethingの型が unit -> unit のとき,Error: This expression has type bool but an expression was expected of type unit とか言われて???となる.
- あとこんな例も以前書いた.なんだ Warning S って! ありがとう!!
閑話:コロンとラベル付き引数
コロンは人畜無害で良いヤツだ. Haskellの :: と意味が逆 (コンスは 1::[]のように書き, 型注釈は 1 : int と書く) なのを除けば,あとはラベル付き引数の書き方を覚えればよい.
ラベル付き引数は,他の言語でいう名前つき引数とか呼ばれる便利な機能だ.この点でOCamlがすごいところは関数プログラミングで名前付き引数が使えることだ.何が凄いのか?無名関数の名前付き引数や半適用が可能なうえ,型推論されるのだ!!! (なにか制限はあったっけ,覚えてない). ラベル付き引数はOCamlプログラムの至るところに現れる. labltk や lablgtkなどのライブラリはHaskellianでも一度は聞いた事があるだろう. ラベル付き引数はプログラムの意味上の誤りを防ぐための便利な道具として効果的に使える.引数の順序について思い悩むことはもうなくなる.格言っぽい言葉まである. Haskellにも似たようなヤツは居るが全然使われないばかりかTemplate Haskellにまでハブられる悲しい存在であるのとは対照的である.
追記 @shelarcy さんよりコメント: Haskellではオプショナル引数やラベル付き引数のようなものを実現するときは,フィールドラベルをもつデータ型を用い,さらにデフォルトの値を前もって定義して,フィールドの一部更新の構文を使うことがあるそうです. これなど. うまいやり方があるものですね…!
こんな風に使う:
let array_unfoldr ~size init f = let arr = Array.make size (fst (f ~index:(-1) init)) in (* これを Obj.magic 0 で初期化したら酷い目に遭った. *) let acc = ref init in for i=0 to size-1 do let res,acc' = f ~index:i !acc in arr.(i) <- res; acc := acc' done; arr
array_unfoldr は その名のとおり Haskellの List.unfoldrの配列版(サイズ付き)である.引数sizeの前についた"~" がラベル付き引数であることを示している.
しかし注目してほしいのは,ループ内での引数fの使われ方だ. f ~index:i とあるが,これは fの名前付き引数 ~index に i を渡すということを示している. (ここでコロンの意味がオーバーロードされているわけだが,初心者にとってこのコロンの意味がわかりにくいかもしれないが,まあ慣れれば別に気にならない). なんとこれは型推論される!こんな型に推論される:
val array_unfoldr : size:int -> 'a -> (index:int -> 'a -> 'b * 'a) -> 'b array
完璧である. もちろんトップレベル定義の型は推論させるのではなく注釈を書いておくべきだが,さらっとこういうことができてしまうのはOCamlの大きな魅力だ. ただ注意してほしい:型シグネチャにおいてラベル付き引数は "~" を伴わない.混乱するがこれは暗記してしまおう.
追記: (ラベル付き引数の発明者である)ガリグ先生によれば,大元のObjective Lablでは 式レベルでも "~" は無かったらしい.参考:O'Lablの説明
またラベル付き引数は
for index=0 to size-1 do let res,acc' = f ~index !acc in arr.(index) <- res; acc := acc' done
の f ~index のようにスコープに同名の識別子がある場合は省略できる.
オプショナル引数のデフォルト値は
let f ?(x=true) = ..
のように書くのだけど,こちらはなぜコロンではないのだろうとか,ちょっぴり気になるけれど,OCamlゴルフとかで慣れてしまえば別にこまらない.
追記: id:camlspotter さんよりコメント:?x:(x=true) の略記法が ?(x=true) だそう.
閑話その2. 異質な「識別子観」
もうひとつの(ややどうでもいい)壁は OCamlの言語そのものではなく,HaskellとOCamlの文化の違いだ.Haskellでソースコードを読み書きするときのことを思い出そう.Haskellの式はすぐさま数学的対象であり,関数はCPO上の関数の最大不動点とみなせるわけであるからいちいち実行順序とかを考えなくてもだらだら書ける幸せ感が満載だ(←適当に書いたが要は怠惰評価&純粋関数型マンセーってことで). Haskellで記述するソフトウェアは定義の羅列であり,プログラムをマクロにみてもミクロにみても定義の「順番」なんか意識することはない.評価順すら意識しないことだってままある,というかだらだら書いてるときはいちいちWHNFまでの簡約を考えるほうがまれだ.
一方,OCamlでは副作用がどうこう以前に,プログラムとして自然に見えるならば識別子を自由にガシガシ上書きしてしまう文化がある.極端な例だが,
let func x = let x = match x with Some x -> x | None -> 0 in ...
なんて時もある.上ではxが2箇所でかぶっているが型が異なったりスコープが局所的なので別に困らない.だがHaskellianにとっては異質だ(ですよね?).
OCamlでは,eager evaluationなので当たり前だが定義の順序に極めて依存した書き方をする. OCamlはpureではないどころか,既存の他のプログラミング言語とかなり異なる「ML系識別子観」をもっている. せっかくimpureなのだからと,モジュールもロード時に上から下に「実行」される.まったく,普通のHaskellではあり得ない考え方である.スクリプト言語かよとか思ってしまう.いや先輩言語に言うのも何ですけど.
ただし実際には,refやmutableを使わないかぎり破壊的代入がないため,バグを仕込みにくいのはHaskellと同じだ. また,分かってしまえば特に難しいわけでもなく,このような識別子観は既存の手続き型プログラミングの破壊的代入の書き方とぐうぜん似通っているので,(Haskellしか触ったことがない,とかでなければ)使いこなすのに全く支障はない. 最初はキモく感じるかもしれない(他のどんな静的型付け言語にも,関数内で同名のローカル変数を再定義するような文化はないはず…)が,実は大した問題ではなく,すぐに良くなる.
型
型でハマったことはあまりないんですけど こういうお話がありました
まとめ
- Haskellのごとく括弧を省略するとセミコロン周りが他の構文と相互作用して意図しない結果をもたらすことがある
- ワザワザfun x -> ...をカッコで囲むのは面倒です!!
- ワンライナーとか禁止
- OCamlプログラマにとっての変数,定義やスコープといった観念はHaskellプログラマのそれと極めて異なる
入門時点では上記のような問題はありましたが,いまではOCamlは私にとってHaskell以外のどんなプログラミング言語よりもクールでパワフルな言語です.このエントリはHaskellプログラマがOCamlを始めるにあたって非本質的なところで私と同じようなハマりかたをしないように書きました.
これからも両方の言語ユーザーが良い影響を及ぼしあえると良いですね.
モジュールの集約に多相バリアントを使ってみた
OCamlならではの型機能である多相バリアントとプライベート列型を「なんとなく」使ってみた話.タイトルはやや(かなり?)大げさで,小ネタです.
追記: プライベート列型は使わなくて済んだ…
ストーリー
仕事でOCamlを使っている. ボスに命ぜられた通り,数週間をかけて製品の色々な機能をモジュール単位で作り上げた(と仮定する). ここにその成果である金融相場のテクニカル分析モジュール一式がある.
(* テクニカル分析を表現するシグネチャ. 関数はすべて純粋にすること *) module type Analysis = sig type t val calc : raw list -> t (* 生データから計算 *) val recalc : raw list -> t -> t (* 前回からの差分より再計算 *) val series : t -> (datum * time) list (* 時系列で分析結果を取得 *) end;; (* 移動平均 *) module MovingAverage : Analysis = struct type t = ... let calc = ... let recalc = ... let series = .. end;; (* 一目均衡表 *) module Ichimoku : Analysis = struct type t = ... (* 略 *) end;; (* その他のテクニカル分析モジュールが続く… *)
ボスはこう言う:
なるほど. 私はmain.mlで,こう書いた:
最後にmain.mlファイルを作って、そこからアプリケーションを起動するようにすればOK。
(* Main.t型 *) type t = MA of MovingAverage.t | IC of Ichimoku.t | ... (* 現在表示中のテクニカル分析 *) let current_techs : t list ref = ref []
ちょっと気になる…
わりとどうでもいい話だが,…この type t = MA of MovingAverage.t | IC of Ichimoku.t | ... という長ったらしい型宣言が気に入らない.複数のモジュールの型tの直和型を作ることだけを目的に,長く余計な型とコンストラクタを導入している.
これを解消するため(?)に, 多相バリアントと private row type を使って次のようにしてみた.
module type Analysis = sig (*変更なし*) type t val calc : raw list -> t val recalc : raw list -> t -> t val series : t -> (datum * time) list end;; module MovingAverage : Analysis with type t = [`MovingAverage of s] = struct type t = [`MovingAverage of s] (* ↑with type t = … で多相バリアントを露出 *) let calc = ... (* 略.適当に書き直す *) end;; module Ichimoku : Analysis with type t = [`Ichimoku of s] = struct type t = [`Ichimoku of s] (* ↑with type t = … で多相バリアントを露出 *) (* 略 *) end;;
こうすれば,先ほどの Main.t は
type t = [Ichimoku.t | MovingAverage.t | ...] let current_techs : t list ref = ref []
と書ける.割とスッキリしたのではないかと思う.複数のモジュールに分散した多相バリアントのタグを混ぜ合わせて一つのバリアントを作った感じだ.上のとやってることは変わらないが,今度は型Main.tがあたらしく作った型でなく既存のtのサブタイプになっている(→coercionが必要だけど).やったぜ! ちょっとした変更ながら喉の小骨が取れたようですごくスカッとする! …のは私だけかもしれない….こんな多相バリアントの使い方もアリかもね,ということで.
雑感
複数のMLモジュールをいかに集約するか,についてはもう少しちゃんとわかりたいと思っている*1.上の話は実際にはOCamlのオブジェクトを使えばよりすっきりと書けるように見えるし*2 *3,オブジェクト指向言語だとそれ以外に手はないのだが,今回はせっかくMLで仕事をしているのとid:osiireさんがモジュールに関する記事を書いたこともありモジュールにこだわってみたかったのと,あとは宗教上の理由から敢えてそうしなかった. そのうちまた何か書くかもしれない.
多相バリアントは使いこなせればコードの再利用を促進するすばらしい言語機能だと思います.この記事ではほとんど触れていませんが,興味のあるかたは是非使ってみてください.
ocamljs を使おう : OCamlからJavaScriptへの変換
ocamljs は OCamlから JavaScript へのトランスレータだ。前回の記事と順番が逆になってしまったけれど今回はocamljsについて書く (基本的に http://jaked.github.com/ocamljs/ 以外のことは書いてないです。)。
JavaScriptは歴史的経緯とその簡潔さからwebブラウザで使われているが,静的型付けでないので信頼性の面でいまいち不安だったり、言語の使いやすさの面でもパターンマッチの構文が無いとか、まともなモジュールシステムがないとかで良くない。 ocamljsを使えば、比較的安全・簡潔に大規模かつ動的なwebページを構成できるかもしれない。
ocamljsは残念ながらまだ成熟しているとはいえないが、実は既にすごく高機能だ。 現バージョンで既に DOM や jQuery を OCaml から操作できるし、いざとなれば JavaScriptのソースを OCaml に直接記述できる構文拡張も備わっている。他には lwt (light-weight thread) のライブラリと併用できるので AJAX関連のコードも複数スレッドを用いてスッキリと記述できるだろう。 作者自身が開発している froc (関数的にイベント動作を記述できる Functional Reactive Programmingの実装) や orpc (リモートプロシージャコール) も楽しそうだ。 参考:froc の examples
これまでocamljsを使っているかぎりでは、実行効率が悪いと感じたことはない。 ocamljsは OCamlコンパイラがコンパイルの途中で生成するλ式を使って、通常バイナリを生成するところを代わりにJavaScriptのコードを生成する。 このλ式はJavaScriptのfunctionに対応する。 効率の良さの理由かもしれない。
類似のプロジェクトに、obrowserという OCamlが吐くバイトコード(.cmoとか)をJavaScriptで解釈実行する 実装がある。でもおそらく ocamljs の方が効率よく実行するのだろう。またocamljsはJavaScriptのオブジェクトをうまくOCamlのオブジェクトに対応づけるのに成功していると思う。 他には Js_of_ocamlという、OCamlバイトコードからJavaScriptを生成するツールがある。まだ試していない。Ocamljsとのパフォーマンスの差はきっとあるはずだ。
静的型付けの言語からJavaScriptのコードを生成するツールは他に Google Web Toolkit がある。余談になるが GWT を使っているアプリ一覧というのを見つけた。 Google Checkout に GWT が使われているのは、やはりクリティカルな部分に静的型付けで信頼性を担保しておきたかったのだろうか?
雑感
OCamlで書けるようになって一番嬉しかったのはバリアント型とパターンマッチが使えることだった。もちろん型検査があるのも嬉しいのだけど。
今は CSS3のアニメーションやスマートフォンのタッチパネルの機能を使って色々書いているのだけれど、JavaScriptで書いていたときはUI周りのイベントハンドリングが複雑になりがちだった。
でも OCaml に切り替えたら、 バリアントで場合分けして、あるケースにのみ必要なデータをタグに押し込めることで色々とスッキリ書けるようになった。 良いです。
面倒なときは JavaScript のコードを直接OCamlに書けばいい。 Obj.magic で強制的に型変換(ライブラリに必要なCSSのプロパティが定義されていないとか)することもあるけれど、ごく稀だと思う。
ここからもっと詳細↓
インストール
使ってみる: test.ml を test.js にコンパイル
ソース:test.ml
let _ = Dom.window#alert "Hello, ocamljs!"
alert('Hello, ocamljs'); を実行するだけのプログラム。
コンパイル
ocamlfindjs ocamljs -syntax camlp4o -package jslib.inline,dom test.ml -linkpkg -o test.js
ocamlfindjs は findlib の ocamlfind コマンド と同じ。つまり:
- ocamlfindjs ocamljs はコンパイラの呼び出し
- -package dom は Domモジュール を利用するためのおまじない
- -syntax camlp4o と -package jslib.inline は OCaml のコード中で JavaScript を書くためのおまじない
- -linkpkg は JavaScript を吐くために必要なオプション
生成された test.js をFirebugなどブラウザのJavaScriptコンソールに貼り付けたり htmlのscriptタグでロードすれば動く。
JavaScriptとの連携
- 数値型はすべてJavaScriptの数値で解釈される。
- 文字列の扱いは少し厄介だ。 String.create で作った場合に mutableなOCamlの文字列に, それ以外の場合はJavaScriptのimmutableな文字列になる。 (といっても、mutableな文字列なんて私は使ったことがないのでそれほど問題とは思わない。)
- OCamlのオブジェクトはJavaScriptのオブジェクトに対応する(後述)。これが ocamljs の最も素敵なところだ
- OCamlの関数はJavaScriptの関数になる(OCamlの関数をJavaScript側からコールバックできる)。(逆は真ではないような気がする;うろ覚え)
- OCamlの配列はJavaScriptの配列…だったはず… だけど Javascriptモジュール には js_arrayという別の配列クラスもある…
- レコードやバリアントはOCamlの中でしか使えない。
基本的なモジュール
PervasivesやPrintfなどOCamlの標準ライブラリに含まれるモジュールは大抵使える。ほかにJavascriptモジュールはJavaScriptの標準ライブラリをラップしている(私は使ったことがなかった…)。
ブラウザに関する殆どの基本的な操作は Domモジュールにある。例えば alert は Dom.window#alert : string -> unit だ。 あと document オブジェクトは Dom.document (つまり document.getElementById は Dom.document#getElementById) この2つがわかれば後はドキュメントを読みつつなんとかなる…かもしれない。
スタイルシートの属性なんかも一通りstyleクラスに揃っているのであまり困らない。
もし足りない属性をみつけたらOcamljs.fieldref : 'a -> string -> 'b でハッシュとして読み出すか, 既存の class type を inherit した新しいclass typeを作り、 Obj.magic でムリヤリ変換する。
ここでJavaScript側からnullかもしれない値を拾ったらOcamljs.option_of_nullable : 'a -> 'a optionでoption型に変えておくとよい
JavaScript のオブジェクトをOCaml側で触るには
メソッド呼び出し
JavaScriptのオブジェクトに対するメソッド呼び出し o.f(arg1,..,argN) は OCaml で o#f arg1 .. argN になる。直感的だ。
値の代入と読み出し
一方、値の設定や呼び出しは少し特殊で、 o.f の読み出しは o#_get_f となり、 代入 o.f = e は o#_set_f e となる。
メソッドの別名と型付けに関して
この他にも ocamljs において メソッド名の後につく _.*_ は無視される。例えば addEventListener と addEventListener_mouseEvent_ は おなじ JavaScript の addEventListener メソッドを呼び出す。 こうやって1つのメソッドに複数の型をうまく付けている(OCamlではメソッドの多重定義はできない)。
DomやjQuery等でどうしてもうまくOCamlの型がつけられない場合は 'a 型を引数に取ったり #element などの #型が戻り値の型になっていたりして型安全ではなくなっている。これは仕方がない。
ほか
ネイティブな(JavaScriptの)メソッド呼び出しの半適用はできない。ひとつでも引数を与えるとその場で呼び出されてしまう。 つまり引数の数をうっかり間違えても普通にメソッドが呼び出されてしまうので少し危険。 ただこうした引数の不足等はOCamlの場合型検査とコンパイラ警告で全て検出できるので、警告を無視したり自分で ignore とかやってしまわない限りは問題ない。 また半適用は例えば2引数のo#fについて o#f a としたいならば (fun x -> o#f a x)とすれば同じことだ。
インラインJavaScript
面倒なら JavaScript を OCaml のコードに直接書けばいい。その場合、
open Ocamljs.Inline
としてから、 << と >> の間に JavaScript の式を書く(ただし正規表現リテラルなど一部使えない構文がある)。 さらに、 JavaScript の式中で OCaml の式を $ 1. +. 0.1 $ のように書けるので、「〜〜が書けない!」ということはまず起こらない。
例えば、あまり役に立たないけれど、alert関数を自前で書くなら
let alert (s:string) = ignore << alert($s$) >>
となる。 ここで << >> で囲んだJavaScript式は 'a 型を持つのでさっさと型を付けてしまったほうがいい。
続きはまた今度