Let's start Scheme

2011-03-25

syntax-rules再び

レコード、コンディションができて、with-exception-handlerも上手いこと動いていそうな感じだったので、
Ypsilonにあるguardの実装を試してみようとおもったら、パターンマッチでエラーがでた。
具体的にはこんなパターン。
;; guardの実装で使われていたものの一部
(define-syntax hoge
  (syntax-rules (else)
    ((_ (var clause ... (else clause2 ...)) b1 b2 ...)
     (do-something))))
エラーとしてはellipsisが足りないらしい。

元々はMIT Scheme由来のものだったので、とりあえず本家でも試してみたが、こけた。一応moshでも試してみたがOKだったので、R6RS的にはOKなんだろうと推測。
ということは、足りない部分を補うか、新たに何とかするしかないということだ。ここは一発気合をいれて実装してみようと思い、まじめに仕様を読むことにした。
要求されているのは以下のとおり。

  • P が下線(_)である場合
  • P がバターン変数である場合
  • P がリテラル識別子であり、マクロの出力に挿入される識別子以外で P と F が両方ともマクロ出力に現れたとき、 F が P と同一の束縛を参照している場合(ふたつの名前風の識別子がどちらも何の束縛も参照しないない場合、すなわち、どちらも未定義である場合も、両方とも同じ束縛を参照しているものと考える)。※1
  • P が (P1 ... Pn) の形式でF が n 要素のリストで P1 から Pn に一致する場合
  • P が (P1 ... Pn . Px) の形式で F が n 要素以上のリストないしは非真正リストで、最初の n 要素が P1 から Pn に一致し、 n 番目の cdr が Px に一致する場合。
  • P が (P1 ... Pk Pe <ellipsis> Pm+1 ... Pn) の形式で、 <ellipsis> が識別子 ... で、かつ F が n 要素のリストで、最初の k 要素が P1 から Pk に一致し、 次の m - k 要素が Pe に一致し、残りの n - m 要素が Pm+1 から Pn に一致する場合。
  • P が (P1 ... Pk Pe <ellipsis> Pm+1 ... Pn . Px) の形式で、 <ellipsis> が識別子 ... で、かつ F が n 要素のリストないしは非真正リストで、最初の k 要素が P1 から Pk に一致し、 次の m - k 要素が Pe に一致し、残りの n - m 要素が Pm+1 から Pn に一致し、最後の n 番目の cdr が Px に一致する場合
  • P が #(P1 ... Pn) の形式で F が P1 から Pn に一致する n 個の要素のベクタである場合
  • P が #(P1 ... Pk Pe <ellipsis> Pm+1 ... Pn) の形式で、 <ellipsis> が識別子 ... で、かつ F が n要素以上のベクタで、その最初の k 要素が P1 から Pk に一致し、次の m - k 要素がそれぞれ Pe に一致し、残りの n - m 要素が Pm+1 から Pn に一致する場合
  • P がパターンデータ(リスト、ベクタ、シンボル以外のデータ)であり、 F が equal? 手続きの意味で等しい場合
syntax-caseの方も※1以外は一緒であった。
基本的なパターンマッチの部分は同じで細かい違いは別にすればなんとかなりそうだろうか。

moshはよく知らないが、YpsilonとGaucheはsyntax-rulesで現れたパターンを一度コンパイル(後でパターンマッチがしやすいように情報を集めておくという意味)して、マクロが使用された際にその情報を元に展開していくという方法を取っている。
それとは別にMIT SchemeやChibi Schemeではsyntax-rulesが現れたらそれ自体をS式を返すS式に変換し、展開時には元のS式をマクロ展開用のS式(要するに展開器)に食わせている。
前者はマクロを展開するコンパイラが展開方法を知っていないといけないが、後者はマクロを展開するタイミングだけ知っていれば後はマクロが勝手に展開してくれる。現状Sagittariusは後者の方法を取っている。(なので、Sagittariusには組み込みのマクロというのはない。マクロはS式とコンパイル時の環境をペアで取るλ式に過ぎなかったりする)

