2009年6月21日

Factorに入門する(20) inlineそのいち

[ Factor ]

「特定のディレクトリにあるjpgファイルのリスト」を得る用事(用事?)があって、factorで書いてみたらけっこう苦労しました。

USING: io.directories io.pathnames kernel sequences io ;
IN: filelist

<PRIVATE
: filter-quot ( str -- quot ) [ file-extension ] swap [ equal? ] curry compose ; inline
PRIVATE>

: filelist ( path ext -- seq ) 
    dupd [ directory-files ] dip filter-quot filter 
   [ dupd "/" swap 3append ] map nip ;

最初は、filer-quotのinline指定なしで書いてみました。そうすると、filelistのコンパイルに失敗します。曰く、

Got a computed value where a literal quotation was expected

filter-quotで生成したquotationを、filterに渡しているところでこのエラーが発生しているようです。filterはliteralのquotationじゃないと受け付けないと。それじゃ全然quotation使えないじゃん!

と悩むこと30分。inline指定しておけばよいことがわかりましたが...これは「optimizing compiler」でのみ効くとのこと。つまり、optimizing compilerを使わなければinline指定しなくても動くはずとのこと。

まだよくわからないので、続きます。

2009年6月 1日

Factorに入門する(19) wordの定義: パース時、ランタイム

[ Factor ]

Factor入門1ヶ月以上あきました。第19回。まだ景色が見えてないところがたくさんあるのに、息切れしてきました。

今日はwordを定義する方法について調べます。普通のword定義はこういうふうにします。

: myword ( x y -- z ) dup * + ;

ほぼ意味のないwordですが、x + y * 2を計算します。mywordは名称。( x y -- z )はstack effectの宣言。: からはじまって、;で終わるまでがwordの定義です。これは、ソースコードのパース時に読み込まれ、定義が実行されます。

ランタイムでwordを定義することもできるそうで、それがdefine。

( scratchpad ) DEFER: foo
( scratchpad ) \ foo [ dup * + ] define
Attempting to define foo outside of a compilation unit

Type :help for debugging help.
( scratchpad ) [ \ foo [ dup * + ] define ] with-compilation-unit
:errors - show 1 compiler errors
( scratchpad ) :errors

==== <Listener input>

<Listener input>

Asset: foo

Missing stack effect declaration
word foo
:errors - show 1 compiler errors
( scratchpad ) [ \ foo [ dup * + ] (( x y -- z )) define-declared ] with-compilation-unit
( scratchpad ) 3 4 foo .
19

defineだとstack effect declarationがない、って怒られてしまいました。用途としては、既に定義されているwordの中身を置き換えるもの、のように見えます。いきなり定義するにはdefine-declaredを使う必要があるようです。いずれにせよ、wordとして用意しておかなくてはならないので、DEFER:を使わなくてはなりません。DEFER:はランタイムじゃなくてパースタイムだよな。ランタイムにwordを作る方法はやっぱりよく分かりません。

あと、with-compilation-unitが必要とのことですが、compilation-unitの詳細もよく分かっていません。要調査。

2009年4月23日

Factorに入門する(18) syntax残り

[ Factor プログラミング言語 ]

18回目。Factor Language Reference: syntaxも終盤です。

Commentは!または#!、行末までコメント。

リテラルは第4回でとりあげたのでとばします。

そして、parse time evaluation。parsing wordsの話か、と思いきや、そうじゃなくて、「パーズ時に評価する」という特殊なことをできるそうな。ソースコード上、"<<"と">>"に囲まれた部分は、パース時(つまりコンパイル前)に評価されます。"rearly-used feature"だそうなので、今回はとばしておきます。

超短くて中身がほぼないですが、今回はここまで。

2009年4月21日

Factorに入門する(17) top level formsの実行タイミング

[ Factor プログラミング言語 ]

収拾付かないFactor入門も17番目。まだまだ全体の景色が見えないので、まとめにははいりませんよ。今日はtop level formsをしっかり理解して帰ってください(オレが)。

top level formsとは、ソースコードのうちdefinitions以外の部分です。どこに書いてあっても、ソースコードすべてがパースされた「後」に実行されます。試してみましょう。

こういうソースファイルを書きます。fooを定義する前にfooを呼ぶコードです。

IN: test
USING: io ;

foo

: foo ( -- ) "test:foo called." print ;

実行すると

P" temp.factor"
4: foo
      ^
No word named ``foo'' found in current vocabulary search path

パース時のエラーになってます。パースして、コンパイルされて、それから実行されるのですね、たぶん。

ではこういうのはどうでしょうか。

USING: io math prettyprint kernel ;
IN: test

3 4 + .

: + ( x y -- ) 2drop "test:+ called. dropped values! HAHAHA" ;

こいつを実行すると

7

普通にmathの+が呼ばれます。サーチの順序ではtestが先に入っていますが、パース時点ではまたtest:+が定義されていないからですね。そういうときには、DEFER:で、先にwordの空定義を作っておくことができます。Pascalのforwardみたいなもんですが、FactorのDEFER:では、呼ぶとエラーになるwordがまず定義されます。

まずは、DEFER:で+を宣言して、しかし定義はしないコードを書いてみます。

USING: io math prettyprint kernel ;
IN: test
DEFER: +

3 4 + .

実行結果。

Calling a deferred word before it has been defined

DEFER:した+がまだ定義されていない、と怒られました。

+の呼び出しの「後」に、vocaburary "test"の+を定義して、実行してみましょう。

USING: io math prettyprint kernel ;
IN: test
DEFER: +

3 4 + .

: + ( x y -- str ) 2drop "test:+ called. dropped the values! HAHAHA"  ;
"test:+ called. dropped the values! HAHAHA"

ということで、test内で+を定義したのより後にtop level formが実行されていることが確認できました。

ドキュメントには、前回疑問に思った"ソースファイル中でinがとれない"ということについても書いてありました。

Top-level forms do not have access to the in and use variables that were set at parse time, nor do they run inside with-compilation-unit; so meta-programming might require extra work in a top-level form compared with a parsing word.

さらに、変数スコープもtop-levelではダイナミックなあたらしいものが生成されるそうで、実行後にそのスコープはなくなってしまうそうな。このあたりの詳細は、スコープのところをやるときにつっこんでみることにします。

2009年4月20日

Factorに入門する(16) vocaburary search path

[ Factor プログラミング言語 ]

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:のほうが優先されるわけですな。