12

マクロ展開には副作用がありますか (またはすべきですか)? たとえば、コンパイル時に実際に Web ページのコンテンツを取得するマクロを次に示します。

#lang racket

(require (for-syntax net/url))
(require (for-syntax racket/port))

(define-syntax foo
  (lambda (syntx)
    (datum->syntax #'lex
                   (port->string
                     (get-pure-port
                       (string->url
                         (car (cdr (syntax->datum syntx)))))))))

その後、私は行うことができ(foo "http://www.pointlesssites.com/")、それはに置き換えられます"\r\n<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0 Transitional//EN\"\r\n\t <and so on>"

これは良い習慣ですか?Racket がこのコードを 1 回だけ実行することを保証しますか? マクロに行を追加する(display "running...")と、一度だけ出力されますが、1 つの例から一般化するのは嫌です...

追伸 - 私が尋ねている理由は、実際にこれが時々本当に役立つと思うからです. たとえば、これは、(コンパイル時に) Google API Discovery サービスからディスカバリー ドキュメントをロードし、そのラッパーを自動的に作成できるライブラリです。ライブラリがローカル ファイルからではなく、実際に Web からディスカバリー ドキュメントをフェッチするようになれば、本当にすばらしいと思います。

また、別の種類の副作用を持つマクロの例を挙げると、Racket の小さなサブセットを (eta 拡張された) ラムダ計算 (もちろん、Racket で実行可能) に変換するマクロを作成したことがあります。マクロが関数の変換を終了するたびに、その結​​果を辞書に格納して、後でマクロを呼び出したときにその関数定義を独自の変換で使用できるようにします。

4

2 に答える 2

14

短い答え

マクロに副作用があっても問題ありませんが、事前にコンパイルしたときにプログラムの動作が変わらないようにする必要があります。

より長い答え

副作用のあるマクロは強力なツールであり、プログラムをより簡単に記述できるようにしたり、まったく不可能なことを可能にしたりできます。ただし、マクロで副作用を使用する場合に注意すべき落とし穴があります。幸い、Racket には、これを正しく行うためのすべてのツールが用意されています。

最も単純な種類のマクロの副作用は、生成したいコードを見つけるために何らかの外部状態を使用する場合です。質問にリストする例(Google APIの説明を読む)はこの種のものです。さらに単純な例は、次のincludeマクロです。

#lang racket
(include "my-file.rktl")

これにより、フォームの内容が読み取られ、フォームが使用されmyfile.rktlている場所にドロップされます。include

さて、includeこれはプログラムを構成する良い方法ではありませんが、これはマクロの副作用としては非常に無害です。の結果はファイルの一部であるため、事前にファイルをコンパイルしても、コンパイルしない場合と同じように機能しincludeます。

よくないもう 1 つの単純な例は、次のようなものです。

#lang racket
(define-syntax (show-file stx)
  (printf "using file ~a\n" (syntax-source stx))
  #'(void))

(show-file)

これは、printfがコンパイル時にのみ実行されるためです。そのため、( のように)show-file事前に を使用するプログラムをコンパイルすると、その時点でが発生し、プログラムの実行時には発生しませんが、これはおそらく意図したものではありません。raco makeprintf

show-file幸いなことに、Racket には効果的にマクロを書けるようにするテクニックがあります。基本的な考え方は、実際に副作用を実行する残りのコードを残すことです。begin-for-syntax特に、この目的でRacket のフォームを使用できます。これが私が書く方法ですshow-file

#lang racket
(define-syntax (show-file stx)
  #`(begin-for-syntax
      (printf "using file ~a\n" #,(syntax-source stx))))

(show-file)

show-fileこれで、マクロが展開されたときに発生する代わりに、展開された構文にソースが埋め込まれたを生成printfするコードでshow-file 発生します。そうすれば、プログラムは事前コンパイルの存在下で正しく動作し続けます。

副作用のあるマクロの他の用途もあります。Racket で最も顕著なものの 1 つは、モジュール間通信requireです。必要なモジュールが取得できる値を生成しないため、モジュール間で通信する最も効果的な方法は、副作用を使用することです。コンパイルの存在下でこれを機能させるには、 とほぼ同じトリックが必要begin-for-syntaxです。

これは、Racket コミュニティ、特に私がよく考えてきたトピックであり、これがどのように機能するかについて話しているいくつかの学術論文があります。

コンポーザブルおよびコンパイル可能なマクロ: いつ必要ですか? 、マシュー・フラット、ICFP 2002

Advanced Macrology and the Implementation of Typed Scheme、Ryan Culpepper、Sam Tobin-Hochstadt、Matthew Flatt、Scheme Workshop 2007

図書館としての言語、Sam Tobin-Hochstadt、Ryan Culpepper、Vincent St-Amour、Matthew Flatt、Matthias Felleisen、PLDI 2011

于 2012-10-28T13:29:24.453 に答える
1

一般的な Lisp では、関数 eval-when を使用して、いつマクロを展開するかを決定できます。

于 2012-11-02T14:52:12.417 に答える