計算言語4(マクロで言語を補強する)
このページはマス旅の一部です。
今回はミニプログラミング言語Mでマクロを作ろう。
最小限のことしかできないミニプログラミング言語M
があるとしましょう。
言語Mができることを列記する。
(宣言D)変数、手続きの宣言。
(実行C) 演算、条件文、手続きの呼びだし。
(プログラムP)実行Cまたは、宣言D列と実行C列をまとめて実行する。
(変数宣言) (def X)は、変数Xの宣言でX=0となる。
(手続き宣言)(def (f)P)は変数fの宣言されて手続きPが実行される。
(条件文)(if (nzero? X) P1 P2 )は、XがゼロでなければP1を実行、そうでないならP2を実行。
(ループ文)(begin (def (loop) (if X Y Z) (loop))は、もしXが真ならば実行Y、そうでないなら実行Z、を続ける。
(演算)(add1(X))はXを1増やす。(sub1(X)はXを1減らす。
この言語Mにはこの定義でできないことがあるね。
・手続きを呼び出すときにパラメータを指定できない。
・変数に他の変数をセットすることができない。
このような普通には付属している機能を
標準機能を使ったプログラムで実現するものを「マクロ」という。
では、「マクロ」の実例を作ってみよう。
課題1:Xの値を0にするマクロ(clear X)を作ろう。
言語Mでこれを作ると、次のようになる。
(begin
(def (loop)
(if(nzero?) (begin(sub1)(loop)) (begin)))
(loop))
このプログラムに名前clearをつけると
clearを言語Mのコマンドとして使えるようになるね。
X=5は、まだ使えない。
(def X)(add1(X))(add1(X))(add1(X))(add1(X))(add1(X))
これで、Xは5になる。
X=0は変数初期化で、そうなるだけで、
一度x=5になったら、X=0には一瞬ではできない。
だから、clear Xというコマンドなり、
一連の実行が必要になるということだね。
geogebraには字句解析と構文解析機能はないので、
言語Mをそっくりマネすることはしなくてよいね。
ただ、X=5をX=0に上書きする機能がないとして、それを実現しようということだ。
<Iterationのマニュアルから>
繰り返しの足し算:3への7の繰り返し加算を求めるには, g(x) = x + 7 と定義し,
Iteration(g, 3, 4) により,(((3+7) +7) +7) +7 = 31となる.
そこで、
sub1(x)=x-1を定義すると
Iteration(sub1, 3, 4)=3-1-1-1-1=-1
Iteration(sub1, 3, 3)=3-1-1-1-1=0
Iteration(sub1, x, x)=x-1-1....-1=0
となって欲しい。
<geogebraでコード化>
x=5はできたとする。
そのあとx=0に戻すマクロを作ろう。
add1、sub1を使ってclear。
作成実験開始!
・数式ビュー
x=5
add1(x)=x+1
sub1(x)=x-1
clear(x)=Iteration(sub1,x,x)
x=5は
eq1:x=5
になり設定で型をみると、「直線」という図形オブジェクトになっています。
つまり、小文字のxは関数グラフの変数になるのがデフォルトなのですね。
そこで、X=5
と「大文字」にすると、変数Xに5がセットされます。
Xの設定で型をみると、「数値」です。
add1(x),sub1(x)は無事に、型は「関数」というxの関数グラフオブジェクトになります。
さて、
clear(x)=Iteration(sub1,x,x)
は拒否されて、エラーになります。
a=Iteration(sub1,3,3)
これは0になります。型は「数値」です。
clear=Iteration(sub1,X,X)
これは0になりますが、型はaと同じ「数値」です。
だから、これではマクロと言えませんね!
・新規ツールの作成
しかし、ヘビーユーザー用の方法があります。
上位のメニュー(3本線)を開き、ツール>「新規ツールの作成」を選ぶ
とダイアログボックスが開きます。
「入力オブジェクト」タブでドロップダウンリストからXを選びます。
「出力オブジェクト」タブでドロップダウンリストからclearを選びます。
ツールの名前を「CL」と入力して、下にある終了を押します。
そこで、アイコンがならぶメニューの右はしをみると、「レンチ」アイコンが追加されてます。
これがCLですね。
押してみると、CLは[<数値>、<関数>」
数値に関数を作用させるマクロとしての型が表示されます。
だから、
CL(10,sub1)
を入れると0とめでたく表示されます。
ためしに、
CL(10,add1)
を入れると20になります。
ということは、入力も関数もフリーで、
ただ、関数を入力Xに対してX回実行するという
可変なマクロを作ったということになりますね。
ある意味素晴らしいです。
調子に乗って、square(x)=x^2をCLに入れるという面白い使い方ができます。
CL=Iteration(x^2,x,x)と使えますね。
CL(3,square)=6561です。
なぜかというと、
Iteration(3^2,3,3)=((3^2)^2)^2)=3^6=6561と計算するからですね。
・ボタンのクリックイベントの手動ループ
計算言語で愛用した、while文がないために目標までボタンを押すという地味な方法も便利です。
「作成」ボタン
result=X
text="いまXは"+result+"です"
「ゼロをめざす」ボタンに次のスクリプトを貼り付けます。
SetValue(X,sub1(X))
"いまXは5です"、"いまXは4です"、"いまXは3です"、"いまXは2です"、"いまXは1です"、"いまXは0です"と
めでたく表示されますね。
・ボタンとツール
ボタンの単純さとツールの便利さがわかりました。
課題に戻ってみると、まだ、課題は実現できていないことに気づきます。
CLツールは多機能すぎるのです。
CLはsub1を隠蔽していません。
そこで、
「Clearマクロ」ボタン
a=5
Ina=InputBox(a)
b=CL(a,sub1)
res="Clear("+a+")="+b
ボタンの中にツールをスクリプトとして貼り付けます。
こうすると、入力ボックスaに何を入れても、Clear(a)=0と表示される。
Clearマクロのできあがりですね。
このマクロボタンはマクロ作成の1回だけの使い捨てで、
入力ボックス1に数100を入れるとClear(5)=0が、Clear(100)=0と表示が変化します。
・ツールよもう一度
新規ツールボックスで、
入力でaを選び、出力をa=CL(a,sub1)を選び、ツール名をCLsubにしましょう。
数式ビューで
CLsub(100)
とすると0が返りました。これで、完全にClearマクロができましたね。
ツールを入れ子にすることで、希望のマクロがつくれそうですね。
Clearマクロを実装する
課題2:valを四捨五入して小数第n位で返すRound(val,n)と可変数で平均を返すAve(a1,...,an)を作ろう。
n個のパラメータを必要とするマクロfの作り方。
まず、n個のパラメータを受け取る変数x1,...,xnと、結果を返すための変数yを呼び出します。
fのn個の引数がw1,...,wnであるとするならば、n個の代入文を実行する必要がある。
AにBを入れるマクロをsetと呼ぶと、set(A,B)とかける。
だから、set(x1,w1),...,set(xn,wn)という実行をもつプログラムを作ろう。
その後、x1,...,xnからyを求めることは、すでにある関数やマクロの組み合わせでできるでしょう。
これは、一般論です。
では、RoundとAveのマクロの作り方を考えましょう。
・Roundマクロ
floor(x)(:=x/10の整商を返す)関数は標準であるとします。
Round(x,n)はx=11.156,n=2だとしたら、第3位の6をみて、11.16を返します。
これは目の計算です。
このプロセスはを詳しくみよう。
x=11.14500から11.15499..までは11.15となり、x=11.15500から11.15999..まで11.16を返します。
第3位が5以上だと切り上げを作動していることがわかります。そこでx+0.005をして、数xをシフトします。
x+0.005=11.15000から11.15999..までは11.15となり、x+0.005=11.1600から11.16999..まで11.16
こうなると、第3位以下を切り捨てても大丈夫ですね。
しかし、切り捨て関数のfloorは小数を整数に切り捨てることしかできません。
どうしましょう。
10^2倍して表示します。
10^2(x+0.005)=1115.000から1115.999までは1115となり、10^2(x+0.005)=1116.00から1116.999まで1116です。
ここで、floorすると、きれいに1115か1116にきりわけができます。でも、10^2の副作用を解除する必要があるね。
ここまでのプロセスをまとめると、x⇒10^2(x+5/10^3)⇒floor⇒÷10^2で求められます。
1本の式でかくと、
round(x,n)=floor(10^{n}( x + 5/10^{n+1} ))/(10^{n})
ですね。
これは、記号の差はあっても、どんな言語でもイディオムとして使えます。
・Ave(可変個引数v)マクロ
リストが使える言語だとする。
set(xs,v)として、変数リストとしてxsを使えるようにします。
Length関数がなければ、loop、whileでcount=count+1を繰り返して個数を数えるマクロが作れます。
Sum関数でなければ、Sum=Sum+xを繰り返して合計を出すマクロが作れます。
だから、LengthもSumも使えると考えて大丈夫です。
そうすると、res=Sum(xs)/Length(xs)をして、これを戻り値とすればいいですね。
<geogebraでコード化>
round(x,n)=floor(10^{n}( x + 5/10^{n+1} ))/(10^{n})
をいきなり貼り付けてもエラーになります。関数一般の定義は関数グラフがかけるものが基本です。
x=123.4567
n=3
rd=floor(10^{n}( x + 5/10^{n+1} ))/(10^{n})
これが数値としてエラーなしならば、
新規ツールで、
入力をx,nとし、出力をrd、ツールの名前をroundとしましょう。これをマクロとして、
round(11.1549,2)=>11.15
round(11.1556,2)=>11.16
と正しく表示されるでしょうか。
されます。
a={1,2,3,4,5}
s=Sum(a)
n=Length(a)
v=s/n
新規ツールで、
入力をaとし、出力をv、ツールの名前をAveとしましょう。これをマクロとして、
Ave({1,2,3,4,5,6,7,8,9}=>5
AVe({5,6,5,6,5,6,5,6,5,6}=>5.5
と正しく表示されるでしょうか。
されます。
<振り返り>
言語理論的にはこれで万歳ですが、
数式ビューでの入力がハードが高い人がいるでしょう。
だから、
value=InputBox(val)
m=3
digit=InputBox(m)
res=round(val,m)
restxt1=val+ "を四捨五入して小数第"+m+"位にすると、"+res+"です。”
この貼り付けがうまくいかないときは、テキストをクリックして、修正してください。
vs=InputBox(c)
resv=Ave(c)
restxt2=c+"の平均は"+ resv
などのように、入力ボックスと出力テキストをセットにすることで、マクロは使いやすくなるでしょう。
もちろん、ツールで作ったマクロを関数としてバリバリ使いたい人は、GUIなしでどんどん作り込みを
重ねましょう。
ユーザーファーストで考えて、ユースケースを考えて、柔軟なアプリ作りをしたいですね。