Upload
-
View
1.079
Download
3
Embed Size (px)
DESCRIPTION
とても簡単なプログラミング言語Robotのインタープリタを、 末尾再帰インタープリタとして実現した。 その解説。 スタックが非常に少なくても、末尾再帰の無限ループが、永遠に回る。
Citation preview
1
Robot言語と
末尾再帰インタープリタ
2014/OCT/18
たけおか@AXEtwitter: @takeoka
www.takeoka.org/~take/
2
Robot言語
3
Robot言語とは● 再帰的な曲線を描くための言語● 貧弱だが、ピュア関数型言語といえる● タートル・グラフィックスで画を描く● 非常に単純なプログラミング言語● 繰り返し回数を記憶するために、Accが一つだけある● オリジナルのRobot仕様書 & 実装
Litchen Wang:An Interactive programming Language for control of robots, DDJ, Vol.2, Issue 10, pp.60-63 (1977).
● 日本での紹介文書
石田晴久: ロボット言語, bit,Vol10, No.7, pp.35-50(1978))
● たけおかのページ
http://www.takeoka.org/~take/kvm/robj.html
4
組み込み関数など● +
● 1+Accをインクリメント
● -● 1-Accをデクリメント
● h● Home 画面中央部に移動
● n● North 画面上方を向く
● c● Clear 画面を消去
● r
● Rotate 45度右へ回る
● f● Forward 線を引きながら1step進む
● j● Jump 線を引かずに1step進む
● t(<非ゼロ文>)(<ゼロ文>)● Test Accが0でないなら<非ゼロ文>のみを
● Accが0なら<ゼロ文>のみを実行する
● dF(<定義文>)● defun ユーザ関数Fを<定義文>として定義する
● a● Acc項になってAccの値を示す
● このRobotにはAccが一つだけある
● (0-2^31の値を取る)
● ユーザ関数● 英小文字かつ予約されていない一
文字がユーザ関数名として使える。
● dでdefineする
5
実行例● ヒルベルト曲線
du(t(-vfxuyfufxv+)(x))
dv(t(-uyfvfxvyfu+)(y))
dx(6r)
dy(2r)
chna-5+2ru
● 無限ループ
db(z)
dz(frb)
6
実行例● ドラゴン曲線
dx(t(-x6rk+)(f))
dk(t(-x2rk+)(f))
chna-10+x
● シェルピンスキー曲線
dx(t(-xfxufuxfx+)(v))
du(5r)
dv(2r)
dy(6r3r4(fx))
chna-2+y
7
テイル・リカーシブ・インタープリタ(末尾再帰インタープリタ)
8
末尾再帰呼出し→ループ 変換(コンパイル時)● 自分自身の再帰呼出しは、容易に、ループに変換可能● 再帰呼び出し recursive call
int
fact_tail_rec(int n, int a)
{
if(n == 1){ return a; }
else
return fact_tail_rec(n-1, a*n);
}● ループ
int
fact_loop(int n, int a)
{
for(;;){
if(n==1) return a;
a= a * n;
n= n-1;
}
}
9
コンパイラって楽だよね● コンパイル時間が長くても、怒られない
● コンパイル時に、めちゃくちゃメモリを喰っても怒られない● 程度問題はあるが…
● 広域を見れる
● (自己)末尾再帰を見つけるのも、関数のアタマからお尻までゆっくりと見れる
● 実行時間が速ければそれで良し
● 組込みだと、メモリ・フットプリントも重要だが
– コード・サイズ– ワーキング・メモリ(RAM)
10
インタープリタは大変● インタープリタの性能 = 実行時間
● JIT なんていうものも大事か…● でも、JIT やるなら、実行前に機械語に変換しろよ
– 大方の言語なら可能だ– Javaとか、わざと複雑にしてるよな– 古いx86の自己書き換えコードなんか動かなくてもいい
よ(フツーの場合)
11
インタープリタは末尾再帰呼び出しを見抜けるのか?
● Tail recursive call は、コンパイラなら、コンパイル中に
見つけて、静的に変換
● インタープリタって、基本、極めて局所しか見ない● 局所しか見ないで、末尾再帰の最適化はできるのか?
12
末尾再帰インタープリタ● 末尾再帰の最適化を見つけるためのものじゃないよ● インタープリタの構造が、末尾再帰
● Continuation Passing 的なことを、インタープリタ内部で、
インタープリタ自身がやっている
↓● インタープリタが、不要なスタックを消費しない
● テイル・リカーシブ・インタープリタ : インタープリタの実現方法● 実行時に、実行のために明らかに不要な情報を記憶しない。
● 当然のようであるが、素朴な古い実装では、なかなか難しい。
● テイル・リカーシブな実現のインタープリタで有名な言語は、Schemeである。
13
本インタープリタの実現int
exeq() // 1命令を実行する
{
n= term();
switch((ccc=CT[Pc++])){
case 'f' :
x1=x + n*v*step; y1=y + n*u*step;
drawLine(x,y, x1,y1, 0);
x=x1; y=y1;
break;
case '\0' : /* func end */
{ Context *c;
c=stk_pop(); Pc=c->pc; CT=c->ct; top= c->top; n= c->n-1;
if(Pc== -1){ contAble=false;finishIt(); return; // 評価、全終了}
if(n>0){ /* n回 繰り返し中 */
c->n=n;
stk_push(c);
CT=top; Pc=0; //再度、関数の先頭
return;
}
return;
}
:
14
本インタープリタの実現
:
default: /* ユーザ定義関数 */
top= defun[ccc-'a'];
/* 末尾再帰 最適化可能な時は、pushしない
ユーザ関数定義の末尾、すなわち、後に実行すべきものが何もない */
if(n!=1 || CT[Pc]!='\0'){
Context c;
c.n=n; c.top=top; c.pc=Pc; c.ct=CT;
stk_push(&c);
}
CT= top; Pc=0;
//printf(" userFunc %c CT=%x ",ccc,CT);
}
}
15
ここまで● 関係ないけど…
● Lispプログラマ、Prologプログラマ募集中● ルールベースの人工知能 開発中● 機械学習も取り込んだ、ハイブリッドAI● 普通の32bit or 64bitマシンが対象です
16
おまけ
17
古い素朴なインタープリタ実現
● 古い素朴なインタープリタ実現の話をしよう。
18
昔風、素朴なeval
eval(関数)
{
:
関数をどんどん実行する
:
if(関数呼び出しに出会った)
eval(新しい関数);
残りを実行
:
}
19
古い素朴なインタープリタ実現
● 関数実行の本体(中心部)は 「eval」と いう関数● evalが、eval自身を再帰的に呼び出す実現が素朴に行われている● 新しい関数を実行するたびに、
evalが呼び出される。● そして、前のevalに戻 るための情報を、常に記憶● 実行対象のソースコード が末尾再帰になっていても…
前のevalのための情報が必ず記憶され、まったく最適化(節約)されない
● 模式evalで、eval中からevalを再帰的に呼び出す● このeval呼び出しによっ て、
「残りを実行」のための情報を記憶する
● 呼び出しの度に、記憶している情報はどんどん増える。
20
テイル・リカーシブなevaleval(関数)
{
eval_loop:
:
関数をどんどん実行する
:
if(関数呼び出しに出会った){
if(新しい関数呼び出しの後に、仕事はある?){
今evalしている情報を、スタックへ退避
}
プログラムカウンタ=新しい関数;
goto eval_loop;
}
※1
残りを実行
:
※2
if(スタック!=空){
スタックから、情報を戻す
goto eval_loop;
}
}
21
テイル・リカーシブなeval
※2のあたりで、
うまいことeval_loopへgotoできるようにするのがコツ
難しいところかな。
※1の「残りを実行」は
なるべく無くすのが、インタープリタをすっきりさせるためには、
良い
22
テイル・リカーシブなeval
● evalそのものが大きなループになっている● 新しい関数の実行をしても、evalの呼び出しは深くならない。
● 関数の末尾からの関数呼び出しは、何も記憶する必要がない● テイル・リカーシブ・インタープリタは、何も記憶しない。
● 関数が自分自身を呼び出していない時も、最適化される
● 実行途中の関数を継続(再開)するために必要な情報は、
ソフトウェアで作った スタックに格納する。● 当然に、テイル・リカーシブでない呼び出しは、
普通に記憶を消費する。
23
テイル・リカーシブなeval
● 関数の末尾からの関数呼び出しは、何も記憶する必要がない。● テイル・リカーシブ・インタープリタは、何も記憶しない。
↓● テイル・リカーシブ・インタープリタは、
局所しか見ない、単純な構成のインタープリタでも、
末尾再帰ループの場合に、無駄な 記憶を消費することがない
● Prologインタープリタや、コンパイルされたコードを実行する仮想マシンのインタープリタ(実行系)は、
テイル・リカーシブ・インタープリタとして実現されているのが、普通である。
24
Robot言語詳細
25
Robot言語とは● 再帰的な曲線を描くための言語● タートル・グラフィックスで画を描く● 非常に単純なプログラミング言語● 繰り返し回数を記憶するために、Accが一つだけある● オリジナルのRobot仕様書 & 実装
Litchen Wang:An Interactive programming Language for control of robots, DDJ, Vol.2, Issue 10, pp.60-63 (1977).
● 日本での紹介文書
石田晴久: ロボット言語, bit,Vol10, No.7, pp.35-50(1978))
● たけおかのページ
http://www.takeoka.org/~take/kvm/robj.html
26
文法<文> ::= <項><関数> | <項>(<文>) | <文><文>● Robotは<文>を実行します。
● <項>はすぐ後のものの繰り返し回数を表わします。
<項> ::= a | <数> | 「何もなし」● 「何もなし」は1を意味します。
● aはAccの内容を意味します。
<数> ::= 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | <数><数>● 0は2^31を意味します。
<関数> ::= 「組み込み関数」 | 「ユーザ関数」● 「組み込み関数」、「ユーザ関数」ともに以下。
27
組み込み関数● +
● 1+Accをインクリメント
● -● 1-Accをデクリメント
● h● Home 画面中央部に移動
● n● North 画面上方を向く
● c● Clear 画面を消去
● r
● Rotate 45度右へ回る
● f● Forward 線を引きながら1step進む
● j● Jump 線を引かずに1step進む
● t(<非ゼロ文>)(<ゼロ文>)● Test Accが0でないなら<非ゼロ文>のみを
● Accが0なら<ゼロ文>のみを実行する
● dF(<定義文>)● defun ユーザ関数Fを<定義文>として定義する
28
予約語● a
● Acc 項になってAccの値を示す
● このRobotにはAccが一つだけある
● (0-2^31の値を取る)
● :● コマンドであることをしめす。
● コマンドは本Robot独自の機能である。
● Robotの言語仕様にはない。
29
ユーザ関数● 英小文字かつ予約されていない一文字がユーザ関数名として使
えます。● dでdefineする
30
コマンド(独自拡張)● コマンドはKRobot独自の機能である。
● コマンドは実行管理環境(トップレベル)に対する指令である。
● :d● ユーザ関数の全ての定義を表示する
● :n● ユーザ関数をすべて消去する
● :<項>s● f関数で進む距離をドット数で指定する
● コマンドを実行する場合も文を入力した後、 DoItでトップレベルに評価させる。
● 一部のコマンドは、それを評価することによって、PC,スタックなどに影響を与えてしまう。
● よって、一部のコマンド評価後はContはできなくなる。
31
以上