!kiyoka.blog.2011_07 RSSPLAIN

Related pages: !kiyoka.blog.list
55555552222222222222222211220222333333333333333330333444444444444444444444444044455555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555505555555555555555555555555555555555555555555555555555555555555555550555555555555555555555555555555555555555555555555555555055555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555505
5

kiyoka日記。NendoSekkaの開発や、最近思うことなど

5

最新10件!kiyoka.blog   過去記事一覧!kiyoka.blog.list

5

kiyoka.blog_header 

5

このブログを書いている人: 西山 清香(kiyoka) - twitter: @kiyokaEXT

5

5

 

5

 

2

kiyoka.2011_07_31[Sekka] Sekka 0.8.8 リリース

2

SKKライクな日本語入力メソッド Sekka 0.8.8をリリースしました。(リリースノート Sekka.ReleaseNote)

2
 NbpKsE
2

 

2

version 0.8.8

2
gemの依存規則で、Nendoの必須バージョンを0.5.2のみとした。
2
 Nendo 0.5.1からNendo 0.5.2に変更することで、Nendo処理系が高速化する。
2
C-gで変換候補のリアルタイム表示を終了するようにした。
2
バグ修正
2
変換候補の中に漢字候補(type=j)が含まれない場合、候補選択ができないバグを修正した。
2
 後で平仮名から片仮名に選択し直そうと思ってもできない。
2
 例えば、「とらとらとら」を「toratoratora」で入力・確定したあと、Ctrl-Jで変換候補選択に入れない。
2

 

2

 

2

次の目標

2

gitのブランチにて「しています」「なりました」等の平仮名キーワードもタイプミス訂正をする機能を試してみています。

2

それが安定したら 0.9.0 としてリリースしようと考えています。

1

Nendo処理系のさらなる高速化とあわせて、もう少し反応速度も上げたいです。

1

特に、辞書登録がもっさりしているので、そちらを改善するのが先かな。

2

 

2

 

0

comment (disabled)

2

2

 

2

 

3

kiyoka.2011_07_30[Nendo] Nendo 0.5.2 リリース

3

Nendo 0.5.2をリリースしました。(リリースノート: Nendo.ReleaseNote)

3

rubygems_icon_128

3

リリースの目玉

3

高速化しました。

3
固定長引数の関数呼び出しは、末尾再帰呼び出しのトランポリンを経由せずRubyのメソッドを直接呼び出す最適化を行った。
3
 => ビルトイン関数の最適化のみに適用した。
3
 => tak関数で約6.5倍、長さ10000のリストのmap filter for-eachがversion 0.5.1に比べて4倍の高速化となった。
3

 

3

全体的に体感速度が良くなっています。

3

これはSekka 0.8.8の変換速度として体感することができます。(Sekkaは後日リリース予定)

3

 

3

 

3

次の目標

3

前回と変わらず、例外処理まわり何とかしたいです。

3

(参考:kiyoka.2011_06_24[Nendo] Nendo 0.5.1 リリース)

3

 

0

comment (disabled)

3

3

 

3

 

4

kiyoka.2011_07_14[プログラミング] コードゴルフならぬシステムゴルフ

4
 XuB7HT
4

ふと、自分のプログラミングに対する趣味・趣向について振り返ってみた。

4

今まで自分の作ってきた作品には、共通項があるんじゃないか。

4

その結果「なるべく短いコードでシステムを作ろうとしている」という共通項が見えてきた。

4

いうなれば、コードゴルフならぬ「システムゴルフ」。

4

 

4

私は実行速度にはあまり拘らないほうだ、それよりは、書いたコードが短く収まるほうを選ぶ。

4

そのために、少しくらい読みにくいコードがあってもかまわない。コード行数が圧縮されれば。

4

読みにくい部分が局所化されていれば。ある程度のトレードオフはあるが。

4

 

4

人のライブラリを使うのも厭わないほうだ、選ぶのにも時間をかける。結果的に自分が見る範囲のコードが短くなるのなら努力を厭わない。

4

最近はプログラミング言語処理系のNendo開発にまで手を出してしまったが、これも考えてみればシステムゴルフのためだと言えなくもない。

4

 

4

ライブラリが大量にあるRuby上にLispを構築したのは自分の潜在意識の中に「システムゴルフ」があるからだろう。

4

何かを作りたくなった時、MongoDBやRedisなど最新の技術を使ってみたくなる。最新の技術はとかくアプリケーション側のコードを短くするための工夫がされていることが多い。

4

