A memo for installing Wodi32 on Vista
I got a trouble to install wodi32 on my Vista box.
bsdtar hang saying:
5 [sig] bsdtar 2428 get_proc_lock: Couldn't acquire sync_proc_subproc for(5,1), last 7, Win32 error 0
The real trouble is, this bsdtar process is a real badass. You cannot kill -9 it. You cannot kill it with the task manager. You cannot reboot your computer since it sits there dead. Only you can do is to force the power down.
I have no idea this is Vista specific issue or a new bug of Cygwin. A workaround is to change wodi's install.sh to use /usr/bin/tar instead of bsdtar bundled in wodi.
Oh btw, I had the same issue with ocamlyacc. I do not remmeber which OCaml distro I used: maybe OCaml on Windows, or Wodi... I do not know.
Maybe it is just the time to buy a new desktop without Vista....
Oh, it seems to be pretty new or rare. Here are what I found on the net:
There are bunch of other get_proc_lock reported but none except the above complains processes go into a zombie.
OutsideIn(X) と OCaml
Haskell の実装 GHC の新しめのバージョンでは 多相let の型付けが今までの HM (Hindley Milner) 方式から新しい OutsideIn(X) に変わっています。(言語拡張でどうたらあるらしいがシラネ) 詳しい動機はまあいろいろあるみたいですが GADT とか Type family の型推論の効率とか完全性とかそういう方面らしいです。正直両方とも使わないのであまりありがたみがわかりません。で、世の中 Haskell のやることは外でも全て正しいという考えの方がおられまして、 OutsideIn(X) は Haskell で問題ないのだから他でも問題が無いはずだとかおっしゃるわけです。
あんまり科学的な態度じゃないですよね。まあプログラミング言語論のこれは便利だ便利じゃないなんて思想であって自然科学じゃあないので究極的には好き嫌いの問題だと私は思うからまあいいっちゃあいいんですが。
というわけで OutsideIn(X) を OCaml でやったらどうなるか確かめました: https://github.com/camlspotter/ocaml/tree/outsideinx
- local let は基本 generalization なし
- let! と書いた場合のみ generalization あり
- top let は generalization あり
- NOOUTSIDEINX という環境変数が定義されていると let でも let! でも generalization あり
という実装です。実装は簡単です。ブートストラップで微妙に手間かかりますので一気にはできません。少しづつブートストラップしていく感じ。
ちなみに OCaml で OutsideIn(X) して何がうれしいというと何もありません。OCaml にも GADT があるじゃないか?うんなんかそれは惹玖が OutsideIn(X) 使わなくても上手くなんかいく方法とかを発表するらしいよ。GADT は使わないんで、正直良くわかんないよ俺は。
さてさて、じゃあ let! がどれくらい必要かということですね。 OCaml コンパイラ一式をこの弱い let と let! で make world 通すのにどれだけ変更がいるかってことでまあ計ります。 find . -name *.ml | xargs grep -n 'let!' だすなー:
./stdlib/camlinternalOO.ml:347: let! undef = fun _ -> raise (Undefined_recursive_module loc) in ./stdlib/arg.ml:110: let! stop error = ./stdlib/printf.ml:488: let! get_arg spec n = ./stdlib/scanf.ml:1328: let! stack f = delay (return f) in ./stdlib/scanf.ml:1329: let! no_stack f _x = f in ./stdlib/scanf.ml:1381: let! stack = if skip then no_stack else stack in ./ocamldoc/odoc_info.ml:211: let! p = Printf.bprintf in ./ocamldoc/odoc_html.ml:509: let! index_if_not_empty l url m = ./ocamldoc/odoc_html.ml:978: let! link_if_not_empty l m url = ./camlp4/boot/camlp4boot.ml:927: let! grammar_entry_create = Gram.Entry.mk in ./camlp4/boot/camlp4boot.ml:10824: let! grammar_entry_create = Gram.Entry.mk in ./camlp4/boot/camlp4boot.ml:12147: let! grammar_entry_create = Gram.Entry.mk in ./camlp4/boot/camlp4boot.ml:14093: let! grammar_entry_create = Gram.Entry.mk in ./camlp4/boot/camlp4boot.ml:14911: let! grammar_entry_create = Gram.Entry.mk in ./camlp4/boot/camlp4boot.ml:15214: let! grammar_entry_create = Gram.Entry.mk in ./bytecomp/translmod.ml:695: let! rec make_sequence fn pos arg = ./typing/typemod.ml:593: let! transition env_c curr = ./typing/includecore.ml:135: let! pr fmt = Format.fprintf ppf fmt in ./typing/typecore.ml:1087: let! bad_conversion fmt i c = ./typing/typecore.ml:1089: let! incomplete_format fmt = ./ocamlbuild/hygiene.ml:157: let! fp = Printf.fprintf in ./ocamlbuild/command.ml:361: let! list = List.fold_right in ./ocamlbuild/rule.ml:255: let! res_add import xs xopt = ./ocamlbuild/main.ml:52: let! pp fmt = Log.raw_dprintf (-1) fmt in
おっこれだけしかない!!そうなんですねー、 OCaml でもローカル let bound な名前が polymorphic に使われることはほとんどないのですね。
OCaml で local polymorphism が使われるパターン:
- 既存の多相型の値の別名をつける: let list = List.fold_right in ...
- 最後に例外を投げるので返り値が多相: let stop error = prerr_endline error; raise Error in ...
- カスタム printf : let pp fmt = Log.raw_dprintf (-1) fmt in ...
OCaml コンパイラでは無闇にこのカスタム printf 系が多い。
じゃあ、あんまりないし、OCaml でも OutsideIn(X) 問題ないよね?!という結論に傾きかけますがそれはどうなんかなとわしは思ったね。
上を見たらわかるんだけど let! が必要なのはほとんどの場合、既存の多相関数に別名をつけているところよね。たとえば let! pr fmt = Format.fprintf ppf fmt とか。OCaml 結構こういう使い方します。Haskell だと一部は import Format(fprintf) とかなるところです。
さらに OCaml は Haskell と違って local なコードは local let にするのが好きな文化です。何故か知らないが Haskell の人は結構外に出しちゃいますよね。Haskell なら外に出しちゃうコードが OCaml では外に出さない文化なので問題出ました、とかありそうです。
OCaml は Haskell と違ってコード中に型をほとんど書きません。(.mli には書くけどこれは実装終わってからおもむろに自動生成する方が普通)なので polymorphic な型書けば元の let になるよ!という GHC の方式は受け入れられないと思いますね。なんでここでは let! と書くことで元の挙動に戻すということをやってみたんですが。それでも、今までの let に慣れている人が、こういう時いちいち let かなー let! かなーとか考えなきゃいけないってのはつらいですね。あと、後から let! つけるの大変ですよ結構。型エラーから読み解くいて let! つけていくの、 OCaml コンパイラのブートストラップで実は時間かかりました。この関数が多相的だったら良かったのにね!みたいな親切なコンパイラエラー出すのはできるんかもしれんけど、多分 let 周りの型付けでこれ多相にできたけど OutsideIn(X) だからあきらめたわーみたいな情報をつけなきゃいけない。すごく手間かかりそうですね…
OutsideIn(X) の論文 http://research.microsoft.com/en-us/um/people/simonpj/papers/constraints/jfp-outsidein.pdf では
- library : 30 packages 94954行で 127行の変更が必要だった
- 793個の Hackage をコンパイルしなおしたら 95個ができなかった(あるパッケージ自体が問題なくても依存パッケージで問題があれば失敗になる、という一番楽なテストみたい)
らしいんでちょっとやってみたんですが
- OCamlFind ですでに修正が必要
- OPAM パッケージは… OASIS が作った setup.ml が入っているとそこで local polymorphism 使っているから自動ビルド絶対無理
ということになり、数字出す前からやる気出ませんね…別に論文書くわけじゃなし。 ちなみに polymorphic variant とか class 使っていると困るかなと思って手で LablGtk2 やってみたんですが 17箇所変更が必要、しかしこれは polymorphic variant も class も関係ありませんでした。結構多いよね。
OCaml に OutsideIn(X) 入れたら既存コードの修正は Haskell より結構大変だと思います。まそれよりなにより OCaml は Backward compatibility を GHC より大切にするから絶対採用できませんね。まあそもそも旨みがないのだから採用する意味も無いが。
金融と OCaml
関数型言語というと金融で流行っている、特に OCaml と言えば金融御用達関数型言語として語られることも多い。金融業界に関数型言語を持ち込み成功した最初の二社 LexiFi とJane Street Capital は共に OCaml を採用し、そして二社とも今日成功しているからだ。
今回はそのうち LexiFi について話したい。私が Ph.D を取った後、一年間、産業ポスドクとしてお世話になった会社である。
金融派生商品(デリヴァティブ)は様々な日時や条件、依存する別の商品価格によって価値が変化する。デリヴァティブは先物やオプションなどのような一般市場でも流通している簡単な派生商品だけでなく、会社間でだけで取引される非常に複雑な物もある。これらの商品にはそれぞれ業界内で通用する名前が付けられ取引、評価、管理が行われてきた。そしてこれらを扱うプログラムもまた、それぞれの商品ごとに別個の機能として実装されてきた。結果としてプログラムやデータ種は膨大なものとなり、その開発やメンテナンスは非常に煩雑なものとなった。例えば金融機関間でこれらの商品のデータをやり取りする規格であるFpML(http://www.fpml.org)はこの実情に合わせた非常に大きなものになっている。
(もちろん FpML に意味がないわけではない…会社間での商品情報の交換には良いだろう。だがモデリングとしては完全に誤ったアプローチである。新しい商品を作ったとして、それが現行の FpML で記述できなければ、FpML 改訂を働きかけなければならないわけだ。そして改定されれば FpML は更に巨大になる。)
これらの金融派生商品一つ一つを見ると様々であり、その仕様も複雑なものもあるが、実はその全ては簡単な人工言語で記述できる。そのことに気付いた LexiFi(http://www.lexifi.com)の Jean Mark Eber は世の中に出回っている金融派生商品のほぼ全てが時期選択や条件分岐などの単純な15個ほどのプリミティブの組み合わせで記述できることを示した。
複雑な数多くの商品を数少ないプリミティブに還元できる、これは大変に強力である。各プリミティブはとても簡単なので、それらの意味や、評価方法は簡単に書くことができ、複雑な商品はこれらの組み合わせから自然に演繹できる。もはやそれぞれの商品ごとに面倒な実装をし直す必要はなくなったのだ。
LexiFi はこの商品記述言語システムを関数型言語 OCaml で実装した。これはまったく自然な選択だった。 http://d.hatena.ne.jp/camlspotter/20131105/1383619506 (そうです。これはこの記事を書くための前座だったのです)でも書いたように、言語を代数的に扱うならばオブジェクト指向言語よりも代数的データ型を持つ関数型言語が適しているし、構成されるプリミティブに第一級関数が使われていたからでもある。
もう少し詳しく話すと、商品記述言語自体は、 OCaml言語ではない。もっと単純なものだ。これは OCaml の代数的データ型として表される。これを組み合わせたり、分解するための操作言語として OCaml を使う。つまり、今流行りの EDSL である。そのころ私達はそんな単語も知らなかったのだが… この EDSL を使って商品記述を解析し、適正な値段をモンテカルロ法でシミュレートしたり、次回支払いの期日を管理したり…様々なアプリケーションがこの OCaml 上の DSL を使ってどんどん書くことができる。
事実、この言語化によるパワー、金融商品の代数的表現を使って LexiFi はデリバティブのモデル化、評価、マネージメントなどの高度なシステムを少人数、短期間で開発し、現在に至っている。
この関数型言語による金融派生商品のモデル化は LexiFi だけでなく現在では複数の大手の金融機関でも採用されている。その一つが今私が働いている銀行である。この仕事場も元を辿れば LexiFi のメンバーからの紹介によるもので私は LexiFi には感謝しきれない。
数多くの対象を一つ一つ個別に扱うのではなく、それらを記述できる単純な言葉を見つけ出し、その言葉の構成子に対して分析やプログラミングを行う…無論これは多くのプログラマー、特に代数的データ型に慣れた関数型言語プログラマーには当然の事なのだが、金融界ではそれ以前にはぜんぜん知られていなかったんだよ、と彼は私に語っている。
問題を上手く言語化できれば問題の九割は解けたようなものだ、と私は考えるようになった。ただしそのためには問題への深い理解が不可欠なのだが…
Risk of Rain
Risk of Rain というミニドットな 2D プラットフォームシューターをちびちびとやっています。面白い。
- 各面の形は毎回少しづつ変化がある(大きいレイアウトは決まっているので迷わない)
- Rogue 系要素。敵はランダムスポーン。時間がたつと強い敵がだんだん出てくる。宝箱のアイテムで自分を強化。経験値。プレーヤークラス。
- 次面に行くには teleporter を活性化させ準備する必要がある。すると 90秒間のボス戦+雑魚大量沸きタイム発生。その後は発生は止まり、全滅させると teleport できる。
強くなるためには宝箱を開けてアイテムを手に入れなければならないが、宝箱を開けるには敵を倒してお金を手に入れる必要がある。とはいえ、ゆるゆるやっていると難易度が上がる。じゃあどんどん先に行けばいいかというと、面数でも難易度が上がるので弱いまま進むわけにもいかない。このバランスが難しい。一番いいのは普通にプレイしつつその面の宝箱を全てゲットするだけの敵を倒して次面へ、なのだけど、 teleporter をオンにすると発生が 90秒後に止まってしまうので丁度良い数の敵発生というのは難しくなっている。
でいろいろ試行錯誤していると自分自身がコツ(プレーヤーキャラの仕様や敵の特性)が解ってくるし、さらに少しづつ便利アイテムが unlock されてくるので俺ツエー感が醸成されてくる。さらに強くなって一時に雑魚集団を爆裂させるとフレームレートがグラディウス的に落ちたりして俺ツエーインフレ感がさらによい。たまにクリアできるようになりました。とはいえボヤッとしていると即死するのでやはり Rogue とか NetHack 的味わい。
以下コツ (初期キャラの Commando でしか遊んでいない)
- 一面は全部宝箱空けるべくちょっと稼ぐ。脱出時 Lv8 位が目安
- 一面以降は無理に宝箱全部探し回らない(多分探し回ってアイテムで強化するよりも難易度が上がるほうがつらい
- 二面からはざっと進行の邪魔になる雑魚を倒しながら teleporter を探し (route A)(大体開始地点とは逆のところにある)、幾つか箱を空けたら teleporter on。 90秒間 route A とは別の道(route B)で逃げつつ箱位置を把握し発生が止まったら、追いかけ系ボスがいれば雑魚の居ないところで倒す。そこから route B を逆進して雑魚を倒しつつ宝箱回収、でお金が残っていれば route A も逆進して宝箱回収、で脱出
- 三面、水中面ならば下部が一方通行ぽいので時計回りに回る
- 四面、この辺りから雑魚を大体無視しつつ teleporter 探索重点で見つけたら即活性化、90秒間もとにかく逃げまくり発生収まってから退治。お金は teleporter がすぐ近くに見つからない限り大体余る
- 五面以降は teleporter を 'a' で抜けると一面へ zap されるので俺ツエーまで回す。'q' で抜けると最終面。体力 1500 もあれば十分か。ただ難易度はどんどんあがる。90~100分辺りで抜けるべき。ただし最終面に進むべきなのに 'a' を間違って押してしまうというのが最大の罠
- 最終面はクリアだけなら扉を開きまくり敵をさけつつ右へ右へと進めばそんなに難しくない。最終ボスはたまに何もせず右往左往するモードにはまるのでそれを利用。途中の蛇二匹の処理の方が難しいかもしれない。レーザーの先と蛇の頭に触れると大ダメージ。だいたい
- とにかく立ち止まらない! ボヤッとしていると自分の真横にテレポートする敵や、誘導弾に一瞬でやられる
- Drone 重要。初期 drone は弱っちいので無視だが、強い Drone をいくつもつれていると逃げているだけでどんどん敵を倒せる。Drone Repair Kit 必須プレイになると 10機くらい drone を引き連れることとなる
- Glowing Meteorite は自分の周囲にも危険が生ずるがかなりのダメージを敵に入れてくれるのでこれも良い。蛇系をはじめとする倒しにくいボスを瞬殺できる
- Jar of Souls は雑魚ダマリに便利
今の好みは Glowing Meteorite >> Drone Repair Kit + Drone いっぱい > Jar of Souls か
大体クリアするときは 100分くらいなのだけど長いのかしらん。
2013/12/06: Acrid で初プレイにして初クリア。70分くらい?強いなあ。最終ボスは Glowing Meteorite で最終蛇x2は楽勝で最終ボスは逃げ回り drone と Meteorite。飛び道具がないから時間だけはかかった。
OCaml がおかしくなったとき
違うバージョンのコンパイラを違うバージョンのライブラリセットに対し間違って使ってしまった
- コンパイラが変われば元のコンパイラで作成したオブジェクトやライブラリは新コンパイラではまず使えない clean せよ
- which ocaml / which ocamlc / which ocamlopt
- ocamlc -where /ocamlopt -where でライブラリディレクトリを確認
- env | grep -i caml で各種ディレクトリの指示先が使っているコンパイラと齟齬が無いか grep -i ocaml ではなく caml であることに注意
- $PWD/.ocamlinit $HOME/.ocamlinit が何か悪さをしていないか
- strace でどのファイルをなめているか見る
- 他人の作ったバイナリディストリビューションは信用しない
言語の実装は関数型言語で、は本当か
表題の様なことがちょっと気になったので、型無λ計算の big step semantics (戦略は正格評価)を実装しました。まあ要するにしょぼいインタプリタですね。
FP でλ計算を実装する
- λ式の型は t (term)
- λ式には整数定数と加減をプリミティブとして追加
- λ式を評価すると value になる(計算止まらない場合はさようなら)
- value には引数を待っているプリミティブがある
open List (** primitives *) type prim = Add | Sub (** terms *) type t = | Int of int | Var of string | App of t * t | Lambda of string * t | Prim of prim (** semantic values *) type value = | VInt of int | Closure of env * string * t | VPrim of prim * int * value list (** Not fully applied primitive *) and env = (string * value) list (** primitive semantics *) let eval_prim prim args = match prim, args with | Add, [VInt i1; VInt i2] -> VInt (i1 + i2) | Sub, [VInt i1; VInt i2] -> VInt (i1 - i2) | _ -> assert false (** big step semantics of terms *) let rec eval env = function | Int n -> VInt n | Var v -> assoc v env | App (t1, t2) -> let v2 = eval env t2 in let v1 = eval env t1 in begin match v1 with | Closure (env, var, t) -> eval ((var,v2)::env) t | VPrim (prim, arity, applied) -> let len = length applied in if arity <= length applied then assert false else if arity = len + 1 then eval_prim prim (rev (v2::applied)) else VPrim (prim, arity, v2 :: applied) | _ -> assert false end | Lambda (v, t) -> Closure (env, v, t) | Prim Add -> VPrim (Add, 2, []) | Prim Sub -> VPrim (Sub, 2, []) (** test *) let () = (* ley y = 2 in let f x = x + y in let y = 100 in f 2 (\y.(\f.(\y.f 2) 100) (\x.x+y)) 2 *) assert (eval [] (App (Lambda ("y", App (Lambda ("f", App (Lambda ("y", App (Var "f", Int 2)), Int 100)), Lambda ("x", App (App (Prim Add, Var "x"), Var "y")))), Int 2)) = VInt 4)
知っている人にはごくごく普通ですね。 まずデータ型を考えて全コンストラクタを列挙。それから関数を書いていく。
知らない?んーとね、関数型言語の凄く簡単なやつのインタプリタを関数型言語で書いたらこれくらいの短いコードで書けますねーということだと思ってください。
ロクにテストしていないがともかくクロージャーはクロージャーとして動いているみたいだ。(let なしでこういう長いλ式書くのは正直体がついていかん歳になりました) eval が末尾再帰でないから気になるという人はご自分で直してください。
OO でλ計算を実装する
でさぁ、これでいやー短くかけますねぇ!関数型言語凄いですねぇ!ねぇねぇ!では自分に対しても説得力無いので、わざわざ今度は同じ物を OO で書いてみます。本論の目的は FP と OO アプローチを比べるものですので、あえて代数的データ型(Variant)は使ったら負け。なので使いません。できるだけクラスで実現します。
open List (** interfaces *) class virtual value = object end class virtual prim = object method virtual arity : int method virtual eval : value list -> value end type env = (string * value) list class virtual t = object method virtual eval : env -> value end (** implementations *) (** values *) class vint (n:int) = object inherit value method int = n end class virtual applicable = object inherit value method virtual apply : value -> value end class closure (env:env) (var:string) (t:t) = object inherit applicable method apply v = t#eval ((var,v)::env) end class vprim (prim : prim) (applied : value list) = object inherit applicable method apply v = let len = length applied in let arity = prim#arity in if arity <= len then assert false else if arity = len + 1 then prim#eval (rev (v::applied)) else (new vprim prim (v::applied) :> value) end (** primitives and their semantics *) class prim_add = object inherit prim method arity = 2 method eval = function | [v1; v2] -> (new vint ((Obj.magic v1 : vint)#int + (Obj.magic v2 : vint)#int) :> value) | _ -> assert false end let prim_add = new prim_add class prim_sub = object inherit prim method arity = 2 method eval = function | [v1; v2] -> (new vint ((Obj.magic v1 : vint)#int - (Obj.magic v2 : vint)#int) :> value) | _ -> assert false end let prim_sub = new prim_sub (** terms and their semantics *) class int i = object inherit t method eval _env = (new vint i :> value) end class var v = object inherit t method eval = List.assoc v end class app t1 t2 = object inherit t method eval env = let v2 = t2#eval env in let v1 = t1#eval env in (Obj.magic v1 : applicable)#apply v2 end class lambda var t = object inherit t method eval env = (new closure env var t :> value) end class add = object inherit t method eval _env = (new vprim prim_add [] :> value) end class sub = object inherit t method eval _env = (new vprim prim_sub [] :> value) end (** tools (OCaml requires explicit upcasting, so things go too verbose) *) let var x = (new var x :> t) let int i = (new int i :> t) let app t1 t2 = (new app t1 t2 :> t) let lambda var t = (new lambda var t :> t) let add = (new add :> t) let sub = (new sub :> t) (** test *) let () = assert ((Obj.magic ((app (lambda "y" (app (lambda "f" (app (lambda "y" (app (var "f") (int 2))) (int 100))) (lambda "x" (app (app add (var "x")) (var "y"))))) (int 2))#eval []) : vint)#int = 4)
OCaml は upcast を明示しなければならず (e :> t)、そして downcast は不可なので (Obj.magic e : t) を 使って無理やり書いていますが、ここは動的なクラス検査コードと思ってもらって良いです。 (実際には検査しませんが)
まず基底クラスを定義して全インターフェースを書く。それから各サブクラスを一つづつ書いていく。 FPでの型がOOでの基底クラス、FPでのコンストラクタがサブクラスと対応します。 値を適用できる値として、closure と vprim があるので、これらのスーパークラスとして applicable という インターフェース(仮想クラス/抽象クラス)を value と closure, vprim の間に定義しました。
長いですね… FP版と比べて 35% 位長いです。
書くのは実際苦労しました。クラス階層をどうわけるのかとか、どこまで method にするか悩みましたが、幸運にもだいたいすっきりクラスに埋め込めました。もちろん慣れてないだけかもしれません。FP アプローチも代数的データ型に慣れてない人にとっては書くのは一苦労だと想像します。
私は両方知っている人にとってはどう考えても代数的データ型の方が判りやすいと思いますが、もちろん※個人の感想です。λ計算の文法やセマンティクスの定義自体、代数的データ型とほとんど外見が同じなので FP アプローチでは写経するだけなので簡単なはずなのですが、もちろんこれは関数型好きな人の※個人の感想です。
OCaml の OO はオカシイと思われる方もいると思いますので Ruby でダックタイピングをふんだんに使って書いてみました。私の初 Ruby プログラムです!:
# values class Vint def initialize(i) @int = i end attr_reader :int end class Closure def initialize(env, v, t) @env = env @var = v @term = t end def apply(v) env2 = @env.clone env2[@var] = v @term.eval(env2) end end class Vprim def initialize(prim, applied) @prim = prim @applied = applied end def apply(v) len = @applied.size arity = @prim.arity if arity <= len then raise else if arity == len + 1 then @prim.prim_eval(@applied + [v]) else Vprim.new(@prim, @applied + [v]) end end end end class Prim_add def initialize() @arity = 2 end attr_reader :arity def prim_eval(vs) if vs.size == 2 then Vint.new(vs[0].int + vs[1].int) else raise end end end prim_add = Prim_add.new() puts prim_add.prim_eval([Vint.new(1), Vint.new(2)]).int class Prim_sub def initialize() @arity = 2 end attr_reader :arity def prim_eval(vs) if vs.size == 2 then Vint.new(vs[0].int - vs[1].int) else raise end end end # terms class Int def initialize(i) @i = i end def eval(_env) Vint.new(@i) end end class Var def initialize(v) @var = v end def eval(env) env[@var] end end class App def initialize(t1, t2) @t1 = t1 @t2 = t2 end def eval(env) v2 = @t2.eval(env) v1 = @t1.eval(env) v1.apply(v2) end end class Lambda def initialize(var, t) @var = var @term = t end def eval(env) Closure.new(env, @var, @term) end end class Add def eval(_env) Vprim.new(Prim_add.new, []) end end class Sub def eval(_env) Vprim.new(Prim_sub.new, []) end end # tools def var(v) Var.new(v) end def int(i) Int.new(i) end def app(t1, t2) App.new(t1,t2) end def lambda(v, t) Lambda.new(v,t) end add = Add.new() sub = Sub.new() test = app(lambda("y", app(lambda("f", app(lambda("y", app(var("f"), int(2))), int(100))), lambda("x", (app(app(add, var("x")), var("y")))))), int(2)) puts test.eval(Hash.new).int
Ruby では cons list がよく判らなかったので Hash を clone していますがまあやりたいことは大体書けています。 バイト数で OO にして型か書かれまくった OCaml コードの 9割位ですか。思ったより縮みませんね。 (行数で比べるほど心の狭い男ではありませんよ私は) まあ、もっと短くなるんでしょうけど、OCaml の OO アプローチは型がたくさん出てきて読みにくかったので書いてみただけで、言語disする目的ではないんでその辺はどうでもいいです。 動的なチェックは明示された動的なクラスチェック(OCaml 版では (Obj.magic e : t) に相当)がなくなり、 暗黙のダックタイピングに置き換わっています。このコードでは OCaml OO 判の影響を受けているので静的型付けの心理学に毒されていますね。真に静的型付け心理学から自由な Rubist ならば VInt と Int は Integer に、 Var は String にして eval メソッド追加してコード量を少し減らせます。私は※個人の感想として、そんなことしちゃあ絶対いけないと思うんですが。
比較してみましょう
客観的には明らかに FP の方が短い。短けりゃ偉いのか、という問は常にあるが。短い。 OO で(代数的データ型なしで)短く書くのはなかなか難しそうです。※個人の感想です
それ以外はもう Expression problem な〜 な感じで出尽くしていますねえ。 http://homepages.inf.ed.ac.uk/wadler/papers/expression/expression.txt
色々主観はあると思うんですが、餅は餅屋ですね。
プログラミング言語の抽象構文木のような、そもそも代数的に定義されている対象はやはり代数的データ型で書くのがよい。わざわざクラスで書く必要は無い
という当たり前な※個人の感想ですの再確認となりました。
そうなると自然と代数的な対象は、代数的データ型を標準装備し そのツールが揃ったた言語で書くのがよろしいという※個人の感想になります。それが、ごく、たまたま…関数型言語であった、そういう状態にあったと認識している、という※個人の感想。え?あなたの好きなOO言語には代数的データ型がある?んじゃそれ使えばいいんじゃないですか。※個人の感想です
そういう※個人の感想でした。
星のキャミバ様 Adventure Calendar 第603~605夜: ペナンに行ってきた
これは 2013年7月の旅行記です。
(面倒なんで東南アジア方面用語は解説しません。ググッておくれー)
うちの息子の毎度曜日のスイミングが先生の夏休みで二週間なしになりました。で、急に旅行してみようということになりました。アンコールワットとか見に行こうかとも思いましたが、急に旅行というとなかなかプラン立てづらそうな重量級ターゲットなのでパスして、近めでちゃっと行ってちゃっと帰れるところにしようということになり、シンガポールから直行便のあるペナン (http://ja.wikipedia.org/wiki/%E3%83%9A%E3%83%8A%E3%83%B3%E5%B7%9E) にしました。
ペナンは檳榔島とか檳城とか書かれますが檳榔樹の産地だったようです。ペナン州の旗にもこの椰子科の木の絵が描かれていますね。ペナン博物館にも檳榔セットが飾ってありました。一度やってみたいものですが、ペナンでもシンガポールでも見たことはありません。台南とか行けば薄着のお姉ちゃんが売ってくれるみたいですね。えへへ
急に決めたのであまり考えがいかなかったんですがマレーシア方面はこの一週間スマトラ島の焼畑煙害のヘイズがまたひどくなり煙かったそうです。で、わざわざ金曜有休取って煙いとこ行くことになってしまったか…とちょっと憂鬱だったのですけれど、木曜日に雨が降ってヘイズは無し!幸運にも日曜日までぜんぜん問題ありませんでした。
ペナンというとジョージタウンという旧市街の西華馬印文化融合地帯と島北岸のビーチリゾートが有名。私たち三歳の子連れで、もし雨が降ったりしたら…と考えて、子供向けのプレイグラウンドやプールのある北岸 Batu Ferringhi のまあツマンナイビーチリゾートホテルに泊まりました。
Batu Ferringhi は海岸良かったですよ。水に透明感はないが、なかなか綺麗な浜がドドーンとあり、ホテルの人たちは大体プールでちゃぷちゃぷやっているので空いている、特に午前中はほぼ無人。マリンスポーツのセールスも淡々としているしウザクない。息子は人の多いプールより波打ち際をワーッと走るのが気に入ったのでずっと付き添いで遊んでおりました。海水がしょっぱいのを知らなかったので驚いていた。私は日焼けで疲れたよ。まあ子供はベッド寝かしたら一瞬で寝ましたから助かりましたが。家族連れだとこういうところが楽なんだなあということがようやく私らもわかりました。客層は多国籍。日本人客も多し。Batu Ferringhi の問題は飯がまずいってことかな…
一日目の夕方は Gurney drive のショッピングセンターをちょっと冷やかして、そばのホーカーで夕食。ショッピングセンターはシンガポールとあまり変わらないが、ただただ広かった。ここのホーカーは青天井。晴れていて気分が良い。
ここのホーカーのシステムはシンガポールのに慣れているとちょっと戸惑いました。シンガポールはどこの席に座って何食っても構わないんですが、ここ(少なくとも Gurney)のはテーブルにメニューが置いてある。で、テーブルに屋台の番号が書いてある。どうやら屋台が持っているテーブルらしくって、そこのメニューが置いてあるわけ。豆乳屋とかの。どうやらまあ、そこに座るならそのテーブル持ってるとこのドリンク買ってね、ということみたい。確かに豆乳とか一杯1.6RMとか50円しないんで場所で縛りいれないときついかもなあと思いました。で、まあドリンク買えばほかの屋台の物を買ってくるのは当然自由。
飯は旨いんだよーこれがー。特にペナンラクサてのはシンガポールラクサと違うというのでぜひ食べようと思っていたんですが、"Penang Laksah" って書いた屋台はない。まあ日本の蕎麦屋で「日本蕎麦」と書いてあるところがないのと同じ。で、"Asam Laksah" て書いてあるところで行列がある店で買ったら当たりでした。ペナンラクサは鯖とかの出汁で Asam つまりタマリンドで酸っぱい味付けがしてある。少しだけ辛い。ココナツミルク風味の辛い辛いシンガポールラクサとはまったく違いました。そのほか雲呑麺。牡蠣オムレツ。ロジャック(Penang Rojak: 何故か半生イカの入ったフルーツ野菜サラダ+デロデロソース。食ったことない人には正直説明できない)など取り全て落ち着いた味付けかつ、具もいろいろ入っているし香草などのシーズニングもしっかりしており、シンガポールのホーカー料理に慣れていると「手を抜いていない」感に圧倒されます。大体値段は 3.5RM。くそ安い。ただし量は全体的に少なめなので、いろいろここの屋台からこれ、こっちからはこれ、と、食べる感じになります。それもいいね。
息子はこのところチェンドルのファンなのでチェンドルを与えました。で私たち夫婦は美味しいもの食べると余計物を食べたくなる人種なので、何かご飯物を…と思いましたが無いのですねなぜか。ほとんど無い。麺類ばっかり。咖喱…と看板に書いてあるからおっと思ってみると咖喱麺だったりする。おやぁと思いつつ屋台を縫って進んでいくとちょっと切れ目っぽいところがあって、その向こうがムスリム系屋台の場所でした。ここはご飯がある。Nasi Lemak とか。さらに Nasi Tomato とか Nasi Kandar とかシンガポールでは見たことの無い物があり、気になりましたが、一番端っこで鳥を大鍋でガンガン揚げている Nasi Lemak Ayam の店に行列があったので、そこで Nasi Lemak + Ayam(鳥揚げ)をもらいました。なんかペナンの人達は食べたい鳥唐揚げを自分で選んでもいいらしい。なんで鳥唐揚げを一つトングで取り上げてウーンこれは骨が多いからこっちにしよ、とかノンビリやっている。
奥のほうでチキン選んでます。米のお櫃の上に一緒に炊いた葉っぱが置いてある。
で周りを見るとムスリム食ゾーンは皆さん食べ物を机の上に置いているが食べていない。ただだべっている。ああ、そうだラマダンだったのでした。日没までは彼らはご飯を目の前に置いて我慢するのです。面白いのはムスリム食ゾーンはほんとにムスリムの人たちだけで中華系で勝手にバクバク食べている人はいなかったってことかな。シンガポールでは Geylang Serai のホーカーとかに行くとこういうのを見れますが非ムスリムもいたと思います。(なかなか我慢している人の横でパクパク食えるものではありませんが)幸い、私が Nasi Lemak Ayam を待っている間に日没が来て皆さん美味しそうにご飯を食べ始めました。で、これ旨かったです。鳥唐揚げは揚げたてで多分衣に何かちょっとスパイスが入っている。サンバルは凄く軽く作ってあって辛味の他にもトマトっぽい味もある。パクパク食べてしまいました。
二日目。Batu Ferringhi から George town に出ていろいろ見学に行きましたが、ホテルが出している無料シャトルバスが満員…なので公共バス101に乗って行きました。一人3RM(<100円)しないくらい。バスは George town 方向に行くって事がわかってただけでどこで降りたものかわかりませんでしたが、事前に Google Map をオフラインで使えるようにしておいたので、通りの名前とにらめっこしつつなんとかなりました。(私の携帯の GPS 効きが悪いんですよね… Wifi access point 使わないとぜんぜん感度が出ない。そして外国なのでそれは使えない。)ここか!と思って降りたら潮州會館の前でした。ちなみにタクシーだと40RM。ここのタクシーはメーターがあるのにメーターを使いません。ゾーン区分けになっていてそれで値段が決まるらしく行き帰りいつも同じ値段を言われましたから、ぼっている感じは皆無でした。で車が混みます。北岸から George town まで、特に Tanjong Bungah から Gurney の間がボトルネックになっていて凄く混みました。
バス、タクシーの運転手も町の人も英語はシンガポール人より聞きやすい。下手に国語になってないからでしょうか。妻はマンダリンでも少し話していた。
George town で見学したのは
- 潮州會館 http://wikimapia.org/3438883/Teo-Chew-Association%E3%80%80%EF%BC%88%E6%BD%AE%E5%B7%9E%E6%9C%83%E9%A4%A8%EF%BC%89
- 観音寺 http://wikimapia.org/#lang=en&lat=5.353521&lon=100.362854&z=11&m=b&permpoly=12187657&show=/288525/
- ペナン博物館 http://en.wikipedia.org/wiki/Penang_Museum_and_Art_Gallery
- ペラナカン博物館 http://wikimapia.org/#lang=en&lat=5.417231&lon=100.340495&z=18&m=b&show=/7379192/
潮州会館では地元の御婦人と少しお話をしました。息子さんを亡くされて線香をあげに来られていたところだとか…子供がいるとこういうお話聞くのつらいですね。
観音寺では皆さん熱心にお祈りしていました。息子は蓮の花の蝋燭を「どうぞ」したいといって聞きませんでしたので一つ小さいのを買ってお供えしました。なんと蝋燭や線香だけでなく、灯明の油もお供えするのですね。300ml入りくらいのボトルを買って、灯明が浮かんでいる油の器に皆さん注いでおりました。(芯が無いと火がつかないから安全みたいです)
ペナン博物館は、まあシンガポールのアジア文明博物館を知っていればあまり行く必要も無かったかと思いますがペラナカン文化に興味を持っている妻は何か資料の写真を取っていました。
ペラナカン博物館に行く前に、その横の Kafeteria Dan Hotel Eng Lo http://wikimapia.org/#lang=en&lat=5.417728&lon=100.341804&z=17&m=b&permpoly=3438883&show=/14073053/ というまあ街食堂で昼御飯食べました。これも旨かった!前日に見た咖喱麺があったので注文したらシンガポールで言うラクサでした。といっても具材が多く辛すぎず…シンガポールラクサももっと手を抜かないで欲しいなあと思ってしまいます。この建物は日本的な観点からはまあキッチャナイ建物なんだけど、どうやら建築様式としてある時代の標準的は造りでそれなりに価値のある建物のようでした。
ペラナカン博物館は…これは凄いですね。もうなんというかここ行くだけでペラナカン分満腹です。ペナン行ったら是非見に行ってください。家の真ん中が吹き抜けになっていて間接光が心地よい。西洋風応接間は壁は中華風があったりして、昔の祖父母の家の応接間の和洋折衷を思わせました(もちろん規模はまったく比べ物になりませんが…)ペラナカンの人達が華洋折衷するのと同じ心なんだなあと。ペラナカン博物館は実は奥が結構広くフルチケット(20RM)を買うとどんどん奥まで進めます。氏族のお寺への入り口が判り難いので注意。一番奥の売店で本やペナンの町の写真を購入。息子はかなり退屈して「新しいおうち(ホテル)帰るー」連呼だったのでもう少し落ち着いて見学したかった。
で、観たのこれだけ!です。 The Blue Mansion も Khoo Kongsi も観れなかった…宿は北海岸だし、お子様がいるので…残念なのでアルメニア通りを歩きつつ中山会館(孫文の居たところ)を通り過ぎ、 Comtar でタクシーに乗り北岸へ戻りました。途中いくつも旨そうな町食堂があって胃袋がいくつも欲しかった。
ナツメグジュースです。結局現地では飲まず、シンガポールのペナン料理店で飲みました。$て書いてあるけど単位はRMです。
三日目。荷物があるのでチェックアウト後 George town に行ってまた戻ってくるわけにもいかないので西側の Butterfly farm とホテル前の海岸など。帰りもまた渋滞。北岸で泊まって空港に向かうときは時間を多めに見積もらないと大変です。最終日は George town に泊まるのが良いかもしれませんね(そこからはあるハイウェイがあるので)。タクシーの運ちゃんは無線で仲間と、「どこどこ Jam、どこどこ Clearer」とマレー語で情報交換しつつ、山沿いのうらびれた福建華僑墓地内や村道(日本と田舎と同じで昔はバスが通っていたけど、本道ができてバスの待合だけのこった、みたいな雰囲気のところ)を縫うように走って空港まで連れて行ってくれました。藤原新也のアジアの写真に出てくる痩せた犬が村でブラブラしたりしてるのが見れて、ちょっとしたペナン Kampung ライフが垣間見れてこれは面白かった。今地図で見ると Jalan Mount Erskine から西に入ったとこらしい。
で早めについてしまったので表示に我々の乗る飛行機の表示がなし。で早々とチェックインを済ませて、ブラブラとご飯を食べたり(ムスリムの人がまた日没を待っていた)コーヒー飲んだりしてから出国していたら、アナウンスでもう出るみたいなことを言われました。これがよく響いてぜんぜん聞こえないんですよ。で、改めてチケット見ると Boarding がえらく早い。急いで搭乗口に駆けつけたらなんと予定より20分ほど早く飛行機が出てしまいました。なんか二時間前チェックインでそろっちゃったらちゃっちゃと離陸してしまうみたいですね…初めての経験なのと、シンガポールの結構 on time な国際線経験に慣れていたので、びっくりしました。
ペナン、日本からの直行便がないけど、いいですよ。僕らがシンガポールで好きな部分、ショップハウスとか、ペラナカンとか、安食堂とかが凝縮されて、ノンビリした感じ。 George town もぜんぜんまだ見れていないし、他にも御飯いろいろ食べたいし、また必ず行くと思います。