_ 日記の引っ越し
これまで tDiary を使ってこの日記を書いてきたのだけれど、 更新頻度が低く、放置している期間が長いこともあって、 いつの間にかコメントスパムや、リンク元スパムの温床になっていた。
加えて、バージョンアップしてみたのだが、なんだかチョーシがよろしくない。
私は無力だ。Orz
それほど機能を使いこなしていたわけでもなかったので、この際、N2Wikiの方に日記を引っ越すことにしました。
これまでの記事については、当面、残しておく予定ですが、新しい記事は、
http://gikoforth.s13.xrea.com/n2/wiki/Diary
↑こちらに書いていく予定です。
よろしくお願いいたします。m(_o_)m
「地味な実演」にて使用したターミナルのフォントに関してですが、「あのフォントは何?」というものを何カ所かで見かけたのでお答えします。
アレは「あくあフォント」というフリーのものを利用させてもらってますです。
私のデフォルトターミナルフォントです。
硬くなくてスッキリしているところが好みなのです。
また新しいconcatenative言語が・・・。
Vと似た記法は自分も以前考えていたことがあったので、ちょっぴりやられちゃった感がありますね・・・。
LL言語のお祭りにはるばる参加してきました。
スタッフの皆様に参加する機会をいただけたことに感謝いたします。
会場では本物のMatzとDan kogaiがいるーーー!、という状況にテンパって、いろいろと挙動不審な人になってました。Orz
言語制作者の方たちと直接お話しできて楽しかったです。
そして、Joyのlanguage updateは自分の手に余るので辞退したハズなのに、何か見えざるプレッシャーを感じ、自分の発表では結局Joyを紹介してしまうというこの矛盾。
セッション途中、ポロリとForth愛を口にしてしまい、これはなんていう羞恥プレイなんだろう?とか思ってしまった。
印象に残ったことをいくつか。
基調講演の和田先生のお話は、アナログコンピュター(?)とアナログハッカー(?)について。
アレを作った人たちが現代に生きてたら、何を作るんだろう?
あと、どう書くセッションのプレゼン画面の中のチャットと会話しながら進める司会進行が新しい感じがしましたです。楽しい。
(追補)
戻りました。尻が痛い。
k.inabaさんに「地味な実演」で使用した素数列生成のコードについて言及していただいてました。
ありがとうございます。
>なんか処理系動かせなかったので
あうあぅあぁぁ(スミマセンOrz)
>dip はスタック操作レベルで考えるより :>> [...] dip というイディオムと思えばいいのかな。
自分ではイディオムという意識が全く無かったのですが、リストに対する再帰関数で頻出しそうなパターンだと指摘して頂いてから気付きました。
・・・ということは独立したコンビネータにする価値があるのかもですね。そうか。
Clean速そうだなぁ。ABCマシンの勉強してみよっかな。とかいろいろとlanguage updateの感想もあるのですが、あとで書・・けたらいいな。
再帰を使って素直に総当たりで書いた・・・のだけれど、GCのバグでハマってしまった。Orz
他にもいろいろあって、いろいろ直しました。
need BNF
class infix super{ BNF }
'+' token '+'
'-' token '-'
'*' token '*'
'/' token '/'
{ <d> @token dup , '0' '9' fromto }
{ <n'> <d> <n'> | <d> }
{ <n> <n'> ," .0 " }
{ <t'> '*' <n> ," :* " <t'>
| '/' <n> ," :/ " <t'>
|
}
{ <t> <n> <t'> }
{ <e'> '+' <t> ," :+ " <e'>
| '-' <t> ," :- " <e'>
|
}
{ <e> <t> <e'> }
{ __start__: <e> }
end
infix :new -> bnf: ifx
{ komachi | nums ops expr c ans | recursive
nums :>> -> nums -> n
expr n :+ -> expr
if nums :isEmpty then
" ifx :init :start " expr :+ :evaluate -> x
if x ans 1e-6 :=~ then
++ c
c . ." : " expr :print cr
endif
else
for ops repeatWith it -> op
nums ops expr op :+ c ans komachi -> c
loop
endif
c
}
1 to: 9 [ :to_s ] :map ( " +" " -" " *" " /" " " )
" " 0 100.0 komachi . ." 通りあった。" cr bye
マシンはいつものようにPPC G4 800MHz。
$ time ./sukuna -q komachi.sk 1: 1+2+3+4+5+6+7+8*9 2: 1+2+3-4+5+6+78+9 3: 1+2+3-4*5+6*7+8*9 4: 1+2+3-45+67+8*9 5: 1+2+3*4-5-6+7+89 6: 1+2+3*4*5/6+78+9 7: 1+2+3*4*56/7-8+9 8: 1+2+34-5+67-8+9 9: 1+2+34*5+6-7-8*9 10: 1+2-3*4+5*6+7+8*9 11: 1+2-3*4-5+6*7+8*9 12: 1+2*3+4+5+67+8+9 13: 1+2*3+4*5-6+7+8*9 14: 1+2*3-4+56/7+89 15: 1+2*3-4-5+6+7+89 16: 1+2*3*4*5/6+7+8*9 17: 1+2*34-56+78+9 18: 1+23-4+5+6+78-9 19: 1+23-4+56+7+8+9 20: 1+23-4+56/7+8*9 21: 1+23-4-5+6+7+8*9 22: 1+23*4+5-6+7-8+9 23: 1+23*4+56/7+8-9 24: 1+23*4-5+6+7+8-9 25: 1+234-56-7-8*9 26: 1+234*5*6/78+9 27: 1+234*5/6-7-89 28: 1-2+3+45+6+7*8-9 29: 1-2+3*4+5+67+8+9 30: 1-2+3*4*5+6*7+8-9 31: 1-2+3*4*5-6+7*8-9 32: 1-2-3+4*5+67+8+9 33: 1-2-3+4*56/7+8*9 34: 1-2-3+45+6*7+8+9 35: 1-2-3+45-6+7*8+9 36: 1-2-3+45-6-7+8*9 37: 1-2-34+56+7+8*9 38: 1-2*3+4*5+6+7+8*9 39: 1-2*3-4+5*6+7+8*9 40: 1-2*3-4-5+6*7+8*9 41: 1-23+4*5+6+7+89 42: 1-23-4+5*6+7+89 43: 1-23-4-5+6*7+89 44: 1*2+3+4*5+6+78-9 45: 1*2+3+45+67-8-9 46: 1*2+3-4+5*6+78-9 47: 1*2+3*4+5-6+78+9 48: 1*2+34+5+6*7+8+9 49: 1*2+34+5-6+7*8+9 50: 1*2+34+5-6-7+8*9 51: 1*2+34+56+7-8+9 52: 1*2+34-56/7+8*9 53: 1*2-3+4+56/7+89 54: 1*2-3+4-5+6+7+89 55: 1*2-3+4*5-6+78+9 56: 1*2*3+4+5+6+7+8*9 57: 1*2*3-4+5+6+78+9 58: 1*2*3-4*5+6*7+8*9 59: 1*2*3-45+67+8*9 60: 1*2*3*4+5+6+7*8+9 61: 1*2*3*4+5+6-7+8*9 62: 1*2*3*4-5-6+78+9 63: 1*2*34+56-7-8-9 64: 1*2/3+4*5/6+7+89 65: 1*23+4+5+67-8+9 66: 1*23+4+56/7*8+9 67: 1*23-4+5-6-7+89 68: 1*23-4-56/7+89 69: 1*23*4-56/7/8+9 70: 1*234+5-67-8*9 71: 1/2*3/4*56+7+8*9 72: 1/2*34-5+6-7+89 73: 1/2/3*456+7+8+9 74: 12+3+4+5-6-7+89 75: 12+3+4-56/7+89 76: 12+3-4+5+67+8+9 77: 12+3*4+5+6+7*8+9 78: 12+3*4+5+6-7+8*9 79: 12+3*4-5-6+78+9 80: 12+3*45+6*7-89 81: 12+34+5*6+7+8+9 82: 12+34-5+6*7+8+9 83: 12+34-5-6+7*8+9 84: 12+34-5-6-7+8*9 85: 12-3+4*5+6+7*8+9 86: 12-3+4*5+6-7+8*9 87: 12-3-4+5-6+7+89 88: 12-3-4+5*6+7*8+9 89: 12-3-4+5*6-7+8*9 90: 12*3-4+5-6+78-9 91: 12*3-4-5-6+7+8*9 92: 12*3-4*5+67+8+9 93: 12/3+4*5-6-7+89 94: 12/3+4*5*6-7-8-9 95: 12/3+4*5*6*7/8-9 96: 12/3/4+5*6+78-9 97: 123+4-5+67-89 98: 123+4*5-6*7+8-9 99: 123+45-67+8-9 100: 123-4-5-6-7+8-9 101: 123-45-67+89 101通りあった。 real 3m47.550s user 3m31.084s sys 0m2.550s $
・・・重いなぁ。
演算子が同じ優先順位を持っていて左結合の場合は、上のinfixパーサークラスを下のように定義すればOK。
class infix super{ BNF }
'+' token '+'
'-' token '-'
'*' token '*'
'/' token '/'
{ <d> @token dup , '0' '9' fromto }
{ <n'> <d> <n'> | <d> }
{ <n> <n'> ," .0 " }
{ <e'> '+' <n> ," :+ " <e'>
| '-' <n> ," :- " <e'>
| '*' <n> ," :* " <e'>
| '/' <n> ," :/ " <e'>
|
}
{ <e> <n> <e'> }
{ __start__: <e> }
end
$ time ./sukuna -q komachi.sk 1: 1+2+3+4+5*6-7+8+9 2: 1+2+3+4*5+67-8-9 3: 1+2+3-4+5+6+78+9 4: 1+2+3-4+5/6*78+9 5: 1+2+3-4*5-6+7+89 6: 1+2+3*4-5-6+78+9 7: 1+2+34-5+67-8+9 8: 1+2*3+4+5*6-7+8-9 9: 1+2*3-4+5-6+7+89 10: 1+2*3-4*5+6+78-9 11: 1+2*3*4+56+7-8+9 12: 1+2/3+4+5-6+7+89 13: 1+2/3+4*5+6+78-9 14: 1+23+4+5-6*7-89 15: 1+23-4+5+6+78-9 16: 1+23-4+56+7+8+9 17: 1+23-4-5*6-7+8+9 18: 1+23-4*5+6-7-8+9 19: 1+23-4*5-6+7+8-9 20: 1-2+3-4+5*6-7+89 21: 1-2+3*4-5*6-7+89 22: 1-2*3-4+5+6+7+89 23: 1-2/3+4*5*6+7-8-9 24: 1-2/3-4+5*6+7+89 25: 1*2+3+45+67-8-9 26: 1*2+3*4+5+6+78-9 27: 1*2+3*4+56+7+8+9 28: 1*2+3*4-5*6-7+8+9 29: 1*2+3*4*5+6-7-8+9 30: 1*2+3*4*5-6+7+8-9 31: 1*2+34+56+7-8+9 32: 1*2-3+4-5+6+7+89 33: 1*2-3+4*5*6-7+8+9 34: 1*2*3+4+5*6-7+8+9 35: 1*2*3+4*5+67-8-9 36: 1*2*3-4+5+6+78+9 37: 1*2*3-4+5/6*78+9 38: 1*2*3-4*5-6+7+89 39: 1*2*3*4-5-6+78+9 40: 1*2*34+56-7-8-9 41: 1*2/3/4+5*6+78-9 42: 1*2/3/4-5+6*78+9 43: 1*23+4+5+67-8+9 44: 1*23-4+5-6-7+89 45: 1*23-4+5/6+7+89 46: 1/2+3-4+5*6*7-89 47: 1/2+3*4+5-6+78+9 48: 1/2+3*4*5+6+7+8+9 49: 1/2-3*4/5+6+7+89 50: 1/2*34-5+6-7+89 51: 1/2/3*456+7+8+9 52: 12+3+4+5-6-7+89 53: 12+3+4+5/6+7+89 54: 12+3-4+5+67+8+9 55: 12+3*4-56+7+89 56: 12+3*4/5+6-7+89 57: 12-3+4+5*6-7+8-9 58: 12-3-4+5-6+7+89 59: 12-3-4*5+6+78-9 60: 12-3*4+56+7-8+9 61: 12*3-4+5-6+78-9 62: 12/3+4-5*6-7+89 63: 12/3*4+5+6*7-89 64: 123+4-5+67-89 65: 123+45-67+8-9 66: 123-4-5-6-7+8-9 67: 123-45-67+89 68: 123-45/6+78+9 68通りあった。 real 3m44.165s user 3m27.379s sys 0m2.455s $
次にコルーチン使ってジェネレータっぽく書いてみた。
infixクラスは再帰版と同じなので本体のみ↓。
need coroutine
{ exps | nums ops | recursive
nil
[ drop continue!
nums :1st -> n
if nums :len 1 = then
n yield!
else
for ops repeatWith i -> op
for nums :rest ops exps repeatWith j -> expr
n op :+ expr :+ yield!
loop
loop
endif
eos
]~ iterate
}
{ komachi | nums ops ans |
nil
[ drop continue!
for nums ops exps repeatWith it -> expr
" ifx :init :start " expr :+ :evaluate -> x
if x ans 1e-6 :=~ then
expr yield!
endif
loop
eos
]~ iterate
}
0 -> c
1 to: 9 [ :to_s ] :map ( " +" " -" " *" " /" " " ) 100.0 komachi
[ 1+ -> c c . ." : " :print cr ] :eachWithIndex
c . ." 通りあった。" cr bye
一つだけ注釈すると、
x [f] iterate
↑これでxを初期値としてfを繰り返し呼んだ結果を返すSequenceオブジェクト(遅延リストっぽいモノ)が返ります。(初期値がnilだと無視されて次の要素から始まるSequenceになります。)
1 [ 1+ ] iterate == ( 1 ;; 1+ ) == ( 1 2 3 4 5 6 7 ... )
結果は同じなので省略。
でも再帰版の3倍ぐらい遅い。
うーーーーーーーーむむむむ。
最初に自力で書いたものは、深さ優先探索で力ずくにすべての解を求めるものだったのだけれど、空気を読み封印。(-_-;
いつものようにsumimさんやk.inabaさんのところを参考にいたしました。
無理矢理コルーチン使ってみたよ。
# abura.sk
need coroutine
{ next_state | xs e oke |
[ drop continue!
for 0 xs :len :to repeatWith i -> x
for 0 xs :len :to [ x = ] :remove repeatWith j -> y
x at: xs y at: oke y at: xs - min -> a
if a then
xs :clone -> ns
x [ a - ] app: ns
y [ a + ] app: ns
( " " x 'a' + " から " y 'a' + " に " a " 升移す" ) :cat -> t
( ns e :2nd ( t ) :+ e :3rd ( xs ) :+ )~ yield!
endif
loop
loop
eos
]~
}
{ search_path | q [is_goal] [next] |
[ drop continue!
begin q :isEmpty not while
q :shift -> e
e :1st -> xs
if xs is_goal then
e :2nd yield!
elif xs e :3rd :include? not then
nil xs e next iterate +>: q drop
endif
repeat
eos
]~
}
{ aburauri | oke |
( ( ( oke :1st 0 oke :len 1- times ) ( ) ( ) ) ) -> start
[ [ oke :1st 2 / = ]~ :countWith 2 = ]~ -> [is_goal]
[ oke next_state ]~ -> [next]
nil start [is_goal] [next] search_path iterate
}
argv :rest [ :to_i ] :map :>> aburauri :take [ [ :p cr ] :each cr ] :each bye
コマンドラインから次のように、求める解の数、桶の容量の順でパラメータを指定します。
桶の数は可変。
$ ./sukuna -q abura.sk 1 10 7 3 a から b に 7 升移す b から c に 3 升移す c から a に 3 升移す b から c に 3 升移す c から a に 3 升移す b から c に 1 升移す a から b に 7 升移す b から c に 2 升移す c から a に 3 升移す $ ./sukuna -q abura.sk 2 12 8 5 4 a から c に 5 升移す c から b に 5 升移す a から c に 5 升移す b から d に 4 升移す c から b に 5 升移す d から a に 4 升移す a から c に 5 升移す c から b に 5 升移す a から c に 5 升移す b から d に 4 升移す d から a に 4 升移す c から b に 5 升移す $
動かないよ!と思ったらライブラリのバグだった・・・Orz。
Forthの場合、リターンスタックに載っているリターンアドレスを操作することで、いろいろとアブノーマルな流れ制御が可能だったりします。
たとえば、gforthで以下のようにワードを定義して、
: yield r> r> swap >r >r ;
: f1
1 . yield
2 . yield
3 .
;
: f2
f1
4 . yield
5 . yield
6 .
;
f2を実行すると、
1 4 2 5 3 6
このように、二つのワードを交互に行き来するコルーチン的な動作をさせることができます。
良く言えばlightweightな継続がある(悪く言えばアセンブリ言語なみに低レベルな)わけです。
Forthレベルからスタックポインタ自身を操作することもできるので、いにしえのForth使いは、協調的マルチタスクモニタをForth上でポータブルに実装して、各種組み込み系ソフトウェアに使用していたとも聞きます。(伝聞)
gforthにもtasker.fsというライブラリがあり、例のベンチマークサイトのcheap-concurrencyなどにその使用例を見ることができます。
さて、
Sukunaもリターンスタックやスタックポインタを操作できるのでtasker.fsのようなpause、wakeといった協調的スレッド用ハイレベルワードを用意しようか・・・とも思ったのですが、それはそれとして、ブロッククロージャを利用したイカサマコルーチンライブラリを書いてみました。
0.4.6bに入れてみました。
Luaのコルーチンサンプルと同じような動作をするものが、次のようになります。
need coroutine
{ foo1 | a |
." foo" a . cr
2 a *
}
{ create_co | |
[ continue!
-> b -> a
." co-body " a :p b :p cr
a 1+ foo1 yield! -> r
." co-body " r :p cr
a b + a b - yield! -> s -> r
." co-body " r :p s :p cr
b " end"
]~
}
create_co -> [co]
1 10 co -> a
." main " a :p cr
" r" co -> b -> a
." main " a :p b :p cr
" x" " y" co -> b -> a
." main " a :p b :p cr
" x" " y" co -> b -> a
." main " a :p b :p cr
実行結果:
co-body 1 10 foo 2 main 4 co-body r main 11 -9 co-body xy main 10end co-body xy main 10end
luaの場合には、コルーチン呼び出しが成功したかどうか、bool値で返ってくるのですが、そのような機能はないので省略してます。
ブロッククロージャの先頭付近にcontinue!ワードがあるとコルーチンになります。
コルーチンの呼び出し(Luaのcoroutine.resume相当)は通常のブロッククロージャの呼び出しと同じで、親への復帰(Luaのcoroutine.yield相当)にはyield!ワードを用います。
Luaの場合、コルーチンを一旦抜けるとresumeできないのですが、上の実装では最後にyield!で保存した場所に戻れてしまいます。
さらに、Luaと同じならfoo1の中にyield!があるべきですが、そうなっていないのにはワケがあります・・・。
yield!はcontinue!と同じスコープでないと使えないのです・・・。
・・・・イカサマですよね?
いいえっ!仕様ですっ!
なんとなく実装の詳細がおわかりかと思いますが、このイカサマコルーチン、実際に保存された継続への分岐が起こるのはcontinue!が実行された時点なのです。
そしてその分岐は単なるジャンプなのでスコープをまたげません。Orz
・・・イイところも少しあります。上を見てわかるようにこのコルーチンはブロッククロージャそのものなので、ブロッククロージャ生成以外のコストが係りません。またコンテキストスイッチのコストもほとんどありません。
というのもスタックポインタがコルーチン間で共有されているからです。
通常、協調的スレッドなりコルーチンなりFiberなりは独自のスタック領域をもつ(コンテキストスイッチの際にスタックポインタの書き換えが起こる)実装が多いと思うのですが、書き換えてません。
書き換えが起こるのは、通常のブロッククロージャの場合と同じでローカル変数フレームポインタだけです。
なのでコルーチン間のデータのやりとりには、通常通りパラメタスタックがオーバーヘッドなしにそのまま使えます。
逆にいえばスタックに載せたモノは、yield!から返って来たときにそのまま残っているとは限りません。
保存したいものはローカル変数に入れておきましょう・・・。
さて、上の例は呼び出し側と呼び出された側という親子の関係のあるコルーチンですが、Fiberのように次に起動するコルーチンを指定する書き方もできます。
need coroutine
{ test | |
nil -> [f1] nil -> [f2] nil -> [f3]
[ ." f1 " cr continue!
[f2] switchTo!
[f3] switchTo!
] -> [f1]
[ ." f2 " cr continue!
[f3] switchTo!
] -> [f2]
[ ." f3 " cr continue!
[f1] switchTo!
] -> [f3]
f1
." end" cr
}
>test f1 f2 f3 f1 f3 end ok
yield!の代わりに、次に実行するコルーチンをスタックに積んでswitchTo!を使います。
いずれかのコルーチンが終了すると、最初に起動したコルーチンが呼ばれた場所へ戻ります。
continue!とswitchTo!が同一スコープでないといけない制限も同じです。
sumimさんのコードを参考に憧れのリングノードベンチマークをやってみました。
need coroutine
need benchmark
" %.1f" setFormat: float
class Node
ref Node neighbor slot_accessor
int num_msgs
slot fiber accessor
{ classNew: | m |
m -> num_msgs
[ -> msg continue!
for [ 0 -> i ; i num_msgs < ; ++ i ] do
msg neighbor :fiber switchTo!
loop
]~ -> fiber
}
end
{ ringNodes | n m |
m Node :new dup -> first -> prev
2 to: n [ m Node :new dup ->_neighbor: prev -> prev ] :each
first ->_neighbor: prev
[ " hello" first :fiber i. ] measure: benchmark
." N =" n . ." M =" m . ." , " 1000 :* :p ." miliseconds" cr
}
N = 10 M = 100 , 1.3 miliseconds N = 100 M = 100 , 15.3 miliseconds N = 1000 M = 100 , 168.5 miliseconds
N = 100 M = 1000 , 144.6 miliseconds N = 1000 M = 1000 , 1403.7 miliseconds N = 10000 M = 1000 , 16951.6 miliseconds
PPC 800MHzという非力なマシンであることを考えても、なかなか頑張っているよーな気がします。
流行りに乗り遅れてるのでアレですが、こんなワードを考えてみました。
{ withSucc | xs [proc] |
xs xs :rest ( nil ) :+ :zip [ :unpar proc ]~
}
{ withPrev | xs [proc] |
( nil ) xs :+ xs :zip [ :unpar proc ]~
}
「一つずらしてzip」とクロージャの修飾を同時にやってその二つをスタックに返しています。
↓で、使うときは、eachの前に”はさむ”わけです。
>( 1 2 3 4 5 ) [ swap :p :p cr ] withPrev :each nil 1 1 2 2 3 3 4 4 5 ok >( 1 2 3 4 5 ) [ swap :p :p cr ] withSucc :each 1 2 2 3 3 4 4 5 5 nil ok >
each以外の反復(mapなど)にも使えるのが利点?
本当はwithPrevとwithSuccを重ねて使えたりするとカッコいいのですが、それはできません。残念。
Sukunaは全然全く型推論してないのですが、Catはちゃんと型推論があるConcatenative言語です。
ところでCatの型推論についてのPDFの論文もあったのだけど、読んでみてもサッパリ理解できなかったわけです。Orz
でも、そのアルゴリズムを説明した記事がありました。
コレです。
自分の頭で考えてみたときには皆目見当が付かなかったeval(Joyのi)の型がきちんと付けられていてすごいなー、と。
しかし、型について調べていくと、知識も頭も全くついていけなくなります。Orz
底なし沼のよーだ。
自然言語で仕様を記述するとお好みのプログラミング言語が出来上がるというようなコードがどこかに落ちていないものだろーか?
k.inabaさんのthisを返すがとてもイイ、というか、入れなきゃダメな機能である気がして入れてみました。
sukuna-0.4.5b
「selfを返す」+「その引数そのものを返す」の両方に対応しました。
これらがあると懸案だったアレやコレやダメポな部分がまとめて解決しそうなのです。
k.inabaさんの例を使うと、これまでは、
class Stream
{ print: | object: obj -- Stream: self |
obj :print
self
}
end
class HogeFugaStream super{ Stream }
{ printHoge: | -- HogeFugaStream: self |
self
}
end
↑こんなクラスがあるときに、
{ test
" bbb" " aaa" 123
HogeFugaStream :new :print :print :print
}
↑これは普通にVMコードにコンパイル、実行できますが、
{ test2
123
HogeFugaStream :new :print :printHoge
}
↑こいつはコンパイル時に、「printHogeなんてシラネーヨ」みたいな警告がでます。
この時の:printHogeは、実行時メソッド探索呼び出しにコンパイルされるので、実行はできますが、printHogeが見つからなかった場合には実行時エラーになってしまいます。
加えて、実行時メソッド探索呼び出しにコンパイルされてしまうと、そこで静的型チェックが必ず失敗するようになるので、自動的にワードの型付けをする機能の恩恵が受けられなくなります。(´・ω・`)
しかし、新しい「selfを返す」機能を使うと、
class Stream
{ print: | object: obj -- 'self |
obj :print
self
}
end
class HogeFugaStream super{ Stream }
{ printHoge: | -- 'self |
self
}
end
↑こんな風に書けて、上のtest1もtest2も警告無しでコンパイルできるようになります。
printHogeは、vtableを使った仮想関数呼び出しにコンパイルされ、静的型チェックも(型が整合しているなら)失敗しなくなります。
このため、上のtestとtest2の場合には、
: test | -- hogefugastream: | : test2 | -- hogefugastream: |
↑こんな感じでどちらもStreamではなくHogeFugaStreamを返す型が自動で付くようになります。
(自動型付けは制御構造が出てくると必ず失敗するようにしてあるので、使用できるところはとっても限定されますけれども。)
この「this(self)を返す」がないとダメポなパターンとして、mixinの中のメソッドがselfを返すパターンがあります。
mixin foo
{ bar: | -- どんな型を返せばいいんだろう? |
self
}
end
class bar super{ foo object }
{ baz: ." baz" cr }
end
barメソッドの返す型をfoo型にしてしまうと、barクラスで定義されたメソッドbazをカスケードしようとした場合に「メソッドみつかんねー」と怒られることになります。
fooは様々なクラスにmixinされる可能性があるので、barメソッドの定義の時点ではselfの具体的な型は静的に決定できません。
なので今まではany型を返してお茶を濁していました。しかしany型を返してしまうと「実行時メソッド探索呼び出しにコンパイル→静的型チェックが必ず失敗」のコンボが決まってしまいます。Orz
ところが、今度からは、
mixin foo
{ bar: | -- 'self |
self
}
end
↑こう書けます!ありがたや!
ていうかmixinと静的型を入れようとした時点で自分で考えておくべきことですよね・・・。
もう一つ。「その引数そのものを返す」があると、例のアレがナニします。
スタック操作ワードを定義するとき、
{ flip | a b -- b a |
b a
}
こう書いてしまうと、このflipの型は、
: flip | any: any: -- any: any: |
こうなってしまっていました。このままだと、
{ test 10.0 10 flip }
↑こんな風にコンパイル時にスタックの型が決められそうなときでも、
: test | -- any: any: |
すべてがanyになってしまいます・・・。Orz
でもこれからは、
{ flip | a b -- 'b 'a |
b a
}
↑こう書けます!
このflipの型は、
: flip | any: any: -- '2: '1: |
このようになります。
2番目の引数の実際の型を持つ値と1番目の引数の実際の型を持つ値を返す、という意味になります。
表記の仕方に悩みましたが、最終的にStrongForth風味にしました。
というか最初からStrongForthを参考にすれば良かったような気がしないでもなく・・・。
今度は、
{ test 10.0 10 flip }
↑これが、
: test | -- int: float: |
↑この型を持つようになります。
というわけで、k.inabaさんのおかげでスッキリしなかった部分がかなりスッキリできた気分です。