takt式マニアックス②
今日のテーマは takt 式のデバッグです。デバッグといったらやはりプリントですね。開発途中でコードに自信がない時にも、メンテナンスで不具合調査の時にも、プログラムの中間結果をプリントすれば大部の悩みが消えるという方法ですね。FLEXSCHE の takt 式でもそれが使えます。
早速参りましょう。
スクリプトレットを用いてメッセージパネルに出力
FLEXSCHEの標準機能では、独立した「コマンド実行」メソッドを用いればメッセージパネルに出力することが可能ですが、takt 式からだと現状難しいですね。
ただし、救いがあります!
バージョン20の新機能「スクリプトレット」は FLEXSCHE の COM モデルに完全にアクセスができ強力な仲間です。今度の実装では、その「スクリプトレット」を使い、プロジェクトからメッセージパネルのオブジェクトを取得し、AddString メソッドで出力するという流れになります。
以下の計算式を実行することでメッセージパネル「一般」タブに Hello FLEXSCHE という内容が表示されます。
Bool.Script(@"
var mp = _arg1.Panels.MessagePanel; //メッセージパネルを取得
mp.AddString('general', _arg2); //AddStringメソッドで出力する
return true;
", Project, 'Hello FLEXSCHE')
計算式ライブラリで関数を登録
先程の計算式は書き込んだ Hello FLEXSCHE という文字列を出力しましたが、任意の文字列を出力したい場合は変数化が必要です。そこで計算式ライブラリの出番です。
計算式ライブラリでは、普通のプログラミング言語と似たように、 名前・引数・コード(takt 式)を登録することで、他の takt 式からの呼び出しが可能になります。
今回は先に Print という名前の関数に str という文字列型の引数を定義すれば、 後は書き込んだ 'Hello FLEXSCHE' を $str に置き換えるだけで済みます。
Bool.Script(@"
var mp = _arg1.Panels.MessagePanel;
mp.AddString('general', _arg2);
return 1;
", Project, $str)
使う時に、以下の takt 式用いればメッセージパネルに出せます。
_Print('任意の文字列')
とは言っても、ここまで来てピンと来ない方もいるかもしれませんね、その Print 関数を使ってなにが嬉しいかと。 なのでこれからマニアックポイントで見せたいと思います。
takt 式では再帰関数をデバッグすることもできますと。
再帰関数のデバッグ
前回 takt式マニアクス①のフィボナッチ関数をあげましょう。
$fibonacci := (Long n)[$n <= 1 ? $n : $fibonacci($n - 2) + $fibonacci($n - 1)]->Long
そのフィボナッチ関数の呼び出し関係を Print 関数を用いてを右図のようにメッセージパネルに出したいと思います。
同じ仕組みを使えば、どんな再帰関数においても、 再帰呼び出しの関係がはっきり見えるので、実装や不具合を調査する際に肝心な変数の値をプリントすれば全体のプロセスもが把握でき楽になるでしょう。
さて、実装に入りましょう。
よく観察すると、
「 c 階層目で呼び出された関数は c 回のインデントをする」
という要件がわかりますね。それを実現しようとすると以下二点がポイントになります。
- インデント c 回をプリントする機能
- 関数に階層数を知らせる機能
まずインデント c 回をプリントする関数を作りましょう。
計算式ライブラリで $c という整数型の引数を持つ PrintIndent 関数を定義し、ロジックの部分に以下のコードを入れれば完成です。
LongList.MakeInRange(0, $c - 1, 1).ForEach([_Print('| ')])
問題は階層数を知らせる機能ですね。どいうふうに再帰的に実装すれば良いかを考えましょう。
入口である fibonacci(5) は一階層目で、その中で呼び出される fibonacci(3) と fibonacci(4) は二階層目になります。ただし、戻り値もプリントしたいので、fibonacci(5) でプリントする際にまた一階層目に戻らないといけないです。
fibonacci(5):
一階層目で
fibonacci(5) の変数をプリント
fibonacci(3):
二階層目で継続
fibonacci(4):
二階層目で継続
一階層目で
fibonacci(5) の戻り値をプリント
一般化すると手続きは以下になります。
- 関数に入ってすぐ階層数に応じたインデントと変数値をプリントする
- 階層数を1で増やす
- 再帰呼び出しする
- 階層数を1で減らす
- 階層数に応じたインデントと戻り値をプリントする
本番で実装する際に、フィボナッチ関数を拡張し、その中用意した Print 関数と PrintIndent 関数をそれぞれ二回使えば以下のコードになります。実行するとメッセージパネルに先頭の呼び出し関係図が表示されますね。
$c := 1,
$fibonacci := (Long n)[
_PrintIndent($c), _Print('n: ' + $n.ToString + String.LF),
$c := $c + 1,
$res := ($n <= 1 ? $n : $fibonacci($n - 2) + $fibonacci($n - 1)),
$c := $c - 1,
_PrintIndent($c), _Print('return: ' + $res.ToString + String.LF),
$res
]->Long,
$N := 5,
_Print('fibbonacci(' + $N.ToString() + ')' + String.LF),
$fibonacci(5),
_Print('end' + String.LF),
色んな使い道
FLEXSCHE は様々な場面で takt 式が使えて高い柔軟性を持ちますが、式が複雑になって不具合調査などが大変になる場合もあります。特に資源選択のソート式や探索基点・再探索基点といったスケジューリング処理ループの中にある takt 式の場合、膨大なデータ量の原因で調査作業量が多くなりかねません。その時、関心のある変数の値を一気にメッセージパネルに出した方が全体像が見えて効率が良くなるでしょう。
次回
今日デバッグしてみた Fibonacci 関数の再帰実装をよく観察したら、そこには n=2 やn=1 の関数が何回も呼び出されて、実に効率が悪そうですね。その改良案と実装は次回で取り込む予定です。お楽しみに。それでは。