F# to ocaml(2)
『食べられないために』(みすず書房)なんて言う、翻訳ものの本を読んでみた。副題に、逃げる虫、だます虫、戦う虫ってのが 付いていた。いわゆる昆虫がどうやって捕食者の餌食にならないかってのを楽しく 解説した本だ。こういう事を研究してる昆虫学者や昆虫行動学ってのがあるそうなんで、 どういう役に立っているか想像するだけで楽しい。
おいらは想像力が貧弱なので、せいぜい、農作物の害虫被害を低減するには? 敵を 知るのがいの一番ぐらいしか思い付かない。ああ、もう一つ思い出した。
今年の夏、(多分)神社の森で喰われたと思うんだけど、眼の下が2箇所腫れて腫れて 痛くて痒くてたまらなかった。おいらに噛み付いて、虫さんに利益が有ったんでしょうか? 別においらが、その虫を捕まえて食べようとは思っていなかったんで。こういうの、 昆虫行動学で説明出来るんでしょうかね。
鳥さんは空中を飛行しながら、小さい昆虫を捕食するそうだ。昆虫によって飛行高度が 決まっているので、鳥さんもそれに合わせるそうだ。自由に大空を飛びまわっているかと 思ったら、そういう行動に出ているのね。初めて知りましたよ。
これに対する昆虫側の防御は群れる事だそうです。カマキリみたいに共食いする種では ない限り、有効な手段とか。群れると、捕食者もどれを食べていいか分からなくなる為 群れから離れた、アウトローのひねくれ者を狙うとか。
著者の軍隊経験談が載っていて、2等兵時代、ともかく上等兵に整列させられて、しごき や雑用を仰せつかる事が多かったそうな。そんな時、注意深く観察していると、列の はじっこにいる兵がよく被害に遭ったとか。で、列(群れ)の中心に隠れるようにして 被害を抑えたそうだ。こういうのも昆虫学の応用なんですかね?
家の台所で出くわすごきぶり。家飼いなやつはチャバネゴキブリと言うとか。歪曲的には ウォーター・バグと言うそうだけど、こいつも群れるそうだ。どうやって群れるかと 言うと、群れろ信号が出ているとか。信号というか正確にはフェロモン。いわゆる臭い ですな。臭いを感じる部分は触角。触角を除去すると、集合現象は見られなくなった そうだ。ごきぶりホイホイにもフェロモンを忍ばせてあるんだろうな。
この他、へぇーと驚く奇想天外な戦略が載ってて、非常に楽しめた。お勧め。
F# to ocaml
っつう事で、前回から retro.fsxをocamlに移植してる。前回は、起動すると直ぐに 終了しちゃうBUG(って、自分のミスを隠す便利な言葉)を潰した。これで動くかな?
[sakae@pcbsd ~/src/ml]$ ocamlrun z Fatal error: exception Invalid_argument("index out of bounds")
また、範囲外エラーを喰らいましたよ。前に潰したエラーが復活した? こういう時は 虫取りホイホイだな。
[sakae@pcbsd ~/src/ml]$ ocamldebug z Objective Caml Debugger version 3.12.1 (ocd) run Loading program... done. Time : 322459 Program end. Uncaught exception: Invalid_argument "index out of bounds" (ocd) backstep Time : 322458 - pc : 16224 - module Z 102 | 29 -> ports.(pop ()<|a|>) <- pop () (ocd) p ip ip : int ref = {contents = 7650}
今検証してるzは、ocamlc -gで追跡機能を取り付けたバイトコードです。ocd上から実行 しても、同様エラーになりました。gdbなんかだと通常ここでbt(backtrace)なんてキーを 叩いて、どこまで走ったか確認しますね。
でも、ocdの場合は、backstepなんです。このコマンドを使うと、過去に遡れるっていう 涙がちょちょくれる機能を展開してくれます。尚、上記のセッション中で、|a| なんて のが見えますが、これは、評価ポインターがそこに有りますよっていう目印です。
これを見ると、命令番地が7650でデコード内容(オペコード)29って事で、指定されたポートに値をセットしようとして そんなポートは範囲外って事なんでしょう。続けてリターンキーを叩き 前に入力したコマンドbackstepを実行させて、過去に遡ってみます。
(ocd) Time : 322457 - pc : 18056 - module Z 16 let popVal d () = match !d with h :: t -> d := t; <|b|>h | _ -> failwith "Underflow" (ocd) Time : 322456 - pc : 18048 - module Z 16 let popVal d () = match !d with h :: t -> <|b|>d := t; h | _ -> failwith "Underflow" (ocd) Time : 322455 - pc : 18008 - module Z 16 let popVal d () = <|b|>match !d with h :: t -> d := t; h | _ -> failwith "Underflow" (ocd) p !d $1 : 'a list = [<poly>]
何やら不思議な<poly> なんてのが出てきたぞ。こんなのがports番号の指定に使われりゃ そりゃ、エラーになるよな。所で、この<poly>って何よ。愛読書にも出てこなかったぞ。 調べてみたら、 ocamlでパラメータが<poly>と表示される件 が出てきました。polyはpolymorphicが元らしい。多相はいいけど、省略しすぎじゃありませんか。> おフランスな人。
もう少し遡ってみると、
Time : 322454 - pc : 16208 - module Z 102 | 29 -> ports.(pop ()) <- pop ()<|a|> (ocd) Time : 322453 - pc : 18056 - module Z 16 let popVal d () = match !d with h :: t -> d := t; <|b|>h | _ -> failwith "Underflow" (ocd) Time : 322452 - pc : 18048 - module Z 16 let popVal d () = match !d with h :: t -> <|b|>d := t; h | _ -> failwith "Underflow" (ocd) Time : 322451 - pc : 18008 - module Z 16 let popVal d () = <|b|>match !d with h :: t -> d := t; h | _ -> failwith "Underflow" (ocd) p !d $1 : 'a list = [<poly>; <poly>]
やっぱり、poly; polyだ。本当は何が入っていなけりゃいけないの? 正解を先に調べて おくかな。勝手知ったる、rubyで行こう。
[sakae@pcbsd ~/src/ml]$ ruby -r debug retro.rb Debug.rb Emacs support available. (rdb:1) b process Set breakpoint 1 at retro.rb:process (rdb:1) display @memory[@ip] 1: @memory[@ip] = : (rdb:1) p @stack #<Stack:0x28560d60 @contents=[-1, 5, nil, nil, nil, nil, nil, nil, nil, nil, : nil, nil, nil, nil, nil, nil, nil], @offset=-1>
ふーん、rubyのretro実装では、stackを配列で実現してるんだな。そして、stackのtopを 内部に隠した@offsetが担当してんのね。それはそうと、emacsで画面を2つに割って、片方を shellに割り当て、もう一方でスクリプトを表示させとくと、rdbのポインターが追従して くれる。こんな機能初めて知ったよ。
で、正解は、[-1, 5]が入っているはずね。retroのvm語に直すと、IOポートの5番に、-1を 書き込め。-1ってのは、実装メモリーサイズの要求だ。まあ、LinuxにしろFreeBSDにしろ OSが起動して、最初にやる事は、どれだけメモリーを積んでいるか知る事だもんな。 奇妙奇天烈なウィンテルマシンでは、これが一筋縄ではいかなくて、苦労してるんだな。 ああ、横道に逸れちゃったな。横道ついでに、retroのvmのもう少し先を覗いておく。
1: @memory[@ip] = 29 (rdb:1) p @stack #<Stack:0x28560d60 @contents=[0, 0, nil, nil, nil, nil, nil, nil, : il, nil, nil, nil, nil, nil, nil], @offset=1>
これ、先ほどの結果を読み出そうと準備してるんだな。先へ少し進めると
(rdb:1) p @stack #<Stack:0x28560d60 @contents=[1000000, 0, nil, nil, nil, ...
メモリーサイズが返ってきた。
また実験コード
先ほどのわけわかめな<poly>をもう少し調べてみる。大きなコードに手を入れると、 見通しが悪くなるんで、エッセンスだけを取り出してみる。現役時代、ハードの お守りで、数万行のコードを見る事がしょっちゅうだった。 そんな時は、エッセンスをLP用紙1枚に取り出し(50行以内に再構成)、あれこれハードをつついたものだ。 こんな習性が、体に染み付いているな。
let data = ref [] let pushVal d x = d := x :: !d let popVal d () = match !d with h :: t -> d := t; h | _ -> failwith "Underflow" let push = pushVal data let pop = popVal data let (|>) x f = f x ;;
関係者だけ抜き出してみた。dataってなっている所をaddressに変えたマクロ(みたいなもの)も あるけど、省略。で、これを読み込ませると、
val data : '_a list ref = {contents = []} val pushVal : 'a list ref -> 'a -> unit = <fun> val popVal : 'a list ref -> unit -> 'a = <fun> val push : '_a -> unit = <fun> val pop : unit -> '_a = <fun> val ( |> ) : 'a -> ('a -> 'b) -> 'b = <fun>
型がおぼろげながら見えてきましたよ。ocaml道の入り口に入ったって所ですかね。これも それも、日本のocamlの聖地、名古屋にある名大の 簡便な講義資料 のおかげです。導師様も筑波へ宣教に? 早速、実験開始。
# data ;; - : '_a list ref = {contents = []} # 5 |> push ;; - : unit = () # -1 |> push ;; - : unit = () # data ;; - : int list ref = {contents = [-1; 5]}
あれ、polyが何処かに消えちゃったぞ。dataもちゃんとした型を示しているな。それじゃ、 popも試してみるか。
# pop () ;; - : int = -1 # pop () ;; - : int = 5 # data ;; - : int list ref = {contents = []}
何ら不都合は無いな。しょうがないので、memoryとipを導入して、もう少し現実に近づけてみる。
let ip = ref 0 ;; incr ip ;; let memory = Array.make 16 0xdeadbee
ref型には、インクリメント(incr)やデクリメント(decr)が定義されているので、使って みた。メモリーは、昔懐かしい16アドレスです。(おいらが初めて使ったメモリーは、確か TIが出していたSN75何とかだったな。4bit X 16Addressだったのは、かろうじて覚えている) 初期値は、0xdeadbeeにした。本当は、かのサンマイクロの人に習いたかったんだけど
let memory = Array.make 16 0xdeadbeaf;; ^^^^^^^^^^ Error: Integer literal exceeds the range of representable integers of type int
おフランスから横槍が入って駄目だった。intとint32の微妙な違いが出てますなあ。
# data ;; - : '_a list ref = {contents = []} # memory.(!ip) |> push ;; - : unit = () # memory.(!ip) + 1 |> push ;; - : unit = () # data ;; - : int list ref = {contents = [233495535; 233495534]}
ここまでも、ちゃんと動いているか。ちょっと検証は徒労に終わっちゃったけど、まあ、眼の 付く範囲は白って事で納得して、現実に戻ろう。
遅まきながら、data stackに初めて値をpushする時を捉えればいいんでないかいって気が 付いた。pushValにBPを置いて走らせてみると、
(ocd) run Time : 322397 - pc : 18108 - module Z Breakpoint : 1 (ocd) p d d : 'a list ref = {contents = []} (ocd) p x x : 'a = <poly> (ocd) back Time : 322396 - pc : 14964 - module Z (ocd) p ip ip : int ref = {contents = 9656}
呼ばれた所は、callの実行で、addres stackにipを退避する所だったけど、ここでも 既にpolyになってるよ。初回のpushで、型が決まっちゃうからなあ。はて、どうすべ?
定義だけを#useして、後は手動でやってみるかな。
# load () ;; - : unit = () # data ;; - : int list ref = {contents = []} # 4649 |> push ; 8938 |> push ;; - : unit = () # data ;; - : int list ref = {contents = [8938; 4649]} # ip ;; - : int ref = {contents = 0}
あらかじめ[ヤクザヤ; ヨロシク]なんてしょうもないデータを突っ込んで、型を決めておく。 それから徐に実行。
# exec () ;; Exception: Invalid_argument "index out of bounds". # data ;; - : int list ref = {contents = [8938; 4649]} # ip ;; - : int ref = {contents = 7650}
ふーむ、型違いでstackに書き込めないってエラーにはならなかったね。でも、落ち方は 前回と同様か。しょうがない、print debug するか。えと、pushする所を見てればいいかな。
let pushVal d x = print_int x ; print_endline " " ; d := x :: !d
走らせてみると、
# 9656 -1 7659 5 Exception: Invalid_argument "index out of bounds".
9656とか雑音が混じっているけど、-1が積まれて、次に5が積まれているな。エラー内容を 鑑みると、データの取り出し方が逆になってるな。ええい、こうしてやる!
| 29 -> let p = pop () in let v = pop () in ports.(p) <- v
えらい回り道をしちゃったけど、少しは先に進んだよ。って、事は次のエラーが 発生したんだな。ご名答!
虫は無視できねぇ
次のステップへ行く前に、移植したコードの点検をしてたんだ。そしたら、 opcode29と同じパターンがopcode15にも有ったので修正。これを見逃していたら、 とんでもないmemory番地が破壊される所だったよ。虫は1匹見つけたら、必ず他にも 居るっていう、ゴキブリ習性が思い出されますなあ。本気で、昆虫行動学を学ぼうかしらん。
その他にも、変数名が被っていたりした不注意を見つけた。こういうのは、型を ちゃんと注意してれば、発見出来るのかな?
let ports = 12 ;; let ports = Array.make ports_size 0 ;;
型が変わっちゃうんだから、警告ぐらいは期待したい所。何か、監視を強めるオプション とか有るのかなあ? ocamlc -w +A を試してみたけど、引っかからなかった。
[sakae@pcbsd ~/src/ml]$ ocamlc -i poly.ml val data : '_a list ref val pushVal : 'a list ref -> 'a -> unit val popVal : 'a list ref -> unit -> 'a val push : '_a -> unit val pop : unit -> '_a val ( |> ) : 'a -> ('a -> 'b) -> 'b val ip : int ref val memory : int array val mon1 : int list ref -> unit val mon2 : int list ref -> unit
こういうのを手がかりにするしかないのかな? 虫を見つける良い捕食者になりたいものだ。 そうでないと、虫は 生き延びちゃうからね。
道具作り
今までCPU作りを、半田ごて、半田吸い取り器、(パターンカット用)ナイフ、ジャンパー線さえも 使わずにやってきた。テクトロ風味のオシロスコープは使ったか。製品名はocamldebugと 言ってたな。
でも、ちょいと細部を覗きたいって時、ocamldebugぢゃ辛い。で、stack内容をモニター出来るように ツールを作っておこう。そうすれば、CPUに埋め込むのも簡単。 仕様は、2本あるデータ用、アドレス用スタックのどちらかを選べるようにする事。
自作第1号
let mon1 x = print_string "[ "; List.iter (fun i -> print_int i; print_string "; ") !x ; print_endline "]" ;;
自作第2号
let mon2 rx = let rec scanlist x = match x with [] -> () | car :: [] -> print_int car | car :: cdr -> print_int car; print_string "; " ; scanlist cdr in print_string "["; scanlist !rx; print_endline "]" ;;
以下は、動作テスト。例の本『プログラミングの基礎』(サイエンス社)では、テストを 必ず書けって指導してたけど、下記で、いいでしょうかね?
# mon1 data ;; [ 4989; 596; ] - : unit = () # mon2 data ;; [4989; 596] - : unit = () # mon2 address ;; [1430] - : unit = () # let foo = ref [] ;; val foo : '_a list ref = {contents = []} # mon2 foo ;; []
2号機の方はスクラッチから書いたんで自由度が高く、ocamlのrepl時と同じ結果に なってるけど、一号機は、手抜きしてList.iterなんて既製品を使っちゃったから ちと、醜い。折角なんで、List.iterを作りを確認しておく。FreeBSDでは、これらは /usr/local/lib/ocaml内に置いてある。
let rec iter f = function [] -> () | a::l -> f a; iter f l
下記がlist.mliって説明書。説明の方が長いじゃん。あ、ocamldocの原稿にも なってて、これから、html版なんかも作成出来るんだな。ほのかに、コメント マークが微妙になってる。
(** {6 Iterators} *) val iter : ('a -> unit) -> 'a list -> unit (** [List.iter f [a1; ...; an]] applies function [f] in turn to [a1; ...; an]. It is equivalent to [begin f a1; f a2; ...; f an; () end]. *)
再びpolyの件
いろいろやったけど、(最初から let data = ref [0] として、intを強要しておくとか、 let (|>) (x: int) f = として、土管の入り口でintを強要するとか)、ocamldebugの 元では、どうしても、polyになっちゃう。
これ以上の悪あがきはやめて、必要な所に、mon2 data なんてのを埋め込んで 監視する事にしましたよ。最初からprint debugしとけば余計な時間を取られな かったね。まあ、これも良い経験ですよ。
究極は、オシロスコープ(ocamldebug)の回路図(ソース)が公開されているんで、読んで みるとか、用途に合うように改造しちゃうとかってのが、残ってますけどね。
おフランス製とは言え、原子力産業みたいに秘密主義じゃない所が救われますね。 自助努力で何とかなるOSSの世界なんですから!
fun OSS