10

Common Lisp の学習 (GNU CLISP 2.43 を使用) .. 初心者の間違いかもしれません。例は「xとyの間の素数を印刷する」です

(defun is-prime (n)
  (if (< n 2) (return-from is-prime NIL))

  (do ((i 2 (1+ i)))
      ((= i n) T)
    (if (= (mod n i) 0) 
        (return NIL))))

(defun next-prime-after (n)
  (do ((i (1+ n) (1+ i)))
      ((is-prime i) i)))

(defmacro do-primes-v2 ((var start end) &body body)
  `(do ((,var (if (is-prime ,start)
                  ,start
                  (next-prime-after ,start))
              (next-prime-after ,var)))
       ((> ,var ,end))
     ,@body))

(defmacro do-primes-v3 ((var start end) &body body)
  (let ((loop-start (gensym))
        (loop-end (gensym))) 
    `(do ((,loop-start ,start)
          (,loop-end ,end)
          (,var (if (is-prime ,loop-start)
                    ,loop-start
                    (next-prime-after ,loop-start))
                (next-prime-after ,var)))
         ((> ,var ,loop-end))
       ,@body )))

do-primes-v2 は完璧に動作します。

[13]> (do-primes-v2 (p 10 25) (フォーマット t "~d " p))
11 13 17 19 23

次に、gensym を使用して、マクロ展開での名前の衝突を回避しようとしました - do-primes-v3。しかし、私は立ち往生しています

*** - EVAL: 変数 #:G3498 に値がありません

間違いを見つけることができるかどうかを確認するためにマクロ展開を使用しようとしましたが、できません。

[16]> (macroexpand-1 `(do-primes-v3 (p 10 25) (format t "~d " p)))
(行う
 ((#:G3502 10) (#:G3503 25)
  (P (IF (IS-PRIME #:G3502) #:G3502 (NEXT-PRIME-AFTER #:G3502))
     (NEXT-PRIME-AFTER P))))
 ((> P #:G3503)) (FORMAT T "~d " P)) ;
4

5 に答える 5

5

DO*の代わりに使用しDOます。

DOまだ表示 されていないスコープ内のバインディングを初期化します。バインディングがDO*表示されるスコープでバインディングを初期化します。

この特定のケースvarでは、他のバインディングを参照する必要がありますloop-start

于 2009-02-16T14:45:57.337 に答える
4

gensym「マクロに対してローカル」な変数を導入しないため、変数のキャプチャを回避するために here は実際には必要ありません。をマクロ展開するdo-primes-v2と、マクロの外には存在しなかった変数が導入されていないことがわかります。

ただし、別の目的で必要になります。つまり、複数の評価を回避することです。

次のようにマクロを呼び出す場合:

(do-primes-v2 (p (* x 2) (* y 3))
  (フォーマット "~a~%" p))

それはに展開します

(do ((p (if (is-prime (* x 2))
            (※×2)
            (next-prime-after (* x 2))
        (next-prime-after p)))
    ((>p(*y3))
  (フォーマット "~a~%" p))

これらの乗算が複数回行われるため、せいぜいこれは非効率的です。setfただし、やのように、入力として副作用を伴う関数を使用する場合incf、これは大きな問題になる可能性があります。

于 2009-02-16T14:25:14.377 に答える
3

loop-start と loop-end のバインディングを外側の LET ブロックに移動するか、DO* を使用してください。その理由は、DO 内のすべてのループ変数が「並列に」バインドされているためです。そのため、最初のバインドでは、(展開された) ループ開始変数にはまだバインドがありません。

于 2009-02-16T14:51:56.123 に答える
1

これがあなたの質問に実際には答えていないことはわかっていますが、関連性があると思います。私の経験では、作成しようとしているマクロのタイプは非常に一般的なものです。あなたが問題にアプローチした方法で私が抱えている問題の1つは、別の一般的なユースケースである機能的構成を処理しないことです。

マクロを使用しておそらく遭遇する問題のいくつかを強調する時間はありませんが、機能的な構成に合わせて主要な反復子を構築した場合、マクロは非常に単純であることがわかり、質問を回避できることを強調します完全に。

注: 一部の機能を少し変更しました。

(defun is-prime (n)
  (cond
    ((< n 2)
     nil)
    ((= n 2)
     t)
    ((evenp n)
     nil)
    (t
     (do ((i 2 (1+ i)))
     ((= i n) t)
       (when (or (= (mod n i) 0))
         (return nil))))))

(defun next-prime (n)
  (do ((i n (1+ i)))
      ((is-prime i) i)))

(defun prime-iterator (start-at)
  (let ((current start-at))
    (lambda ()
      (let ((next-prime (next-prime current)))
         (setf current (1+ next-prime))
         next-prime))))

(defun map-primes/iterator (fn iterator end)
  (do ((i (funcall iterator) (funcall iterator)))
      ((>= i end) nil)
    (funcall fn i)))

(defun map-primes (fn start end)
  (let ((iterator (prime-iterator start)))
    (map-primes/iterator fn iterator end)))

(defmacro do-primes ((var start end) &body body)
  `(map-primes #'(lambda (,var)
                   ,@body)
               ,start ,end))

Seriesを見ることもお勧めします。ジェネレーター パターンは、lisp プログラムでも非常によく見られます。Alexandria、特に関数 ALEXANDRIA:COMPOSE を調べて、関数型合成で何ができるかを確認することもできます。

于 2009-02-17T01:23:04.157 に答える
0

DO/DO* とマクロを完全に避けて、代わりにシリーズ(シリーズの実装はseries.sourceforge.netにあります) を使用することをお勧めします。

それが複雑すぎる場合は、再帰またはジェネレータ(オンデマンド生成用) を使用して素数のリストを生成することを検討してください。

于 2009-02-16T16:25:52.137 に答える