Upgrade to Pro — share decks privately, control downloads, hide ads and more …

scheme-syntactic-quasiquote.pdf

Niyarin
October 31, 2019

 scheme-syntactic-quasiquote.pdf

Niyarin

October 31, 2019
Tweet

More Decks by Niyarin

Other Decks in Programming

Transcript

  1. syntax-rulesを楽に書くためのおもちゃ
    syntactic-quasiqoute
    Niyarin

    View full-size slide

  2. 今日の内容
    ・syntax-rulesのめんどうな部分を改善するライブラリの紹介
    ・R7RS-smallで解決することが目的
    ・syntax-case(R6RS/未来のR7RS?)の劣化版
    → ポータブル以外のメリットがないよ
    → おもちゃとしてはおもしろいね

    View full-size slide

  3. syntax-rulesとは
    (define-syntax cond-subset (else)
    ((_ (else body))
    body)
    ((_ (test1 body1) clauses …)
    (if test1 body1 (cond-subset clauses … )))
    ((_ (test body))
    (if test body))))
    syntax-rulesはパターンマッチを使うマクロシステム
    パターン
    テンプレート

    View full-size slide

  4. syntax-rulesとは
    (define-syntax cond-subset (else)
    ((_ (else body))
    body)
    ((_ (test1 body1) clauses …)
    (if test1 body1 (cond-subset clauses … )))
    ((_ (test body))
    (if test body))))
    テンプレートの識別子は基本的にパターン変数
    →任意のものにマッチングし、テンプレートで展開される
    リテラル
    (同じシンボルだけマッチする)
    “_ “も任意のものにマッチする(非展開用)が
    テンプレートで2回以上使用できる
    繰り返し

    View full-size slide

  5. syntax-rulesとは
    (define-syntax 1symbol?
    (syntax-rules ()
    ((_ a …)
    (let-syntax ((aux-syntax
    (syntax-rules %... ()
    ((_ a …)
    #t)
    ((_ _ %... )
    #f)))
    (aux-syntax symbol))))
    繰り返し記号は好きに変えられる
    →syntax-rulesがネストしたときに区別するため

    View full-size slide

  6. syntax-rulesのうれしくない点
    (define-syntax case
      (syntax-rules (else =>)
    ((case (key ...)clauses ...)
    (let ((atom-key (key ...)))(case atom-key clauses ...)))
    ((case key (else => result))
    (result key))
    ((case key(else result1 result2 ...))(begin result1 result2 ...))
    ((case key((atoms ...) result1 result2 ...))
    (if (memv key '(atoms ...))(begin result1 result2 ...)))
    ((case key((atoms ...) => result))
    (if (memv key '(atoms ...))(result key)))
    ((case key((atoms ...) => result)clause clauses ...)
    (if (memv key '(atoms ...))(result key)(case key clause clauses ...)))
    ((case key((atoms ...) result1 result2 ...)clause clauses ...)
    (if (memv key '(atoms ...))(begin result1 result2 ...)(case key clause clauses ...)))))
    パターンマッチは単純なので複雑なことをさせようとするとパターン数が多くなる
    面倒そうなsyntax-rulesコード

    View full-size slide

  7. syntax-rulesのうれしくない点
    (define-syntax rev-lambda
    (syntax-rules ()
    ((_ (args …) body)
    (lambda (rev-macro args …) body))))
    → lambda式の引数部は式ではないので展開されない
    (lambda (a b) (cons a b)) は (lambda (rev-macro a b) (cons a b))となるだけ
    外側から展開されていく性質上、
    2つ以上のマクロを組み合わせる場合CPSマクロが必要になることがある
    例) リストを反転するマクロを使って、lambda式の引数を反転させるマクロを作る
    (※rev-lambdaのやっかいな点はまだあって、後で出てきます)

    View full-size slide

  8. syntactic-quasiquote
    (define-syntax
    (syntax-rules ()
    ((_ …)
    (syntactic-quasiquote
    ))))
    ・syntax-rulesのラッパ
    ・CPSマクロが不要にする
    ・lispで式を操作できる
    syntactic-quasiquoteの定義
    syntacticテンプレート
    ・syntactic-unquote:中のsyntactic式を評価した結果をいれる
    ・syntactic-unquote-splicing:syntactic式を評価して括弧を1つ取っていれる
    ・リテラル:そのまま

    View full-size slide

  9. syntactic-quasiquoteの例
    (define-syntax like-let
    (syntax-rules ()
    ((_ bindings bodies ...)
     (syntactic-quasiquote
      ((lambda
         (syntactic-unquote
      (syntactic-map1
         syntactic-car
         (syntactic-quote bindings)))
      bodies ...)
      (syntactic-unquote-splicing
        (syntactic-map1
        (syntactic-lambda (x)
         (syntactic-car
         (syntactic-cdr x)))
      (syntactic-quote bindings)))))))))
    普通のlisp式のようにマクロがかけます。
    ※Schemeにリードマクロをください
     →syntactic-quoteとか
       syntactic-unquote-splicingとか
       かきたくない
    ※別の問題でletのようには動きません
    →正しく展開はされます

    View full-size slide

  10. syntax-rulesでLispもどき1
    (syntax-rules ()
    ((_ (a . b)) a))
    (syntax-rules ()
    ((_ a b) (a . b)))
    (syntax-rules ()
    ((_ (a … ) (b …)) (a … b …)))
    listの操作をsyntax-rulesでやる
    ○CAR
    ○CONS
    ○APPEND

    View full-size slide

  11. syntax-rulesでLispもどき2
    条件分岐:パターンマッチをそのまま使う
    (define-syntax sif
    (syntax-rules ()
    ((_ #t true-body false-body)
    true-body)
    ((_ #f true-body false-body)
    false-body)))
    関数呼び出し、ジャンプとかはlet-syntaxやdefine-syntaxを使う
    → このへんができればほぼ全ての処理はできる

    View full-size slide

  12. syntactic-quasiquoteの実装した機能
    syntactic-lambda
    syntactic-define
    syntactic-if
    syntactic-quote
    syntactic-quasiquote
    syntactic-unquote
    syntactic-unquote-splicing
    syntactic-map1
    syntactic-symbol?
    syntactic-equal?
    syntactic-car
    syntactic-cdr
    syntactic-cons
    syntactic-append

    View full-size slide

  13. Lisp評価器っぽいものはできるが..
    ・syntax-rulesはチューリング完全なのでLisp評価器っぽいものを動かすのは可能
    ・パターンマッチと展開の外側の部分でまだ問題が残っている
    → Hygienic macroに関する問題

    View full-size slide

  14. Hygienic macroの恩恵1
    (define-library (library a)
    ~略~
    (define-syntax my-let                      
    (syntax-rules (_)
    ((_ ((a b) ...) body)
    ((lambda (a ...) body) b ...))))
    ・ユーザーはlambdaを別のものに置き換えてしまった
    hygienic macroがないとどうなるのか?
    ・ライブラリ内でmy-letというマクロを作った。中でlambdaに変換ている
    (define lambda ‘AAAAAAAAAAA)                 
    (my-let ((a 1)(b 2)) (cons a b))
    → ((AAAAAAAAAAAA (a b) (cons a b)) 1 2) となってしまうと困る。

    View full-size slide

  15. Hygienic macroの恩恵2
    (define-library (library a)
    ~略~
    (define-syntax my-let
    (syntax-rules (_)
    ((_ ((a b) ...) body)
    ((lambda (a ...) body) b ...))))
    (define lambda ‘AAAAAAAAAAA)                 
    (my-let ((a 1)(b 2)) (cons a b))
    マクロを定義した環境と呼び出した環境で識別子の評価を自動的に分けてくれる
    syntax-rules内の識別子なので
    ライブラリの名前空間で評価される
    ユーザー側で定義した識別子は
    ユーザー側で評価される

    View full-size slide

  16. Hygienic macroは良い機能
    ・syntactic-quasiquoteと相性が悪いだけで良い機能
    ・自動ですべての識別子をきれいにするため、
    ・名前空間を意図的に汚すことができない
     → anaphoric macroとかはできない
    (define-syntax bad-aif
    ((syntax-rules ()
    ((_ test tbody fbody)
    (let ((it test))
    (if it tbody fbody))))))
    (define it ‘aaaaaaa)
    (aif (foo x) (car it) (cdr it)) ;syntax-rulesの外側なので中のitは見れない

    View full-size slide

  17. Hygienic macroとCPSマクロ1
    例) リストを反転するマクロを使って、lambda式の引数を反転させるマクロを作る
    (define-syntax rev-lambda
    (syntax-rules ()
    ((_ args body)
    (rev-syntax
    (syntax-lambda (it)
    (lambda it body))
    args))))
    CPS は Continuation Passing Style (継続渡しスタイル)のことです
    渡す継続
    結果をitに束縛して、
    lambdaの中に展開する

    View full-size slide

  18. Hygienic macroとCPSマクロ2
    渡した継続に結果を渡すところの抜粋
    (let-syntax ((cont-syntax
    (syntax-rules ()
    ((_ cont-args) cont-body))))
    (cont-syntax reverse-result))
    ・ (rev-lambda (a b) (cons a b))を与えたとすると
    cont-args:(a b) ← rev-lambdaのargs
    cont-body:(cons a b) ← rev-lambdaのbody
    → rev-lambdaのargsの(a b) とbodyの(cons a b)がsyntax-rulesの中と外で別れた
    仕様で決められた挙動ではないが(cons a b)が(a b)を刺さなくなる
    比較的メジャーな処理系はすべてこうなる

    View full-size slide

  19. 解決できませんでしたよ。
    symbolを最後に置き換えればいけると思ったが、hygienicな箇所を全部破壊す
    るので結局
    ・hygienic macroによって名前のバインディングを壊すか
    ・後処理でhygienicでなければならない場所も壊すか
    の選択になってしまった
    →今後は、この間をうまくやりたい。

    View full-size slide

  20. まとめと感想
    ・syntactic-quasiquoteは展開はうまくいくけど、hygenic-macroとの相性が悪かった
    → 名前空間の拘束をしないマクロだけならうまくうごく
    ・syntax-rulesはデバッグしずらい
    (macro-expand-1みたいなのが標準ではない、lispで評価するマクロではないから)
    → こんなおもちゃよりもマクロデバッガの開発したほうが世の中のためになった
    ・高速なsyntax-rules展開器を作りたくなった
    syntactic-quasiquoteは中で大量のマッチングと展開が行われる
    →syntactic-quasiquoteはそこそこ負荷のあるコードを生成できるので、
    ベンチマーク用に有効活用したい
    Github:https://github.com/niyarin/syntactic-quasiquote

    View full-size slide