Island Life

2015/01/25

生物の分類

子供の宿題が難しい。"Protista"って何? "Eubacteria"って? と聞かれて 調べ始めたんだけど、なんか諸説入り乱れててわけがわからなくなりそうだ。

教材では次の6つに分類しているので、Woeseの六界説だな。

  • Eubacteria (真正細菌)
  • Archaea (古細菌)
  • Protista (原生生物)
  • Fungi (菌類)
  • Plantae (植物)
  • Animalia (動物)

落ち着いて考えてみたら自分らの時は確か五界説で、要は最初のふたつを 分けてなかっただけか? でも「モネラ界」なんて名前だったかなあ? (原核生物、と習ったような気もする。)

Tags: 生活, 教育

2015/01/23

入れ子のバッククオート

自分も良く迷うんだけど、一応簡単な指針がある。

まず、コンマのネストレベルとバッククオートのネストレベルを常に合わせるようにする。 次は良くない例:

    ``(list ,(+ 1 2))

これは(list ...)が2重のバッククオートの中にあるのに中でコンマを1重にしか使ってない。 規約上はバッククオートよりコンマのネストが少なければ動作はするけど、わかりづらくなるので避ける。

次に、「どのレベルのバッククオートで式を展開したいか」を考えて、 機械的に次の指針をあてはめる:

  • 最初のレベル(外側)のバッククオートで式を展開したい場合はこうする:
    ,',式
  • 最初のレベルでは展開せず、二番目(内側)のバッククオートで展開したい場合はこうする:
    ,,'式

ネストしたバッククオートが出てくる典型例はマクロ定義を生成するマクロだけれど、 その場合、最初のマクロ展開で埋め込みたい式は前者、生成されたマクロ定義の展開時に コンマとして機能させたい場合は後者を使う。

ちょっと手元に例として引けるコンパクトなコードがないので、わざとらしい例だけど、 例えば