とりあえず、パターンの部分の解析からはじめてみよう。

2011-03-22

R6RSのレコード

例外機構を入れようと思ったら、避けて通れなかったので先に実装することになった。
(with-exception-handlerはすでに作ったのに・・・)

とりあえず、仕様書を読むもよく分からん。Ypsilonの実装がかなりシンプルでいい感じなのでそれを参照することにする。
まずは動作確認。
この段階でとりあえず気づいたこととしては、レコードの中身はtupleみたい。
ちなみに、Ypsilonのtupleはかなり気が利いてる仕様で、見た目は単なる配列なんだけど、先頭要素に「type:」ってついたシンボルを指定すると出力時に「type:」以降だけを出力してくれる。あたかもそれが組み込みの型かのように。
話がそれた。実際に(tuple? (make-warning))とすると真を返してくるので、間違いないだろう。

ここで問題になるのはSagittariusにはここまでシンプルかつ高機能な配列はないということ。Generic&Instanceなんて無駄にごてごてした機能はあるんだけど、こいつを使えばいいかな。
とりあえず、やってみるか。

2011-03-18

Dynamic-windがほしい

テストしながらやっているとどうしても例外を捕捉する機構がほしくなる。
というのも、定義ファイルに書いた組み込み関数の中身が間違っていたとき、現状だと例外を投げて、スタックトレースを出力して落ちる。スタックトレースにある程度どこで起きたのか書いてあるんだけど、直して走らせたら次が出てきたときにがっかりする。
そもそも、単体テストを走らせてるのに、テスト中に落ちるのはあまりに不都合な気がする。ここは一発Dynamic-windを実装して、guardとraiseの実装かね。次の目標にしよう。
(算術用の関数がまだ終わってないけど、一段楽したし)

例外周りを整備するということは、今適当に投げてる例外をちょっと整理しないとなぁ。R6RS的には例外はいくつかの階層を持ってるみたいだし。その辺も考えないと。本当に先が長い。

2011-03-17

なんだか本体以外が充実していく

本体が全然進まないのに周りが進んでいく感じだ。

とりあえず横道にそれた便利ツールとして、
  • VMインストラクションの定義から実装を自動生成する仕組み
  • 簡単なユニットテストフレームワーク
一つ目のはいずれやろうと思っていたが、いろいろ行き詰っていたのでえいやっとやってしまった。もともとcgenがあったのでちょいちょいと定義を足すだけであった。(それ以外にも多少の改修があったが)
これをやったおかげでDirect Threaded Codeの実装が簡単にできそうだったのでついでに実装。どれくらい早くなったのかは分からないけど、とりあえずGaucheとの速度比較。
マシンスペック: ThinkPad X60 Core Duo 1.6GHzくらい?
ベンチマークプログラム: たらいの12, 6, 0
結果:
$ time ./build/sash.exe test3.scm
./build/sash.exe test3.scm 3.52s user 0.01s system 98% cpu 3.601 total

$ time gosh test3.scm
gosh test3.scm 2.91s user 0.01s system 99% cpu 2.934 total
以前と思うと少し差が縮まったか?(でも、以前はGCCの最適化オプションをデバッグのために切ってたから微妙かも)

二つ目のはコンパイルするたびに何かしら(今のところR6RSのライブラリだが)を足していたのだが、いちいち目視で確認するのがイヤになったので、簡単なのでいいから作るかと思って作った。最初は完全R6RSの範囲内で書けるようにしてたけど、諸事情によりSagittarius(というかexplicit renamingに)依存になってる。
一応機能としては、テスト結果と失敗したテストを表示する(そのままは表示されないけど、数値とか)

あと、bitwiseな計算とかがちらほらサポートされたかな。
syntax-caseの実装をとりあえず後回しにして、進められそうなところから進めてる。しかし、R6RSは標準のライブラリがかなり巨大で実装にちょっと辟易してきた・・・orz
でも負けないもん!!

2011-03-07

マクロのシグネチャを変更

(書いてたのが手違いで消えたので2回目。萎える)

