2012-11-14

この発想はなかった

Chibi-SchemeのIssue 149にあった面白い発想のdefineをsyntax-caseで実装してみた。
(import (rnrs))
#|
;; ※1
;; 投稿した2分後にsyntax-case一回だけでいけることに気づいた。
;; なので、コード直下のぼやきはこのコードに対してのもの。
(define-syntax define-function
  (lambda (x)
    (define (compose expr)
      (let loop ((expr expr))
        (syntax-case expr ()
          (((name . formals) body ...)
           (identifier? #'name)
           #'(define name (lambda formals body ...)))
          (((name&formals . rest) body ...)
           (not (identifier? #'name&formals))
           (loop #'(name&formals (lambda rest body ...)))))))
    (syntax-case x ()
      ((_ exprs ...)
       (compose #'(exprs ...))))))
|#
;; すっきしたバージョン。これなら処理系関係ない。
(define-syntax define-function
  (lambda (x)
    (syntax-case x ()
      ((k (name . formals) body ...)
       (identifier? #'name)
       #'(define name (lambda formals body ...)))
      ((k (name&formals . rest) body ...)
       ;;(not (identifier? #'name&formals)) ;; この行要らない
       #'(define-function name&formals (lambda rest body ...))))))

(define-function ((f x) y) (+ x y))

(print ((f 1) 2))
多少以上に余計な手間をかけているのだが、マクロ周りがR6RS準拠な処理系と違ってSagittariusでは識別子のリネームに制限がある。それは、同一のsyntax構文内でリネームを行わないと識別子が同じにならず、unbound variableエラーを投げるというもの。なので、上記は本来ならquasisyntaxを使ってもう少し分かりやすく書けるのだが(まぁ、どちらが分かりやすいかは人によるが)、Sagittariusで動かすために式を全部一緒に処理してやる必要がある。(※1のコード)

この制限はずっと気づいているし、問題だと思っているのだが、いかんせんスマートな解決方法が思い浮かばないのと、慣れてしまえば別に気にならないので放置してある。0.4.0辺りまでに直したいという思いはあるが、あるだけともいえる・・・(マクロ周りは本当に鬼やぜ)

さて、ようやく本題。上記のマクロがどのように展開されるかというのが個人的には面白いなぁと思ったという話。Chibiのissue見れば答えは載っているのだが、引数の個数にも寄るが、この構文簡易カリー化と取れなくもない。上記の手続きfは以下のように展開される。
(define f (lambda (x) (lambda (y) (+ x y))))
たとえば、SXPathはこんな感じの手続きをころころ作るのだが、実装を見てみると(当然なんだけど)明示的にlambdaをいっぱい書いている。でも、これ使えばそのわずらわしさから開放される!というわけだ。

僕自身、あまりそんな使い方しないんだけど、マクロ使えばこんなこともできちゃう!というちょっとした例。

2012年11月15日 追記
実はこのマクロを書く必要もなく、Sagittariusでは上記の形式がサポートされている。理由は実に簡単で、defineはコンパイル時に以下のように展開されるから。
;;; 単純バージョン
(define (f x) x)
;; -> (define f (lambda (x) x))

;;; ネストした名前と引数バージョン
(define ((f x) y) (+ x y))
;; -> (define (f x) (lambda (y) (+ x y)))
本来なら後者は束縛名の部分がsymbolもしくは識別子ではないというエラーにならないといけないんだけど、手抜きでそのまま展開しちゃうからエラーにならない。mosh、Ypsilon、Chezなんかはまじめにチェックするので、エラーになる。

RnRS(nは5, 6, 7)的には正しくないが、普通に動くわけだし、わざわざチェック入れてパフォーマンスを落とす必要もないと思うので放置する。

No comments:

Post a Comment