それらは自分のシステムをなるべく短い行数でプログラムする努力を後押ししてくれる。

4

さらには、Lispのマクロによってコードを圧縮できる。

4

 

4

ただし、短いコードは意味が濃縮されているので、他人からはファーストインプレッションで拒否反応を起こされることもあるのが欠点。

4

実は拒否反応は括弧の多さ(見た目?)だけによるものかもしれないけど…

4

 

4

とにかく、自分は今後も「システムゴルフ」の上達を目指す姿勢は変わらないんだろうなぁと思う。

4

 

0

comment (disabled)

4

4

 

4

 

5

kiyoka.2011_07_13[Sekka] 平仮名フレーズ辞書を追加してみようかな(3)

5
 NbpKsE
5

先日来のエントリ 

5
 「kiyoka.2011_07_06[Sekka] 平仮名フレーズ辞書を追加してみようかな(1)」
5
 「kiyoka.2011_07_07[Sekka] 平仮名フレーズ辞書を追加してみようかな(2)」
5

の続き。

5

 

5

実際にWebコーパスの6-gramのデータの中から、文末に出てくる定型フレーズを集めるスクリプトを書いてみた。

5

文末で、且つ、平仮名のみで構成されている形態素を連結した文字列をSekka用フレーズとした。

5

文末は </S> (文境界マーク) で判断した。

5

 

5
 入力例
5
% が 加算 さ れ て    1152
5
% が 加算 さ れ ます   1463
5
% しか あり ませ ん </S>       1995
5
% だっ た そう です </S>       1554
5
% だっ た の が 、    1142
5
% だっ た の に対し 、  3496
5
% で 、 環境 問題 に   1898
5
% で あっ た が 、    2212
5
, と し て いる </S> 1007
5
, と 思い まし た </S>        1780
5

 

5
 出力例
5
しかありません    ;;  7 ;; ("!%" "しか" "あり" "ませ" "ん" "</S>" "1995")
5
だったそうです    ;;  7 ;; ("!%" "だっ" "た" "そう" "です" "</S>" "1554")
5
としている    ;;  5 ;; ("!," "と" "し" "て" "いる" "</S>" "1007")
5
ました    ;;  3 ;; ("!," "と" "思い" "まし" "た" "</S>" "1780")
5

 

5

 

5

プログラムはNendoで書いたが、非常に計算が重い。(処理系の問題)

5

Gaucheでも動くポータブルなコードにすべきだったかも。まあ何回も動かすわけではないからいいか。

5
#!/bin/sh
5
:; #-*- mode: nendo; syntax: scheme -*-;;
5
:; exec /usr/local/bin/nendo $0 $*
5
5
(use srfi-1)
5
(use sekka.roman-lib)
5
5
(define (hiragana-filter words)
5
  (define (hiras lst)
5
    (cond
5
     ((null? lst)
5
      lst)
5
     ((is-hiragana (car lst))
5
      (cons
5
       (car lst)
5
       (hiras (cdr lst))))))
5
     
5
  (let1 lst (cdr (reverse words))
5
    (reverse (hiras lst))))
5
5
5
(define (include-slash-s? lst)
5
  (any
5
   (lambda (x y)
5
     (and
5
      (string=? "</S>" y)
5
      (is-hiragana     x)))
5
   (cons "" lst)
5
   lst))
5
   