define-syntaxが取る式のシグネチャを変更した。今まで2つの引数を必要としていたのに対し、1つの引数を取ることにした。ただ、S式とコンパイル時環境のペアにしただけではあるが、明示的に何が来ているのか分かりにくくなったというデメリットもある。まぁ、生のマクロをいじることはそうないだろうと思うのであまり関係はないが。

これに伴って、今まで構文キーワードだったer-macro-transformerが単なるクロージャに格下げ(?)となった。これはchibi-schemeで実装されていたマクロ変換器からアイデアをもらっている。

これらの変更をしていたので全然syntax-caseの実装が進んでいないが、パターンの解析、マッチング部分の構成、出力部の構成はsyntax-rulesの実装が流用できると思っている。問題となるのは、syntax-caseは構文オブジェクトを返す必要があるということ。
構文オブジェクト自体はあるのだが、それが返っても今の段階ではなんの意味もない。その辺を考える必要がある。

しかし、syntax-rulesが動くので、どっかからexpander(psyntaxとか)を拾ってきてしまおうかという誘惑に駆られる。いかんいかん。

2011-03-05

マクロの問題

syntax-caseを実装していて困った問題に気づいた。
問題の本質を指すコード
(define-syntax hoge
  (lambda (x)
    (er-macro-transformer
      (lambda (form rename compare)
        (display x)
        `(display ,form)))))
一見特に問題ないように見えるが、ここで「x」はer-macro-transformerからは見えない。理由はいたって簡単で、er-macro-transformerの後ろに来たλ式は、それだけでコンパイルされるからである。でも、S式から中間式に変換する段階では見えているので、「x」は大域変数としては扱われない。束縛変数か、自由変数から探そうとしてこける。

実装の問題になると思うのだが、Sagittariusではdefine-syntaxというキーワードはマクロかクロージャのどちらかを取る様にしてある。(以前は何でも取れた)
こんなのでも、特に問題なく動く。
(define-syntax hoge
  (lambda (a b c d e)
    (+ a b c d e)))
(hoge 1 2 3 4 5) ; -> 15
おそらくこの辺が問題なのだと思う。
(ちなみに、クロージャが取れるようにしたのはsyntax-caseを実装するためにこの構文が必要だっただけ)
syntax-rulesもライブラリで実装している今、まじめにdefine-syntaxについて何か考えるときなのかもしれない。

R6RSでこれが問題ないならあんまり気にしなくてもいいのに↓
(define-syntax hoge
  (syntax-case ()
    ((...))))
あぁ、でもこれじゃwith-syntaxの実装とかできなくなるのね。というか、syntax-caseってそれ自体はたんなるマッチングみたいなのか、引数取るし。

syntax-case、かなりいいところまで行ったような気がしたけどやり直しだ・・・

2011-03-04

syntax-caseの実装

結局MIT Schemeのsyntax-rulesの実装を移植して現在syntax-rulesとsyntax-caseを実装中。
syntax-rulesはYpsilonのストレステストが動かないが(正しくマクロのマッチングが行われない)、まぁ通常用途には使える程度に動いてる。
っで、syntax-caseなのだが、とりあえず、動作結果を確認するために以下のようなものをYpsilonに喰わせてみた。
(import (rnrs))

(define-syntax fuga
  (lambda (x)
    (syntax-case x ()
      ((_ r ...)
       (begin
  (display x)(newline)
  (syntax
   (list '(r (r ...)) ...)))))))
(display (macro-expand (fuga 1 2 3)))
で、結果はこんな感じ
(fuga 1 2 3)
((1 (1 2 3)) (2 (1 2 3)) (3 (1 2 3)))
これが意味するところとしては、マクロ内で定義された構文オブジェクト意外はマクロ展開時に実行されているということ。
っで、macro-expandの部分を%macroexpandに変更してSagittariusにも喰わせてみると。こんな感じ。
(begin
 (display x)
 (newline)
 (#<syntax list>
  (#<syntax quote> (1 (1 2 3)))
  (#<syntax quote> (2 (1 2 3)))
  (#<syntax quote> (3 (1 2 3)))))
ここから察するに、もう一段階実行してやって、構文オブジェクトで包まれてる部分を取り除いてやればよさそうだ。
問題はいくつかあって、
  • コンパイラは構文オブジェクトを見つけた際に、(組み込みかユーザー定義かの差はあるが)マクロ展開を行おうとする
  • もう一段階実行した際に、構文オブジェクト以外の部分がコンパイル実行されるため、たとえばシンボルをリストにしようとした際にVMがエラーを通知する
とりあえず、思いつくだけで2つか。まだありそうだが。前者はコンパイラに(また)条件式を追加する必要がある。(いやだなぁ)
後者はどうしようかねぇ。考えるか。

2011-03-02

プロジェクトホスティング

GoogleのCode Hostingサービスを利用しだした。
まだ、0.0.1にも届いていないが、ちょっと不便になってきたので。
場所はここです。興味があれば。
名前はSagittariusです。

未だにsyntax-caseの実装に戸惑っているが、とりあえず、組み込みのマクロはexplicit renaminig一本にして、syntax-rulesもライブラリとして提供するようにした。
おかげで、マクロ(syntax-rules)使うとコンパイルが遅いが・・・まぁ、それはそのうち何とかしよう。

基本的にsyntax-rulesのパターンマッチとsyntax-caseのパターンマッチは同じなはずなので、流用できるのではないかと考えている。
っが、Sagittariusのdefine-syntaxは与えられたフォームを特に構文オブジェクトにラップするということをしないので、(syntax '(let ...))とかって構文が困るんだよね。というか、syntax->datumとかdatum->syntaxとかその辺にかかわる手続きをどうしようかなぁと。syntax-case構文内だけで使うってことなら、er-macro-transformerのcompareとかrenameをごにょごにょすればいいかなとかも思うけど、そうもいかないよなぁ。

そうそう、
かっちょいいロゴ作ってくれる人募集中です。
あと、syntax-caseを実装してくれる方も絶賛募集中ですw

2011-03-01

まぁここは英語圏ではないのだが

Island Life - [i]と[iː] を読んでちょっと思ったこと。(1年前の記事だが)

[i]と[iː]の発音の癖が抜けない。つい、「イ」と「イー」で発音してしまう。実際は、音の長さではなく、[i]の方が「イ」と「エ」の中間、というかゆるい感じで、 [iː]は緊張した「イ」なんだけど、知識として知っててもうまく出てこない。

個人的に[i]と[iː]の違いは長母音と短母音の差だと思う。BBC Learning Englishにきっちりshort vowelsとlong vowelsに分けられているので。
少なくとも、上記のBBCから辿れる発音の講座で見られるビデオでは長いか短いかくらいしか差がないような気がする。
(個人的にイとエの中間は、eを逆さにした記号で表されるやつ(schwa)だともう。これは弱母音なので、慣れるまで発音が激ムズだった。日本語にない母音だし。あ、これはアとエの中間か?)

ここからは、単に僕の経験からなのだが、アメリカンとブリティッシュで母音の発音がかなり違うと思う。
(まぁ、聞けばすぐ分かるレベルでいろいろ違うので、言うまでもないのだが)
こっちに来てから毎年のようにフロリダに旅行で行っているのだが、そこでは、以前にも書いたが、僕の英語は割と通じない。それは、アメリカ人が自分たちの訛しか理解しないだけではなく、僕の話す英語がアメリカンではなくなってきていることにもあるのかもしれない。
(と言って、ブリティッシュというわけでもないが)
例えば、「hot」はこっちではそのまま「ホット」で通じるがアメリカ(南部訛?)だと「ハット」に近い感じにしないと店員さんは分かってくれない。
(前にも書いたなこのネタ)

そもそも、多国籍な会社で共用語として話される英語なので比較にならないが、正直だれもそんな発音の違いをつけてないと思う。ロンドン出身のイギリス人も会社にいるが、fishを「ふぇっしゅ」に近い発音で喋るようには聞こえないなぁ。

話がまとまらなくなってきた。結局言いたいことは、ここでは[i]と[iː]の違いは短いか長いかの差だということです。