5

Clojure でマクロを学んでいて、マクロ展開について質問があります。replで、これを行うと:

user=> (defmacro unless [pred a b] `(if (not ~pred) ~a ~b))
#'user/unless
user=> (macroexpand-1 '(unless (> 5 3) :foo :bar))
(if (clojure.core/not (> 5 3)) :foo :bar)

しかし、clj ファイルで同じことを行うと、次のようになります。

(ns scratch-pad.core
     (:gen-class))

(defmacro unless [pred a b]
    `(if (not ~pred) ~a ~b))

(defn -main [& args]
    (prn
        (macroexpand-1 '(unless (> 5 3) :foo :bar))))

コードを実行すると、次のようになります。

$ lein run
(unless (> 5 3) :foo :bar)

コードをreplと同じように印刷するにはどうすればよいですか?

4

1 に答える 1

6

どうしたの

これは、現在の名前空間の概念が Clojure でどのように機能するかによって発生します。macroexpand-1現在の名前空間で引数を展開します。

REPL では、これはuser; 名前空間でマクロを定義しているuser場合、その名前空間で呼び出すmacroexpand-1と、すべて問題ありません。

名前空間、:gen-class'dまたは実際には他の名前空間では、コンパイル時の現在の名前空間はその名前空間そのものです。ただし、後でこの名前空間で定義されたコードを呼び出すと、その時点で適切な名前空間が現在の名前空間になります。コンパイルされると、それは他の名前空間になる可能性があります。

最後に、アプリの実行時のデフォルトの現在の名前空間はuserです。

これを確認するには、マクロを別の名前空間に移動して、関数も定義し、use-the-macroこの関数を最上位で呼び出します。'd 名前空間は、:gen-classマクロの名前空間を要求または使用する必要があります。次にlein run、(マクロの名前空間のコンパイル時に) 期待するものを 1 回出力し、展開されていない形式を 2 回 (マクロの名前空間がrequireメインの名前空間によって d され、次に が-main呼び出されたときにuse-the-macro) 出力します。

ソリューション

Clojure REPL はbinding;を使用して現在の名前空間を制御します。同じことができます:

(binding [*ns* (the-ns 'scratchpad.core)]
  (prn (macroexpand-1 ...)))

quote in の代わりに syntax-quote を使用することもできます-main

(defn -main [& args]
  (prn (macroexpand-1 `...)))
                      ^- changed this

もちろん、以外のシンボルunlessが関係している場合は、それらを出力で名前空間修飾する必要があるかどうかを決定し、場合によってはプレフィックスを付ける必要があります~'。ただし、これがポイントです。syntax-quote は、ほとんど「名前空間に依存しない」コードを生成するのに適しています (これが、便利な構文に加えて、マクロを作成するのに非常に優れている理由です)。

別の可能な「修正」(Clojure 1.5.1でテスト済み)は、へのin-ns呼び出しを追加すること-mainです:

(defn -main [& args]
  (in-ns 'scratchpad.core)
  (prn (macroexpand-1 '...)))
                      ^- no change here this time

と同様にbinding、このようにして、元の名前空間で元のフォームの拡張を実際に取得しています。

于 2013-05-12T23:12:17.137 に答える