7

マクロではなく関数に関する関連する質問のコメント スレッドに触発されました。

新しい定義で構文の以前の定義を使用できるように、Scheme 構文定義を拡張する方法はありますか? さらに、これは拡張可能である必要があります。つまり、技術を複数回連鎖させることが可能でなければなりません。

たとえば、でlambda定義された関数が呼び出されるたびにlambda、関数本体を実行する前に「foo」を出力するように拡張したいとします。これは次の方法で行うことができます。

(define-syntax old-lambda lambda)

(define-syntax lambda
  (syntax-rules ()
    ((_ args body ...)
     (old-lambda args (display "foo") body ...))))

次のようにして、これを別の方法で拡張することもできます (たとえば、「バー」を印刷することによって)。

(define-syntax old-lambda-2 lambda)

(define-syntax lambda
  (syntax-rules ()
    ((_ args body ...)
     (old-lambda-2 args (display "bar") body ...))))

最終的に、 new で定義された関数lambdaは、呼び出されるたびに「foo」、次に「bar」を出力します。

ただし、名前空間を大量の で汚染するだけでなく、これを行うたびにソース コード レベルでold-lambda-<x>新しいものを作成する必要があります。たとえば、構文定義でもold-lambda-<x>使用できないため、これを自動化することはできません。gensymしたがって、これを拡張可能にする良い方法はありません。唯一のもっともらしい解決策は、それぞれに名前を付けるold-lambda-print-fooか、あいまいさを解消するのに似たものにすることですが、これは明らかに確実な解決策ではありません。(これがどのように失敗するかの例として、コードの2つの異なる部分がlambda「foo」を出力するように拡張されたとします。当然、両方とも名前が付けられold-lambda-print-foo、voila!lambdaは無限ループになります。)したがって、非常に理想的には次のような方法でこれを行うことができれば素晴らしいです。

  • 多くの名前空間を汚染する必要はありませんold-lambda-<x>
  • または、それが失敗すると、衝突が発生しないことが保証されます。
4

1 に答える 1

1

Racket では、モジュールを使用してこれを行うことができます。Racket を除く Racket 言語全体を再エクスポートするモジュールを作成し、lambda新しいマクロを という名前でエクスポートできますlambda。コードを整理する 1 つの方法を示します。

foo-lambdaモジュールはフォームを定義してエクスポートし、適用foo-lambda時に「foo\n」を出力するプロシージャを作成します。

(module foo-lambda racket
  (define-syntax-rule (foo-lambda formals body ...)
    (lambda formals (displayln "foo") body ...))
  (provide foo-lambda))

このracket-with-foo-lambdaモジュールは、Racket 言語全体を再エクスポートしますfoo-lambdaが、名前で提供するものは除きますlambda

(module racket-with-foo-lambda racket
  (require 'foo-lambda)
  (provide (except-out (all-from-out racket) lambda)
           (rename-out [foo-lambda lambda])))

これで、この「新しい言語」でモジュールを書くことができます:

(module some-program 'racket-with-foo-lambda
  (define f (lambda (x) x))
  (f 2))
(require 'some-program)

これはの Racket バージョンを変更lambdaせず、他の Racket フォームはまだ Racketlambdaバインディングを使用していることに注意してください。たとえば、f上記の定義を のように書き直した場合(define (f x) x)、Racket のdefineは Racket の の使用に展開され、「foo」の出力は得られlambdaません。

拡張機能を連鎖させることができます。各拡張機能は、以前のバージョンをインポートするモジュールで定義されています。たとえば、bar-lambdaモジュールはモジュールをインポートしfoo-lambdaます。

実際、Racket はこれを内部的に行っています。コンパイラlambdaは位置引数のみを理解しますが、Racket 言語には位置引数lambdaとキーワード引数の両方をサポートする があります。lambdaRacket 言語の実装には、組み込みのand #%app(関数アプリケーション構文を処理するために暗黙的に使用される) を、キーワード引数を処理するバージョンに置き換えるモジュールがあります。

于 2014-05-28T15:06:46.500 に答える