2

init から end-1 までの x の body の値を 1 ずつ計算するマクロ (my-dotimes [x init end] & body) を書きたいと思います。捕獲問題」。次のように動作するはずです。

user=> (my-dotimes [x 0 4] (print x))
0123nil

私のコードは:

(defmacro my-dotimes [[x initial end] & body]
`(loop [i# ~initial]
    (when (< i# ~end)
        ~@body
        (recur (inc i#))))))

しかし、macroexpand を使用してチェックすると、次のことがわかります。

user=> (macroexpand '(my-dotimes [x 0 4] (println x)))
(loop* [i__4548__auto__ 0] (clojure.core/when (clojure.core/<i__4548__auto__ 4)
 (println x) 
(recur (clojure.core/inc i__4548__auto__))))

どう変えようか迷ってます

(println x) => (clojure.core/println i__4548__auto__)
4

1 に答える 1

4

ここでは、カウンター (ここでは ) にバインドする必要があるシンボルを指定するため、gensyms を使用する必要xはありません。を使用する代わりに、マクロのユーザーから与えられたシンボルを導入するだけです。新しいシンボルを導入し、既存のシンボルと衝突させたくない場合は、gensyms が必要です。i#

iCommon Lisp では、を使用して、ユーザー提供のシンボルから の現在の値へのバインディングで本体をラップすることは理にかなっています(let ((,x ,i)) ,@body)。これは、ユーザーのコードが反復中にカウンターの値を変更する可能性があるためです (これは悪い可能性があります)。しかし、ここでは変数を直接変更することはできないと思うので、心配する必要はありません。

2番目の例は次のとおりです。

(defmacro for-loop [[symb ini t change] & body]
  `(loop [symb# ~ini] 
     (if ~t 
         ~@body
         (recur ~change))))

最初の問題: 1 つまたは複数のフォームであるボディを展開すると、if2 つではなく多くのブランチを持つフォームになります。たとえば(if test x1 x2 x3 (recur ...))、ボディにx1x2およびが含まれている場合がありますx3doを使用して、ボディを式でラップする必要があります(do ~@body)

現在、状況は以前とあまり変わりません。ユーザーによって指定されたシンボルがまだあり、マクロでバインディングを確立する責任があります。symb#とはまったく異なる新しいシンボルを作成するを使用する代わりに、直接symb使用してください。symbたとえば、これを行うことができます(テストされていません):

(defmacro for-loop [[symb init test change] &body]
  `(loop [~symb ~init]
     (if ~test (do ~@body) (recur ~change))))

マクロの呼び出し元が提供するシンボルを使用する限り、gensyms は必要ありません。生成されたコードで新しい変数を作成する必要がある場合は、gensyms が必要です。これには、新しいシンボルが必要です。たとえば、式を 1 回だけ評価し、その値を保持する変数が必要な場合:

(defmacro dup [expr]
  `(let [var# ~expr]
      [var# var#]))
于 2016-04-04T16:11:27.737 に答える