SICPの図形言語を JavaScript+html5のcanvasで解く
SICPの2章に図形言語というセクションがあって、環境を用意するのが面倒なのでスルーしていた(通常coLinux+gaucheとかDrSchemeとかでやってるのだけど、描画のためにLinux側にgauche+GL、Windows側にXサーバを入れて窓をXで飛ばすとか面倒だし、これ以外の目的に使いそうにない)。
しかし、canvasを使って本文中のSchemeプログラムをJavaScriptで記述してやればできるんじゃないか?と思ってやってみた。
なお土日は本当は月曜SICPのためにambインタプリタを書かなければいけなかったのだが、図形言語にかまけていて予習をやってない。でも単に遊んでいたわけではないんだよというアリバイ工作のためにこのような記事を書いてみる。
SICPではSchemeを使うとはいっても、マクロとかは取り扱っていない。SICPの範囲内で使われるScheme処理系の機能で、JavaScript処理系が持ってないのはquoteくらいのものである。図形言語のセクションではquoteは導入されていないので、ほとんど一字一句そのままJavaScriptに写像可能である。ただ、JavaScriptとしての記述の簡潔さのために、簡易なデータ構造の構築子/選択子の組はJavaScriptのオブジェクトを使うことにした。たとえば次のように。
//ベクトルの構築子&演算 //構築子は make_XXX のようなプリフィックスをやめる。 function vect(x, y){ return {x:x, y:y} } //選択子は 単にオブジェクトプロパティのアクセスとする function add_vect(a, b){ return vect(a.x+b.x, a.y+b.y) } function sub_vect(a, b){ return vect(a.x-b.x, a.y-b.y) } function scale_vect(s, v){ return vect(s*v.x, s*v.y) }
あとは、適切な箇所にreturnを自分でいれてやらなければならない程度で、ほとんどSICPソースそのまま移植可能だ。こんな具合に。
//フレーム内へベクタを写像 function frame_coord_map(f){ return function(v){ return add_vect( f.origin, add_vect( scale_vect(v.x, f.edge1), scale_vect(v.y, f.edge2))) } } //線分を描画するdraw_line手続きが所与であるとして、線分のリストを描画するペインタを返す function segment2painter(ls){ return function(f){ var map = frame_coord_map(f); for( var i=0, len=ls.length; i<len; i++ ){ draw_line( map(ls[i].start), map(ls[i].end) ); } } }
canvasのAPIに依存するのは、絶対座標により線分を描画するとか、(アフィン変換+平行移動した上で)画像を描画するとかのプリミティブな処理くらいだ。
var ctx = document.getElementById("canvas").getContext("2d"); function draw_line(start, end){ ctx.beginPath(); ctx.moveTo(start.x, start.y); ctx.lineTo(end.x, end.y); ctx.stroke(); } function draw_image(id){ var img = document.getElementById(id); return function(f){ var w = img.width; var h = img.height; ctx.setTransform( f.edge1.x/w, f.edge1.y/h, f.edge2.x/w, f.edge2.y/h, f.origin.x, f.origin.y); ctx.drawImage(img, 0, 0); ctx.setTransform(1, 0, 0, 1, 0, 0); } }
こんな感じ。あとは実際に動いているところでご確認を(IEでcanvasを使えるようにするライブラリは入れてないので、モダンなブラウザでどうぞ)。
この図形言語抽象はなかなか面白いなと思った。もうちょい凝ったこともできる。それはまた今度。