(defmacro foo (x) `'(foo ,x))

みたいなマクロを名前を指定して生成するマクロ

(defmacro gen-defmacro (name) ...)

を考えよう。(gen-defmacro foo) とすると上の(defmacro foo ...)が生成される。(gen-defmacro bar)とするとこれが生成される:

(defmacro bar (x) `'(bar ,x))

まず、ここまではすぐ書けるだろう:

(defmacro gen-defmacro (name)
  `(defmacro ,name (x) `'(...)))

ここのnameはまだバッククオートが1重なので迷わない。問題はバッククオートがネストする...の部分だ。

  • nameはgen-defmacroの展開時に埋め込みたい。つまり外側のバッククオートで展開
  • xは生成されたdefmacroの展開時に埋め込む、つまり内側のバッククオートで展開

上の指針をあてはめればこの通り。

(defmacro gen-defmacro (name)
  `(defmacro ,name (x) `'(,',name ,,'x)))

実行例

[1]> (defmacro gen-defmacro (name)
      `(defmacro ,name (x) `'(,',name ,,'x)))
GEN-DEFMACRO
[2]> (gen-defmacro foo)
FOO
[3]> (gen-defmacro bar)
BAR
[4]> (foo (a b c))
(FOO (A B C))
[5]> (bar (d e f)
(BAR (D E F))

3重以上のネストも基本的に同じ。ポイントは comma-quoteが互いにキャンセルするということ。後は右側から展開されてく。

    ,',',式   ; 一番外側のバッククオートで展開
    ,',,'式   ; 二番目のバッククオートで展開
    ,,','式   ; 三番目(最も内側)のバッククオートで展開

なお、バッククオートのネストレベルはコンマがあると下がるので、次のようなコードは ネストとは考えない:

    `(list ,(list `(list ,x)))

外側のバッククオートはすぐ次のコンマと対応するんで、2番めのlistの式はネストレベルが0になる。

Tags: Programming, CommonLisp, Macro

2015/01/17

簡単で直交性の高い道具を組み合わせる

Explicit-renaming macroは何をやっているかが透明でわかりやすいのだけれど、 それだけでマクロを書くのは面倒だ。

Gaucheの低レベルマクロ機構で出したswap!マクロをもう一度取り上げてみよう。

まず、高レベルマクロのsyntax-rules版。 パターン置換だけで話が済むなら大抵はこれが一番簡潔。

(define-syntax swap!
  (syntax-rules ()
    [(_ a b)
     (let ((tmp a))
       (set! a b)
       (set! b tmp))]))

swap!マクロならこれでいいんだけど、マクロ展開時にパターン置換だけではできない 計算をやろうとすると高レベルマクロでは限界がある。

syntax-case は高レベルマクロの「パターンマッチによる入力の分解機能(1)」および 「マクロ出力の組み立てで適切なリネーミングを行う機能(2)」をそのままに、 (1)と(2)の間に任意のScheme式による計算を入れることを可能にしたものだ。

(define-syntax swap! 
  (lambda (stx) 
    (syntax-case stx () 
      [(_ a b)
       (syntax 
        (let ((value a)) 
          (set! a b) 
          (set! b value)))])))

この例では入力の分解と出力の組み立ての間にやる計算がないので あまり有難味はないけれど、必要なら取り出したaやbを使って複雑なことができる。

一方、ER macroは、hygienic macroに必要な最低限の機能、 つまり「環境を考慮した識別子の比較とリネーム」しか提供しない。 入力の分解と出力の組み立ては自分でやる必要があり、これはかなり面倒くさい。

(define-syntax swap!
  (er-macro-transformer
   (^[f r c]
     (let ([a (cadr f)]
           [b (caddr f)])
       `(,(r'let) ([,(r'value) ,a])
          (,(r'set!) ,a ,b)
          (,(r'set!) ,b ,(r'value)))))))

けれども、入力の分解や出力の組み立てというのは、単なるS式の操作なのだから、 マクロシステムでわざわざ用意しなくても、一般のS式操作ライブラリを使えば良いともいえる。

パターンマッチによるS式の分解についてはGaucheはmatchを持っている。

出力の組み立てについては、「指定の式の識別子をリネームしつつ、必要な箇所に 計算結果を埋め込む」ようなマクロがあればよい。

(define-syntax quasirename-sub
  (syntax-rules (unquote unquote-splicing)
    [(_ rename ()) ()]
    [(_ rename (unquote x)) x]
    [(_ rename ((unquote-splicing x) unquote y)) (append x y)]
    [(_ rename ((unquote-splicing x) . y)) (append x (quasirename-sub rename y))]
    [(_ rename (x unquote y)) (cons (quasirename-sub rename x) y)]
    [(_ rename (x . y)) (cons (quasirename-sub rename x)
                              (quasirename-sub rename y))]
    [(_ rename x) (rename 'x)]))

(define-syntax quasirename
  (syntax-rules ()
    [(_ rename form)
     (let ([xrename (lambda (x)
                      (if (or (symbol? x) (identifier? x))
                        (rename x)
                        x))])
       (quasirename-sub xrename form))]))

これを使うと、ER macro版は次のとおり。

(define-syntax swap!
  (er-macro-transformer
    (^[f r c]
     (match-let1 (_ a b) f
       (quasirename r
        (let ((tmp ,a))
          (set! ,a ,b)
          (set! ,b tmp)))))))

これで手間としてはsyntax-case版と変わらない。しかも、

  • 入力の分解部分は普通のS式のパターンマッチャであり、 マクロ特有の事情を考慮する必要はない。 より強力なパターンマッチャを書けばそれを使うこともできる。
  • 出力の構築部分はquasiquoteと同じセマンティクスである。 パターン変数が特別扱いされていないことに注目。aやbは単なるローカル変数束縛 なのだから、unquoteでその値が取り出せる、それだけである。 もちろんquasirename単独で使うこともできる。例えば識別子にプレフィクスを つけつつ、計算結果を埋め込みたい、とか。

syntax-casesyntax, quasisyntaxも裏では 似たようなことをやってるんだけど、マクロ特有のコンテキストを構文オブジェクトに 隠して持ち運んでいるので、応用が効かない。

(注: ここのquasirenameの定義だと、出力にunquoteやunquote-splicing を含めるのが難しいので、unrenameとか別の名前を使った方が良いかもしれない。)

Tags: Programming, Gauche, Scheme, Macro

2015/01/12

Waldstein

アマチュアのピアノ愛好家有志による新年コンサートでヴァルトシュタインソナタを 弾かせてもらった。 これも子供の時から弾きたかった曲なのだ。

細かいところは相変わらずほつれているけど、 止まるとか余分な繰り返しみたいな大きな破綻は無かったかな。 (何か所か非常にやばい瞬間があるが…)

「芝居の時はあがらないのに演奏であがるのはなぜか」というのが ここ3~4年くらい考えていたテーマのひとつなんだけど、 今回また新たな知見が得られた。ような気がする。

舞台に立っている時の心理状態に、「入る」というのがある。 芝居が作り出す「場」に浸かっている状態。 マイズナーによる演技の定義は "Reacting truthfully to the imaginary circumstances" だが、 この "imaginary circumstances" が確固たるリアリティを持って 感じられている状態。この時はあがることがない。

舞台進行上、しかるべききっかけにしかるべき場所にいる、とか、 台詞を外さない、とかテクニカルに追わなくちゃならないことはたくさん あるんだけど、 場に対して素直に反応していれば演技が壊れることはないので、 そういうテクニカルな細々したものに振り回されることがなく、 むしろ余裕を持って対応できる。

(場に浸かっているからといって観客やら舞台装置やらが見えなくなるということはなく、 むしろそれらは明晰に見えていて、観客の反応や舞台装置の故障にも冷静に対応できる。)

芝居の訓練の多くは、必要なときに「入った」状態になるために 必要な準備の習得やそのための道具を揃えること、と言えるかもしれない。

演奏でも、音楽に「入る」状態があるようだ。出す音は全部意識してるんだけど、 何をどうすれば良いのかが自明な状態で、だから(脳からフィードフォワードで がちがちに制御するんではなく)出るべき音に身体をなるべく沿わせてやる感じ。

人前での演奏で上がりまくるというのは、うまく音楽「入る」ことができずに、 テクニカルなことを全て頭で制御しようとして、処理能力がパンクしているって ことかもしれないと思った。

今回も冒頭ではちゃんと入ってなくて、頭をフル回転させて制御しようとしてるんだけど、 第2主題の右手3連符のセクションに入るところの上行パッセージ(42小節目)、 処理が回らなくなってどの音を弾いてよいんだかわからなくなったことを覚えてる (んで、最後の拍は弾いてない)。 ただまあ、以前とは違って自分がそういう状態にあることは分かってたんで、 パニックにならずにどうにか前に進めたけど。

どこらへんかなあ、1楽章の展開部ではある程度入ってたのを覚えてる。 1楽章のコーダも間違いやすいんで普段は緊張するんだけど、 今回はかなり落ち着いてやれた。273小節1拍目の左手装飾音のAを外して、そのせいで 3拍目の左のスタート位置がわからなくなったんだけど、 ここは上のメロディさえ外さなければなんとなるだろ、と思いながら弾いてたり。

3楽章の難しいところで何度も抜けかけたけど、全体的に 鍵盤に大きな穴が開いてその向こうの世界に入り込む感覚がつかめつつあるので、 しばらくその感覚を追っかけてみよう。

Tags: Piano, 芝居

2014/12/01

Gaucheの低レベルマクロ機構

(Lisp Advent Calendar 2014参加エントリ)

まだ不完全だが、GaucheのHEADにExplicit renaming macro (er-macro)を入れた。 これから徐々にレガシーマクロ (define-macro) で定義されているマクロを 置き換えてゆく予定。 (ただ、0.9.4ではdefine-syntaxフォームをプリコンパイルできないので、 組み込みマクロを本格的に置き換えるのは0.9.5リリース以降になる。)

低レベルマクロ、つまりマクロ展開にScheme自身を使えるマクロシステムは いくつも提案されている。R6RSにはそのうちのひとつであるsyntax-caseが入った。 syntax-case マクロはパターンマッチによる 入力フォームの分解が使えるし、出力も特に凝ったことをしなければ syntax で囲むだけなので、使い勝手だけを考えるならおそらく最良だ。

例えばふたつの変数の値を入れ替える swap! マクロは syntax-case ではこうなる。

(define-syntax swap! 
  (lambda (stx) 
    (syntax-case stx () 
      ((_ a b) 
       (syntax 
        (let ((value a)) 
          (set! a b) 
          (set! b value))))))) 

一時変数valueは自動的にリネームされるので、 マクロ呼び出し側で (swap! value x) のように呼び出しても干渉することはない。 またマクロ展開結果に挿入されるletおよびset!も、 マクロ定義時に見えている束縛 (ここではグローバルな束縛が見えているので、 Schemeでの標準的な意味) を参照するようになっているので、次のように マクロ使用環境でset!等が別の意味に束縛されていても問題ない。

(let ((set! list))
  (swap! x y))

さてこのswap!をer-macroで書くとこうなる。

(define-syntax swap!
  (er-macro-transformer
   (^[f r c]
     (let ([a (cadr f)]
           [b (caddr f)])
       `(,(r'let) ([,(r'value) ,a])
          (,(r'set!) ,a ,b)
          (,(r'set!) ,b ,(r'value)))))))

行数こそさほど変わらないものの、syntax-case に比べかなり面倒くさい。 何をやっているかというと、

  • fにはマクロ呼び出しフォーム全体がS式で渡ってくる。
  • マクロ引数のaやbにあたる式は自力で取り出す必要がある。
  • 出力を組み立てる際に、マクロが挿入する識別子はすべてrename手続き (上のコードではr) を通して陽にリネームする。 リネームというのは若干誤解を招くんだけど、letset! は 単にシンボルの名前を変えるんじゃなくて、どうにかして「トップレベルで見えているletset!を確実に参照できる名前」になる、と考える。

いちいちこんなふうにマクロを書くのは実用上は大変面倒なのだけれど、 「マクロが何をやっているかが明示されている」という意味では もっとも明確だ。Gaucheがer-macroを低レベルマクロの一番の基礎に 置こうとしているのは、動作が目に見えるプリミティブな部品をまず用意して、 便利な機能はそれを組み立てて作る、と考えているからである。

実際、入力フォームの分解部分はutil.matchを使えばパターンマッチでいけるし、 出力の組み立て部分についても、「マークされたもの以外の識別子を全部renameする」 といった上位のユーティリティを提供して、実用コードはそれらを使って書いてもらう予定。

syntax-caseでは、パターンマッチ部分が 「環境情報でラップされた入力フォームを必要なだけアンラップする」という 動作と密都合されてしまっているので、util.matchのような 外部のS式マッチャを使うことは出来ないし、syntax-caseのパターンマッチャ部分だけを 取り出して普通の手続きの中で使うこともできない。 このせいで、syntax-caseは、まるで 汎用的なレゴの中に一個だけ巨大な単一用途のブロックが混ざってくるような 感じになってしまっている。それがGaucheでsyntax-caseを避けた理由。

★ ★ ★

ところで、上で 「トップレベルで見えているletset!を確実に参照できる名前」に リネームされる、と書いたが、具体的にどうリネームされるのだろうか。 実は、rename手続きだけで頑張っても無理で、処理系自体の何らかのサポートが必要になる。 Schemeの衛生マクロ(hygienic macro)が何となくつかみ所が無いのは、 このへんが処理系に任されていて、具体的なイメージを持ちにくいからだろう。

例えば処理系が、トップレベルの束縛についてはletset!等の 単純な名前だけでなく、モジュール名を付加した scheme.base/letscheme.base/set! のような別名を用意するようになっていれば、 ここでのrename手続きはletscheme.base/let にリネームする、 といった動作になる。

Clojureのバッククオートでリテラルシンボルがnamespace qualifiedなシンボルに 変換されるのは、まさに上記のようなリネームをやっているわけだ。ローカルマクロが 無い場合、マクロが挿入する自由変数はすべてトップレベルへの参照になるので、 それで十分なのだ。(ローカルマクロが入ってくると、トップレベルだけでなく マクロ定義の外側にあるローカル変数を参照するケースがありえるので、 Clojure方式はそのままでは応用できない。)

Gaucheでは、シンボルをidentifierというオブジェクトでラップして、 モジュールや環境情報を付加することで擬似的にリネームを実現している。

Tags: Programming, Gauche, Macro

More entries ...