Factor入門 16th。今日もLanguage referenceからsyntaxの続きで、"vocabulary search path"を地味にみていきます。Factorのparserがトークンを読むにあたって、vocabulary search pathに入ってるvocabularyの順番で、その名前で定義されているwordを探しに行きます。

vocaburary search path : use

vocaburary search pathは、変数useに連想配列の形で入っているとのことで、調べてみます。

( scratchpad ) USE: vocabs.parser
( scratchpad ) use get .
V{
H{
{ "change-reg" change-reg }
{ "change-ref" change-ref }
{ ">>number" >>number }
{ "change-in1" change-in1 }
{ "change-in2" change-in2 }
{ ">>minute" >>minute }
(以下略)

useの中身は、すでにwordの名前からwordの実体への連想配列になっていて、vocaburary名は直接出てこないようです。

wordを定義する先のvocaburary

新しいwordを定義する場合、それもどこかのvocabularyに属させないといけません。対話環境(listener)ではvocabulary "scratchpad"が使われるので気にしなくてもよいですが、通常定義するときはIN:をつかって、これから定義するvocabularyをどこに属させるのか決めます。このIN:で定義されている最新のvocabulary名は、変数inで取り出せます。

( scratchpad ) USE: vocabs.parser
( scratchpad ) in get .
"scratchpad"

ところがこれ、ファイルを作ってやるとこうなります。

test.factor:

USING: vocabs.parser namespaces io prettyprint ;
in get .
IN: test-temp
in get .

実行結果

$ /Applications/factor/factor temp.factor
f
f
$

IN: が効いているのはパース中で、in get . が実行されるときはコンパイル後、って違いなんだろうと思いますが、いまのところは完全に理解できていません。第19回くらいに明らかになるかもしれません。

USE: USING:

さて、IN:は新規にwordを定義するvocabularyを指定しますが、サーチパスに追加するにはUSE:またはUSING:を使います。USE:はひとつだけ、USING:は、;(セミコロン)があらわれるまでの複数vocabularyを指定できます。順番に、search pathの先頭に追加されていきます。

USE:のいろいろなバリエーションとしてQUALIFIED:, FROM: EXCLUDE:...などいろいろあります。たとえば、QUALIFIED:で読み込まれたwordは、prefixつきになります。名前の衝突を避けるのに使うのでしょうか。例えばこんなです。

( scratchpad ) QUALIFIED: math
( scratchpad ) 5 9 math:*
--- Data stack:
45
( scratchpad )

useの中身の連想配列のキー側に"math:*"が入っていて、value側にはword '*'の実体をいれることでこれを実現しているようです。

それから、private wordを定義する方法があります。

( scratchpad ) IN: myvocab
( myvocab ) <PRIVATE
( myvocab.private ) : my-word ( -- ) "it's private" print ;
( myvocab.private ) PRIVATE>
( myvocab )

単に、現在のinにたいして".private"をつけたものをinにセットするだけです。documentにもこう書かれています。

Private words can be defined; note that this is just a convention and they can be called from other vocabularies anyway

auto-use?

さて、ここまでは、サーチパスにあるvocabularyからword定義を探す前提でしたが、他のメカニズムもあります。

もし、サーチパスからtokenの名前がつけられたwordが見つからなかった場合には、ロードされているすべてのvocabularyから探しにいきます。

このとき、auto-use?がtかつ、ロードされているvocaburaryのひとつだけで該当するwordがみつかれば、それをサーチパスにくわえてparsingが続行されます。parse後、そのwordが属するvocaburaryが表示されます。

( scratchpad ) t auto-use? set
( scratchpad - auto ) 10 <vector>
1: Note:
Added "vectors" vocabulary to search path
--- Data stack:
V{ }
( scratchpad - auto )

開発中にうっかりUSING:を忘れたときのために使うもの、だそうです。

auto-use?がfだとerrorになりますが、キャッチしてなんとかできるようです。errorの仕組みがまだ分かっていないので、詳細はまた今度、です。

もうひとつはっきり分からないことがあって、"loaded vocaburary"の意味です。サーチパスにはいっている場合、useに名前とwordの連想配列が保持されている、のは分かります。USING:していないけど、イメージに入っているものがloaded vocaburaryなんでしょう。ではイメージにvocaburaryがloadされるタイミングは? とか、useのスコープは? とか、useはいつ使われる? (パース時のみ?)とか、この疑問の先にはおそらくいろいろ分からないことがぶらさがっていますが、今回はとりあげません。

word定義のshadowing

さて、vocaburary search pathの最後は、shadowingの話です。Factorではvocaburaryが異なれば同じ名前のwordが定義できますが、その名前のスコープについて、です。これは実例をみるのがはやいとおもわれるので、Factor documentを見ながら自分でも書いてみましょう。

ソースコード

IN: foo
USING: math io prettyprint ;
: + ( x y -- z ) "foo:+ called" print + ;
"checkpoint1: " print
3 4 + .
USE: foo
"checkpoint2: " print
3 4 + .

実行結果

checkpoint1:
7
checkpoint2:
foo:+ called
7

最初にfooで+を定義しています。このとき、inはfooなので、新しい+はfooに定義されます。その中で使われている+は、USING: で指定されているmathの中にあるので、そちらが優先されます。そのすぐした、トップレベルに書いた+も、mathのものが使われます。

しかし、USE: fooをすれば、その前でUSING:されていたmathの+は隠されて、fooの+が呼ばれます。しかしfoo:+の定義の中で呼ばれる+は、相変わらずmath:+です。

USING:とIN:の順序を逆にするとどうでしょうか。

USING: math io prettyprint ;
IN: foo
: + ( x y -- z ) "foo:+ called" print + ;
"checkpoint1: " print
! ここで呼ばれる+は、fooの+。
3 4 + .
USE: foo
"checkpoint2: " print
3 4 + .
checkpoint1:
foo:+ called
foo:+ called
foo:+ called
foo:+ called
foo:+ called
foo:+ called
foo:+ called
foo:+ called
foo:+ called
(以下いつまでも続く)

後に書いたIN:のほうが優先されるわけですな。