« 2009年3月 | メイン | 2009年5月 »

2009年4月23日

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

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

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

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

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

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

2009年4月21日

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

収拾付かない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入門 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:のほうが優先されるわけですな。

2009年4月17日

Factorに入門する(15) Parser algorithm

今回からしばらく、Factorのドキュメントの中からLanguage referenceを地道にみていきます。地を這うような内容が、当分続くことになると思います。飽きたら3Dオブジェクトをまわしたりしてみるかもしれません。

Conventionsは以前とりあげたので、今回はdocumentのなかから、syntax(文法)の記事をみていきましょう。 Factorには2種類のsyntaxがあって、それがdefinition syntaxとliteral syntaxですが、ここではliteral syntaxについてとりげられています。本日はまずparser algorithだけとりあげます。

Parser algorithm

FactorはForthの末裔ですので、基本は、"空白でくぎられたトークン"の集まりです。そして大文字小文字は区別されます。 パーサはトークンを読んで、それが数値なのか、ふつうのwordなのか、parsing wordなのかをみていきます。 パース結果はparse treeに追加されていきます。

1文字目がダブルクオーテーション(")のときは、その後に空白があいていなくてもparsing word " が実行され、文字列が読み込まれます。なるほど、これだけ特別扱いなんですね。それ以外の場合、通常のwordであればparse treeに追加され、parsing wordであれば実行されます。

以前parsing wordに挑戦して、 意外に奥が深くて負けましたが、 コンセプトは単純ですね。通常のwordはすぐには実行されず、parsing wordsはその場で実行される。Forthでいうimmediate wordに似ています。

parsing wordはその性質上どんな文法のものでも扱えますが、デフォルトのものはsyntaxボキャブラリにあります。

Factorという言語は検索しにくい

ここのところずっとFactorという言語で遊んでいて、このブログでもいろいろ書いています。面白いだけじゃなくて実用的にも強力そうな言語です。

この言語について調べるとき、"factor"というごく普通の単語が言語の名称になっているため、検索するのがけっこう難しいです。この言語はドキュメンテーションが充実していて勉強しやすいので検索しにくくてもそれほど困らないのですが、でもやはり同じようにおもうひとがいるようで、FactorのMLでも「どういう単語で検索していますか?」というネタが出ていました。

その中の提案で、たとえば"factorcode"という言葉をみんな含めておくべきじゃないか、というのがあったので、とりあえず"factorcode"と書いておきます。公式サイトのアドレスもfactorcode.orgだし、これはいいかもしれません。

ついでに、日本語でもキーワード「スタック指向」というのを書いておきます。Forth、連鎖型言語(concatenative languageにたいする翻訳)はいままでも書いたキーワードですが、"スタック指向"というのは書いていませんでした。Factorは、スタック指向言語ともいえます。スタック指向、って言葉はこのブログでははじめてつかいましたよ(わたしは普段は「スタック言語」という言葉をつかっているので)。

明日、というか今日かな、から、またFactor入門再開します。よろしくどうぞ。

2009年4月16日

RSSフィードをfeedburnerに一本化しました

RSSリーダでここを観察している方へ。

いまさらながら、ここのRSSフィードをfeedburnerにしました。ふるいRSSからはリダイレクトするよう設定していますが、もし問題があれば、これに登録しなおしてください。

よろしくどうぞ。

2009年4月 8日

Factorに入門する(14) 好きな場所に自分のvocaburaryをつくる

Factorの門 その14。今回は軽めのネタ。

前々回・前回でvocaburaryをつくりテストをかきコードをかきドキュメントを書き、ってやりましたが、この場所は基本的に固定されてました。Macで/Applicationsの下にfactorをいれたのであれば、ここ。

/Applications/factor/work/

scaffoldで作るvocaburaryはこの下に作成されます。もちろん、好きなところに作ってもいいんですが、そうするといろいろ不便です。vocaburaryのロードパスに含まれないので、USE:で簡単にロードできなかったり、helpも探してくれなかったり、emacsやGUIのいろいろなサポートを受け付けられなかったり。

というわけで、デフォルト以外の場所にvocaburary root(こういう用語なんです)を追加する方法です。

ドキュメントに"Workng with code outside of the Factor source tree"という項があって、そこに書いてあります。

方法は3つ:

  1. FACTOR_ROOTS環境変数に追加パスを設定する。
  2. ~/.factor-rootsに追加パスを設定する。
  3. add-vocab-rootで設定する。
2番目の方法では、たとえばこんなふうに書きます。

/Users/skoji/Projects/factor/
/mnt/share/factor-codes/

こうしておくと、vocaburary作成時に、resource:workなど以外にここに記述したパスがrootとして使えるようになります。

2009年4月 7日

Factorに入門する(13) ドキュメントも簡単に書ける

Factorの門に入る第13巻。前回に続いて、今度はドキュメントを書いてみます。ドキュメント作成についても、Factorは手厚いのです。しかも、ドキュメントのマークアップもFactorのコード、ドキュメントの出力もFactorのコードで実現できるという徹底ぶり。まあすごいので見てください。

前回からtestやコードを書き続け、17歳教のコードがかきあがったとしましょう。

! Copyright (C) 2009 KOJIMA Satoshi.
! See http://factorcode.org/license.txt for BSD license.
USING: kernel math.parser calendar math io ;
IN: age17
: duration>monthsInInteger ( duration -- x ) duration>months >integer ;
: getDaysPart ( duration -- x ) dup duration>monthsInInteger months time- duration>days >integer ;
: whenAge17 ( timestamp -- timepstamp ) 17 years time+ ;
: age17duration ( timestamp -- duration ) whenAge17 now swap time- ;
: age17and ( timestamp -- months days ) age17duration dup duration>monthsInInteger swap getDaysPart ;

このドキュメントを書くことにしましょう。emacsでM-x fuel-scaffold-helpと入力し、vocaburaryとしてage17を指定してみます。age17-docs.factorが作成されて開かれるはずです。

! Copyright (C) 2009 KOJIMA Satoshi.
! See http://factorcode.org/license.txt for BSD license.
USING: calendar help.markup help.syntax kernel ;
IN: age17

HELP: age17and
{ $values
    { "timestamp" null }
    { "months" null } { "days" null }
}
{ $description "" } ;

HELP: age17duration
{ $values
    { "timestamp" null }
    { "duration" duration }
}
{ $description "" } ;

HELP: whenAge17
{ $values
    { "timestamp" null }
    { "timestamp" null }
}
{ $description "" } ;

ARTICLE: "age17" "age17"
{ $vocab-link "age17" }
;

ABOUT: "age17"

Factorでは、ドキュメントもFactorのコードなのです! これだけでもう、helpを検索することができます。たとえば、age17andをfuel-helpで検索すると、こんな表示がされます。

age17and ( timestamp -- months days )

Vocabulary: age17

Definition

USING: kernel ;
IN: age17
: age17and ( timestamp -- months days )
    age17duration dup duration>monthsInInteger swap getDaysPart
    ;

definitionのUSINGもよきにはからって表示してくれています。でもまだ何の説明もないので、書いていきましょう。日本語通るかな? age17-docs.factorのage17andを次のように書きます。

HELP: age17and
{ $values
    { "timestamp" "誕生日" }
    { "months" "月数"} { "days" "日数" }
}
{ $description "誕生日を入力すると、17歳と何ヶ月何日かを求めます。" } 
{ $examples
  "1970 1 1 <date> age17 and" } ;

これでC-c rして、listenerに読み込みなおさせます。fuel-helpを使ってもいいのですが、listenerからいま書いたヘルプを呼び出してみましょう。

( scratchpad ) \ age17and help
age17and ( timestamp -- months days )
Vocabulary
age17

Inputs and outputs
timestamp 誕生日
months    月数
days      日数

Word description
誕生日を入力すると、17歳と何ヶ月何日かを求めます。

Examples
1970 1 1 >date> age17 and

Definition
USING: kernel ;
IN: age17
: age17and ( timestamp -- months days )
    age17duration dup duration>monthsInInteger swap getDaysPart
    ;
( scratchpad ) 

前回・今回紹介したように、FactorではUnitTestの敷居が低く、ドキュメント作成の敷居が低いので、ちゃんとテストもドキュメントもかかれたコードが作られやすいと思われます。そして、これらのフレームワークも全部Factorで書かれていて簡単に呼び出せるので、開発環境も作りやすいとも思われます。すごいなFactor。でも複数言語(e.g 日本語と英語)でhelpを書くのはどうすればいいんだろう。そういう仕組みはまだ存在しないのかな。もしかするとcontributeするチャンスかもしれません。

Factorに入門する(12) UnitTestが簡単に書ける

Factorへの入門第12回。ここまできたら、自分のためにも適切に配列したちゃんとした入門を書こうかと目論んでいます。

さて、Factorは言語としても強力ですが、"a practical stack language"と名乗っているとおり、実用的な面も抜かりありません。

ライブラリだけじゃなくて、テストやドキュメンテーション作成のサポートもしっかりしてます。現時点では標準のGUI環境より、添付されている強力はemacs factor mode "fuel"を使ったほうが便利なような気がしますが、factorはかなりのハイペースでバージョンアップしているので日々状況は変化しています。

今回は、vocaburary作成とテストをemacsからやってみます。ネタは先日やった17歳教にします。

emacsをたちあげ、

M-x fuel-scaffold-vocab

こう入力すると、

Vocab name:

ときいてきます。vocaburary名はage17としますので、age17と入力します。すると、

Vocab root: resource:

と、vocaburary rootをきいてきます。どこに配置するのかきいてくるのですね。ここでresource:につづけてworkと入力すると、age17.factorが作成され、ファイルが開きます。

! Copyright (C) 2009 KOJIMA Satoshi.
! See http://factorcode.org/license.txt for BSD license.
USING: ;
IN: age17

ここで1行目に注目。すでに私の名前がはいっています。通常、developer-nameというfactorのグローバル変数が使われますが、 emacs上のfuel-scaffold-vocabを使った場合はちょっと違います。fuel-scaffold-developer-nameというemacs上の変数が使われます。この値にはデフォルトでemacsのuser-full-name変数がセットされます。このため、私の環境では特に新たな設定をしなくても、名前がセットされたのですね。

コードをちょいと書いてみます。途中をとばして、まずはdurationから月数をintegerで取り出すwordを書きます。

! Copyright (C) 2009 KOJIMA Satoshi.
! See http://factorcode.org/license.txt for BSD license.
USING: kernel math.parser calendar math io ;
IN: age17
: duration>monthsInInteger ( duration -- x ) duration>months >integer ;

ここで、C-c rとすれば、このvocaburaryをlistenerで読み取ってくれます。

そしたら、こいつのテストを書いてみましょう。fuelでは、C-coで、code/documents/testが切り替わります。

Create /Applications/factor/work/age17/age17-docs.factor? (y or n)

ここではdocument作成はとばします(documentもscaffoldでひな形つくれるので、それは次回)ので、n。

Create /Applications/factor/work/age17/age17-tests.factor? (y or n)

ここではyとして、testのファイルを作成します。中身にこんなふうに書いてみましょう。

USING: math kernel age17 calendar tools.test ;

[ 10 ] [ 0 10 0 0 0 0 <duration> duration>monthsInInteger ] unit-test
[ 130 ] [ 10 10 0 0 0 0 <duration> duration>monthsInInteger ] unit-test
[ 130 ] [ 10 10 1 2 3 4 <duration> duration>monthsInInteger ] unit-test
[ 0 ] [ 0 0 1 2 3 4 <duration> duration>monthsInInteger ] unit-test

ワードunit-testは、期待されるstackのシークエンスと、テストしたいコードのquotationを与えると、テストして結果を返します。listener上かの実行は、"vocaburary名" test、とシンプルです。

( scratchpad ) "age17" test
Loading resource:work/age17/age17-tests.factor
{ [ 10 ] [ 0 10 0 0 0 0 <duration> duration>monthsInInteger ] }
{
    [ 130 ]
    [ 10 10 0 0 0 0 <duration> duration>monthsInInteger ]
}
{
    [ 130 ]
    [ 10 10 1 2 3 4 <duration> duration>monthsInInteger ]
}
{ [ 0 ] [ 0 0 1 2 3 4 <duration> duration>monthsInInteger ] }

==== ALL TESTS PASSED

今回はtestを後にかきましたが、このくらい手軽なので、test駆動でやるのも気楽です。次回は、helpのscaffoldについてとりあげます。