5
5
(define (grep-last-phrase filename)
5
  (with-open
5
   filename
5
   (lambda (f)
5
     (for-each
5
      (lambda (line)
5
        (let* ([lst   (to-list (line.chomp.split #/[ \t]+/))]
5
               [freq  (take-right lst 1)]
5
               [words (drop-right lst 1)])
5
          (when (include-slash-s? words)
5
            (let1 phrase (apply + (hiragana-filter words))
5
              (printf "%s    ;; %2d ;; %s\n"
5
                      phrase
5
                      phrase.size
5
                      (write-to-string lst))))))
5
      (f.readlines.to_list)))))
5
5
(define (main argv)
5
  (if (> 1 (length argv))
5
      (error "hiragana_phrase.nnd requires [6gram web corpus file]")
5
      (grep-last-phrase (car (to-list argv)))))
5

 

5

 

5

この処理のあと、「ー」が含まれるフレーズや、「っぁぃぅぇぉ」などで終わるフレーズは固い文章を書いている時に出てくると困るので、外している。

5

例えば、「どーも」とか「よろしくっ」とかが頻繁にサジェストされてると非常に困る。そういうくだけた表現は自分で故意に入力すればいい。

5

また、辞書の語彙として有意なものに限定するため 2文字から7文字までの長さのフレーズに絞り込んだ。

5

 

5

プログラムはこちら。

5
#!/bin/sh
5
:; #-*- mode: nendo; syntax: scheme -*-;;
5
:; exec /usr/local/bin/nendo $0 $*
5
5
(define (writing-phrase? str)
5
  (not
5
   (or
5
    (rxmatch #/ー/ str)
5
    (rxmatch #/[ぁぃぅぇぉゃゅょっー]$/ str))))
5
  
5
(define (writing-phrase-filter filename)
5
  (with-open
5
   filename
5
   (lambda (f)
5
     (for-each
5
      (lambda (line)
5
        (let* ([lst   (to-list (line.chomp.split #/[ \t]+/))]
5
               [word  (car lst)])
5
          (when (and (<= 2 (word.size))
5
                     (<= (word.size) 7)
5
                     (writing-phrase? word))
5
            (printf "%s  //\n" word))))
5
      (f.readlines.to_list)))))
5
5
(define (main argv)
5
  (if (> 1 (length argv))
5
      (error "writing_phrase_filter.nnd requires file as 'hiragana  ;; ....' ")
5
      (writing-phrase-filter (car (to-list argv)))))
5

 

5

ファイルから読みこんで1行ずつ処理するプログラムを書くことが多いので、もっと便利な関数を作りたくなってきた。

5

Gaucheに習って Nendo に port->string-list を作るかな。

5

あっ、そうか portの概念をどうする決めかねていて止まっているんだった。どうするかなぁ。

5

とりあえず これでお茶を濁してportとしてしまうかー。いいのかな。

5

 

5
  class LispPort < File
5
  end
5

 

5

 

0

comment (disabled)

5

5

 

5

 

5

kiyoka.2011_07_07[Sekka] 平仮名フレーズ辞書を追加してみようかな(2)

5
 NbpKsE
5

昨日のエントリ「kiyoka.2011_07_06[Sekka] 平仮名フレーズ辞書を追加してみようかな(1)」の続き。

5

平仮名フレーズ辞書の作りかただが、再度 矢田さんのウェブコーパスを調べてみた。

5
 日本語ウェブコーパス 2010EXTから引用
5
 本コーパスの作成においては,様々なウェブサービス,ツール,コーパスを利
5
 用させていただきました.開発者・研究者の皆様に感謝いたします.
5
 
5
   * コーパスの作成・保存・配布には Amazon Web Services を利用しています.
5
   * ウェブ検索には Yahoo! JAPAN 検索 Web API を利用しています.
5
   * ウェブコーパスのシードには IPAdic を利用しています.
5
   * 文字コードの変換には日本語用のパッチを適用した libiconv を利用しています.
5
   * Unicode の正規化には ICU を利用しています.
5
   * 形態素解析には MeCab を利用しています.
5
   * コーパスの圧縮には XZ Utils を利用しています.
5
   * 他にも様々なソフトウェアを利用しています.
5

 

5

なんか勘違いしていた。IPAdicとMeCabが使われている。

5

うまくフィルタリングすれば有用なデータが取れることがわかってきた。

5

 

5

Sekka用に一番集めたいフレーズは、「なりました」「しました」などの文末に出てくる定型フレーズ。自分としては使用頻度が高く、かつミスタイプでグダグダになりやすい傾向がある。

5

これは「なり」「まし」「た」などのような形態素を結合したものになるのだが、N-gramコーパスの 6-gramの </S> (文境界マーク) 付きの高頻度共起データから取れそう。

5

まず、</S>を見つけて、その直前の平仮名だけで構成される形態素を結合すれば良いかな。

5

例外として 「と」「の」「て」「に」「お」「は」 の形態素は捨てるべきかな? これは結果データを見てから決めよう。Sekkaのユースケースから考えると付いている/付いていないの両方があったほうがいいのかも。

5

 

5

「6mg-0000.xzの抜粋」

5
    .
5
    .
5
 % OFF と なり ます </S>     1222
5
 % OFF に なり ます </S>     2189
5
    .
5
    .
5
 " 監督 やり たい " </S>      1800
5
 " 現象 " で ある </S>       1319
5
    .
5
    .
5
 % 減少 し て いる </S>       1973
5
 % 減少 し まし た </S>       1714
5
 % 近く 上がり まし た </S>     3594
5
    .
5
    .
5
 % と なっ て いる </S>       27945
5
 % と なり まし た </S>       8012
5
 % な ん です か </S>        1184
5
 % に すぎ なかっ た </S>      1546
5
 % に すぎ ませ ん </S>       2356
5
 % に とどまっ て いる </S>     4109
5
 % に なっ て いる </S>       2149
5
 % に なり まし た </S>       3459
5
 % に も なり ます </S>       1126
5
 % に も 満た ない </S>       1974
5
 % に 上っ て いる </S>       1020
5

 

5

その次には「しかし」「もっとも」などの接続詞や副詞、や「こういう」などのような連体詞、その他使用頻度の高いフレーズが欲しい。

5

それは 1-gramから単純に取るのでいいのか、2-gramくらいから抜き出したほうがいいのか… これも推測より実験かな。

5

 

5

なんとかあまり労力をかけずにデータを集めれそうだ。いい時代になったなあと思う反面、便利すぎて大規模データに触れるチャンスが…

5

もっとデータの精度に拘れば、大規模データを自分で処理する必要が出るのだろう。

5

うーん。このままHadoopなどを使って大規模データマイニングをやるのが先延ばしになりそう。

5

 

5

COMMENTyoriyuki

Unicodeの正規化にicuを使っているとのことですが、具体的にどういう正規化なのか分かりますか?

5

COMMENTkiyoka

残念ながら、どういう正規化が行われているかはわかりませんでした。

ウェブコーパス作成におけるICUの利用目的も書いてありませんので、手がかりがありません。

ウェブマイニングする場合の最低限の正規化が組み込まれているのでしょうか。

5

COMMENTyoriyuki

なるほど、どうもありがとうございました。

0

comment (disabled)

5

5

 

5

 

5

kiyoka.2011_07_06[Sekka] 平仮名フレーズ辞書を追加してみようかな(1)

5
 NbpKsE
5

現在のSekka (version 0.8.7)は、漢字の語彙ならば、曖昧辞書検索を使ってミスタイプを補正してくれる。これがSKKに対するアドバンテージになっていると思う。

5

しかし平仮名のフレーズ、例えば「しました」とか「なっています」などのように平仮名のフレーズは辞書に無いのでミスタイプを救えない。

5

「なっています」のように少々長めのフレーズだとかなりミスタイプをしてしまう。まだ改善の余地がある。

5
 natteimas   (最後のuを入力せずに変換した例)
5

 

5

そこで、平仮名の入力モードにおいても、辞書にあるフレーズならば曖昧辞書検索で救済することを考える。

5

 

5

Sekka側の実装自体はそんなに手間では無いと思うが、辞書をどうやって入手するか、もしくは作るかが問題。

5

そんなおり、@nokunoEXTさんがこんな記事を書いてくださっていたので、ここから選ぶことにし

5

た。(NLP関係のリソースまとめ - nokunoの日記EXT)

5

 

5

ざっと中を見てみた。ライセンス的に使えなさそうなものは最初から除外した。

5

 

5

N-gram コーパス - 日本語ウェブコーパス 2010EXT

5

1-gramで頻度まで求めてくれているので非常に使いやすく、なんの加工もなしにフレーズ辞書に使えそう。

5

但し、日本語として美しくないものも大量に含まれていて、今回のアプリケーションには適さない。曖昧辞書検索で間違いを正そうとしているので、書き言葉でないフレーズに補正されるのはいただけない。

5

 

5

一部抜粋してみてみよう。

5
xz -cd ./nwc2010/ngrams/word/over999/1gms/1gm-0000.xz | head -1000
5
   .
5
   .
5
 ぁり     9140
5
 ぁりがと   4256
5
 ぁりがとぅ  4372
5
 ぁりがとぅござぃました    2241
5
 ぁりがとぅござぃます     3422
5
 ぁりがとう  1224
5
 ぁりがとうございます     1497
5
 ぁりがとぉ  3532
5
 ぁりました  1084
5
 ぁります   3322
5
 ぁりません  1482
5
   .
5
   .
5

 

5

 

5

IPAdicEXT

5

ここから美しい書き言葉の組み合わせを生成できそう。

5

ただ、付属のドキュメント ipadic-ja.pdfEXT を見た限りでは、かなりの作業がいりそう。

5

まあ形態素解析用の辞書から、全組み合わせのコーパスを作りだそうというのだから目的が違いすぎる。

5

ほかのデータを見てから再度検討。

5

 

5

 

5

と、ここまで来てふと考えた。

5

上記のコーパスの1-gramをIPAdicに付属のChasenで解析し、正しく解析しきれたものを採用すればいいのではないかな?

5

それなら頻度1000以上ではなく頻度100以上の1-gramに対して解析してもそんなに大量の語彙数にならないのではないか。

5

 

5

他にもWikipediaとかのコーパスもあるので、もう少し見てから決めよう。

5

 

0

comment (disabled)

5

5

 

5

 

5

kiyoka.2011_07_04[Nendo][Scheme] chibi-schemeのsyntax-rulesをポーティングする方法

5

オレ処理系のNendoについての開発メモ。

5
 SYKjVO
5

 

5

chibi-schemeのsyntax-rulesをポーティングした時のメモ。

5

Nendo 0.5.1時点での実装を解説となっている。

5

以下の実装で、chibi-scheme 0.3のsyntax-rules関連のテストケースが通り、さらに、match.scmも修正無しでテストケースをパスしている。

5

 

5

 

5

背景

5

NendoはRubyで書かれたScheme処理系である。サブセットではあるが。

5

syntax-rulesの処理は複雑で、自分で新しくコードを書くのはメンテが大変なので、できれば別の処理系で書かれたポータブルな実装を使いたい。

5

探した結果、chibi-schemeのsyntax-rulesがScheme自身で書かれたいて、機能的にもGaucheのライブラリを動かすのに十分であったため採用した。

5

ただ、ポータブルなSchemeで書かれているため、マクロ展開が重いという課題もあるが、Nendoではマクロ展開の処理速度には重きを置かない方針なので、とにかく動かすことを最優先とした。

5

 

5

 

5

chibi-scheme 0.3のsyntax-rulesについて

5

chibi-schemeのsyntax-rulesは、explicit macro transformerを使って書かれている

5
(define-syntax syntax-rules
5
  (er-macro-transformer
5
   (lambda (expr rename compare)
5
     (let ((ellipsis-specified? (identifier? (cadr expr)))
5
           (count 0)
5
           (_er-macro-transformer (rename 'er-macro-transformer))
5
 .
5
 (略)
5
 .
5
 .
5

 

5

従って、er-macro-transformerが正しく動くことが必須となる。

5

但し、syntax-rulesが使われる場所は、define-syntaxとlet-syntaxのルール記述部分になるので、処理系には大域的syntaxと局所的syntaxのマクロ展開の機能が必要になる。

5

Nendoは伝統的マクロの展開とあわせて3種類のマクロ展開をサポートしている。

5

Nendoのマクロ展開エンジンはRubyで書かかれている。

5

 

5

 

5

syntaxのサポート

5

syntaxは、マクロ展開フェーズで実行される特別なプロシジャである。

5

処理系の都合で、lambdaで定義されたプロシジャとは別の特別な型が使われることが多いようだ。

5

Gaucheでは #<syntax>という型が使われる。ユーザ定義関数は #<closure>となる。

5
$ gosh
5
gosh> if
5
#<syntax if>
5
gosh> (define (func1) #t)
5
func1
5
gosh> func1
5
#<closure func1>
5

 

5

Nendo(0.5.1)でも、それぞれ以下のように、それぞれNendo::LispSyntaxとProcという型が使われる。

5
$ nendo
5
nendo> syntax-rules
5
#<Nendo::LispSyntax:0x8d9b608@/usr/local/stow/..(略)../nendo.rb:23500>
5
nendo> (define (func1) #t)
5
#<Proc:0x8adf6e4@(stdin):9>
5
nendo> func1
5
#<Proc:0x8adf6e4@(stdin):9>
5

 

5

chibi-schemeでは、syntaxは構文エラーになってしまうがprocedureとは別ものとして扱われていることにはかわりない。

5
$ chibi-scheme
5
> if
5
ERROR: invalid use of syntax as value: if
5
> (define (func1) #t)
5
> func1
5
#<procedure func1>
5

 

5

これを実現するために、NendoではLispSyntaxという型をProcから継承して実装した。中身は空。

5
  class LispSyntax < Proc
5
  end
5

 

5

 

5

大域的syntaxのサポート

5

大域的syntaxはレキシカルスコープを意識しなくていいので、マクロ展開は非常に簡単だ。

5
 例:
5
(define-syntax cut
5
  (syntax-rules () ((cut args ...) (%cut #f () () args ...))))
5

 

5

上のように cut というシンタックスが定義された場合、マクロ展開される時は常にトップレベル環境を使って展開すればいいので、レキシカル環境は気にしなくていい。

5

 

5

 

5

局所的syntaxのサポート

5

一方、let-syntaxを使って定義される局所的syntaxは、定義されたレキシカル環境の中でマクロ展開される必要がある。

5
 R5RS日本語訳(PDF)EXT
5
 4.3.1. 構文キーワードのための束縛コンストラクト
5
 
5
 (let-syntax <束縛部> <本体>)
5
  構文: <束縛部> は次の形式をとること。
5
 ((<キーワード> <変換子仕様>) … )
5
 各<キーワード> は識別子である。各<変換子仕様> はsyntax-rules のインス
5
 タンスである。<本体> は1個以上の式からなる列であること。束縛されるキー
5
 ワードの並びの中に一つの<キーワード> が複数回現れることはエラーである。
5
  意味: let-syntax 式の構文環境をマクロで拡張することによって得られる構
5
 文環境の中で,<本体> が展開される。ただし,それらのマクロはそれぞれ<キー
5
 ワード> をキーワードとし,仕様が規定する変換子に束縛されている。各<キー
5
 ワード> の束縛は<本体> をその領域とする。
5
(let-syntax ((when (syntax-rules ()
5
                     ((when test stmt1 stmt2 ...)
5
                      (if test
5
                          (begin stmt1
5
                                 stmt2 ...))))))
5
  (let ((if #t))
5
    (when if (set! if 'now))
5
    if))
5
   => now
5

 

5
(let ((x 'outer))
5
  (let-syntax ((m (syntax-rules () ((m) x))))
5
    (let ((x 'inner))
5
      (m))))
5
   => outer
5
   ※ これがinnerとなっては駄目
5

 

5

これを実現するには、let-syntax された syntax は let で束縛された変数も考慮した環境を保持する必要がある。

5

 

5

 

5

マクロ展開に求められる要件

5

上記の仕様を満たすマクロをRubyのようなレキシカル変数を持った言語にトランスレートするには次の要件を満たす必要がある。

5

 

5
要件
5

局所的syntaxが展開される場所でも、let-syntaxで定義された場所のレキシカル環境を擬似的に復元する必要がある。

5

 

5
Nendoでの実現方法とそのメリット
5

マクロ展開エンジンはletとletrecで束縛されたレキシカル変数を全て抜き出しておいて、syntaxがRubyのメソッドとしてコンパイルされる時に、

5

レキシカル変数の環境をRubyのレキシカル変数の環境として復元する。

5

 

5

Nendo

5
(let ((x 'outer))
5
  (let-syntax ((m (syntax-rules () ((m) x))))
5
    (let ((x 'inner))
5
      (m))))
5

 

5

5

 

5

Rubyでのマクロ展開中イメージ(説明のためかなり簡略化している)

5
# 新しいS式に変換するマクロ m
5
m = LispSyntax.new {|expr,use_env,mac_env|
5
  # let-syntaxの定義環境で expr の式を展開して返す
5
  # 外部の環境が m の内部の x に影響を与えない。
5
  _tmp = lambda {|x|   # マクロ定義の環境を再現(start)
5
    x
5
  } ; tmp( :outer )    # マクロ定義の環境を再現(end)
5
}
5
5
# マクロ展開フェーズで、callProcedure部分が展開される
5
_tmp = lambda {|x|
5
  _tmp = lambda {x|
5
    callProcedure ( m,
5
      Cell.new,    # mの引数
5
      [].to_list,  # use_envは常に空
5
      # mac_envはグローバル変数、ローカル変数の全シンボルリスト
5
      (global_variables() + local_variables()).to_list
5
    )
5
  }
5
  _tmp( :inner )
5
}
5
_tmp( :outer )
5

 

5

Rubyへのマクロ展開結果(説明のためかなり簡略化している)

5

 

5
マクロ展開フェーズで、callProcedure部分が展開される
5
_tmp = lambda {|x|
5
  _tmp = lambda {x|
5
    :outer
5
  }
5
  _tmp( :inner )
5
}
5
_tmp( :outer )
5
   => 結果S式評価結果は outerとなる。
5

 

5

NendoではLispのlambdaをRubyのlambdaで置きかえる方式のため、上記のような方法でレキシカル環境をマクロ展開する場所に埋めこむ。

5

もし、VMのバイトコードに変換する方式を採用した場合は、環境フレームを処理系が管理するやりかたが恐らく一般的だろう。(chibi-schemeもそうなっている)

5

Nendoでは最大限Rubyとの親和性を担保しやすいこと、環境フレームを管理することの実行時オーバヘッドを軽減するという観点から、VM方式は採用していない。

5

 

5
デメリット
5

処理系が環境フレームを管理しない場合のデメリットは次の通り。

5

複数の場所で局所的syntaxが使われた時は、それぞれが別の環境になる。

5

環境フレームを自前で管理する方式では、同一のフレームは一つのインスタンスを共有することができるが、Nendoのようにマクロ展開する場所に環境を復元するコードを埋めこんでいく方式では、全て別々のインスタンスとなる。

5

 

5

これは、局所的syntaxが使われる回数によっては、展開後コードが肥大化するというデメリットがある。

5

 

5
注意点
5

マクロ展開の結果、let-syntaxを含むS式が変革される場合は注意が必要となる。

5

さらには、let-syntaxで定義したsyntax-rulesの<本体>部分でlet-syntaxで束縛したsyntaxを(再帰的に)使用している場合を考慮すると、環境復元コードも再帰的に展開されることになる。

5

そのため、環境復元コードの中に同一の変数が見つかったものをリダクションする必要がある。

5

 

5

いいかえると、結局のところ必要に応じて環境フレームの共有(融合)を行う必要がある。

5

 

5

 

5

er-macro-transformer(hygienic explicit renaming transformer)に必要な関数

5

chibi-scheme 0.3のREADMEに記載されているように、以下の関数を実装する必要がある。

5

 

5
 SYNTAX-RULES macros are provided by default, with the extensions from
5
 SRFI-46.  In addition, low-level hygienic macros are provided with
5
 a syntactic-closures interface, including SC-MACRO-TRANSFORMER,
5
 RSC-MACRO-TRANSFORMER, and ER-MACRO-TRANSFORMER.  A good introduction
5
 to syntactic-closures can be found at:
5
 
5
  http://community.schemewiki.org/?syntactic-closures
5
 
5
 IDENTIFIER?, IDENTIFIER->SYMBOL, IDENTIFIER=?, and
5
 MAKE-SYNTACTIC-CLOSURE and STRIP-SYNTACTIC-CLOSURES are provided.
5

 

5

Nendoで実装した関数と、その解説を行う。

5

VM方式のLisp処理系とは違った考慮が入っている可能性がある。そのため、VM方式を採用しているLisp処理系にはそぐわない可能性があることに注意すること。

5

 

5
er-macro-transformer
5

chibi-scheme 0.3のコードは全てlambda式で書かれており、意味が汲み取りにくい。

5

読みやすいように改変してはいるが、本質的な意味においては chibi-scheme 0.3のコードから改変なし。

5
;; readable code for nendo. (original code is chibi-scheme-0.3)
5
(define er-macro-transformer
5
  (lambda (f)
5
    (%syntax (expr use-env mac-env)
5
      (define (expander-main rename compare)
5
        (f expr rename compare))
5
      (define (_rename renames)
5
        (lambda (identifier)
5
          (let ([cell (assq identifier renames)])
5
            (if cell
5
                (cdr cell)
5
                ((lambda (name)
5
                   (set! renames (cons (cons identifier name) renames))
5
                   name)
5
                 (make-syntactic-closure mac-env '() identifier))))))
5
      (define (_compare x y)
5
        (identifier=? use-env x use-env y))
5
      
5
      (expander-main
5
       (_rename '())
5
       _compare))))
5

 

5
identifier?
5

シンボルとidentifierの区別は不要。

5
(define (identifier? x)
5
  (symbol? x))
5

 

5
identifier?
5

シンボルとidentifierの区別は不要なので、無変換。

5

別の型が入ってきたことに気づけるようにエラーチェックを入れているのみ。

5
(define (identifier->symbol id)
5
  (when (not (symbol? id))
5
    (error "Error: identifier->symbol requires only symbol"))
5
  id)
5

 

5
identifier=?
5

シンボルとidentifierの区別は無いため、シンボル同士のeq?でOK。

5
(define (identifier=? use-env-x x use-env-y y)
5
  (eq? x y))
5

 

5
make-syntactic-closure
5

Rubyで実装している。

5

リストは (syntax-quote ...) しか許さず、

5

シンボルで、マクロ環境で束縛された変数なら、その束縛を含むレキシカルフレームのラップ 「(let (let (let ...)))」 で包んで返し、

5

シンボルで、マクロ環境で束縛された変数でないなら、SyntacticClosureを返す。

5

上記以外の式は許さない。(型エラー)

5

これにより、chibi-schemeのsyntax-rulesでrenameされるシンボルはマクロ環境の束縛状態においてidentifierの健全性が保たれる。

5
    def _make_MIMARKsyntactic_MIMARKclosure( mac_env, use_env, identifier )
5
      if _pair_QUMARK( identifier )
5
        if :"syntax-quote" == identifier.car
5
          identifier
5
        else
5
          raise TypeError, "make-syntactic-closure requires symbol or (syntax-quote sexp) only. but got: " + write_to_string( identifier )
5
        end
5
      elsif _symbol_QUMARK( identifier )
5
        if mac_env.to_arr.include?( identifier.intern )
5
          found = @lexicalVars.find { |x| identifier == x[0] }
5
          if found
5
            lexvars = @lexicalVars.clone
5
            __wrapNestedLet( identifier, lexvars )
5
          else
5
            identifier
5
          end
5
        else
5
          SyntacticClosure.new( identifier, (toRubySymbol( identifier ) + _gensym( ).to_s).intern )
5
        end
5
      else
5
        raise TypeError, "make-syntactic-closure requires symbol or (syntax-quote sexp) type."
5
      end
5
    end
5

 

5

ちなみに、SyntacticClosureは次のように定義され、Nendo独特のトリックが使われてる。

5

SyntacticClosureはリネーム前のシンボルと、リネーム後のシンボルが保持されており、

5

マクロ展開後に、quoteされたSyntacticClosureは、originalSymbol側が使用され、quoteされずに変数名として使用されるとgensymでリネームされた(健全性が保たれた)ほうが使用される。

5
  class SyntacticClosure
5
    def initialize( originalSymbol, renamedSymbol )
5
      @originalSymbol = originalSymbol
5
      @renamedSymbol  = renamedSymbol
5
    end
5
5
    def to_s
5
      @renamedSymbol.to_s
5
    end
5
5
    def intern
5
      @renamedSymbol
5
    end
5
5
    attr_reader :originalSymbol, :renamedSymbol
5
  end
5

 

5
strip-syntactic-closure
5

Rubyで実装している。

5

リストをトラバースしてSyntacticClosure型が見つかったら、sexp.internでリネームされた側のSymbolに変換する。

5
    def _strip_MIMARKsyntactic_MIMARKclosures( sexp )
5
      case sexp
5
      when Cell
5
        if _null_QUMARK( sexp )
5
          sexp
5
        else
5
          Cell.new(
5
                 _strip_MIMARKsyntactic_MIMARKclosures( sexp.car ),
5
                 _strip_MIMARKsyntactic_MIMARKclosures( sexp.cdr ))
5
        end
5
      else
5
        if sexp.is_a? SyntacticClosure
5
          sexp.intern
5
        else
5
          sexp
5
        end
5
      end
5
    end
5

 

5

 

5

再帰的にマクロ展開する箇所

5

マクロ展開エンジンで下記の箇所を再帰的にマクロ展開しなければならない。

5
 (let-syntax
5
   ((<シンタックス1> (syntax-rules (ellipse)
5
            ((<マッチングルール1-1> <展開テンプレート1-1>)
5
             (<マッチングルール1-2> <展開テンプレート1-2>))
5
            ...))
5
    (<シンタックス2> (syntax-rules (ellipse)
5
            ((<マッチングルール2-1> <展開テンプレート2-1>)
5
             (<マッチングルール2-2> <展開テンプレート2-2>))
5
            ...)))
5
  <本体>)
5

 

5

<展開テンプレート1-1>、<展開テンプレート1-2> を <シンタックス1>の定義と大域シンタックスを使ってマクロ展開する必要がある。(再帰的マクロ展開も考慮すること)

5

<展開テンプレート2-1>、<展開テンプレート2-2> を <シンタックス2>の定義と大域シンタックスを使ってマクロ展開する必要がある。(再帰的マクロ展開も考慮すること)

5

<本体>を<シンタックス1>の定義、<シンタックス2>の定義、大域シンタックスを使ってマクロ展開する必要がある。

5

 

5

 

5

制限事項

5

上記の方式ではletrec-syntaxはサポートできていない。

5

※ 現時点で、chibi-schemeとGaucheのライブラリを確認したところ、letrec-syntaxを使ったライブラリは無かったため、Nendoではサポートは保留とした。

5

 

5

 

5

まとめ

5

schemeで書かれた、ポータブルなsyntax-rulesを、Rubyのようなレキシカルスコープを持つ言語にトランスレートする方法を解説した。

5

簡略化しているもののchibi-scheme 0.3のsyntax-rulesをポーティングする際に実装が必要となる関数群をNendoの実装例とともに解説した。

5

 

5

 

0

comment (disabled)

5