5

私は次の一般的な Lisp 関数を持っています:(aggregate line1 line2)(queuer data result).

queuerline1値と最初のフィールドが異なる場合は結果にプッシュする必要line2があり、最初のフィールドが等しい場合はこれらの2行の集計をプッシュする必要があります。

結果リストが変更されない理由がわかりません。

注:(push (pop data) result)最初の要素が含まれるように、結果リストを a で初期化しています。2 つのリストは、深さ 1 のネストされたリスト(("1" "text") ("2" "text") (...))です。

(defun aggregate (line1 line2)
  (progn
    (list 
     (nth 0 line1)
     (nth 1 line1)
     (nth 2 line1)
     (concatenate 'string (nth 3 line1) ", " (nth 3 line2))
     (concatenate 'string (nth 4 line1) ", " (nth 4 line2)))))

(push (pop x) y)

(defun queuer (data result)
  (loop do
       (let ((line1 (pop data))
             (line2 (pop result)))
         (if (equal (first line1) (first line2))
             (progn
               (push (aggregate line1 line2) result)
               (print "=="))
             (progn
               (push line2 result)
               (push line1 result)
               (print "<>"))))
       while data))

洞察をありがとう。

4

4 に答える 4

8

変数の値のみを受け取る関数で変数の内容を変更することはできません。

次の簡単な例を見てください。

(defun futile-push (thing list)
  (push thing list))

(let ((foo (list 1)))
  (futile-push 2 foo))

何が起こるのですか?

  • Fooそれが指すリストに評価されます。
  • 22 に評価されます。
  • これら 2 つの引数は、関数に渡されます。

関数呼び出しの内部:

  • Thingは現在 2 にバインドされています。
  • Listリストにバインドされるようになりました(1)

fooリストは、関数外の変数によっても参照されていることを認識していないことに注意してください 。

         foo
          |
          v
        ---------
list -> | 1 |NIL|
        ---------
  • Pushlistリストにバインドされるように変数を変更します(2 1)

fooこれは外部 には影響しないことに注意してください。Fooは以前と同じものを指しています。

                     foo
                      |
                      v
        ---------   ---------
list -> | 2 | ----> | 1 |NIL|
        ---------   ---------
  • Futile-pushpushの新しい値であるフォームの戻り値を返しますlist

  • その戻り値は使用もバインドもされないため、消えます。

     foo
      |
      v
    ---------
    | 1 |NIL|
    ---------
    

やりたいことを行う最も簡単な方法は、新しい値を返し、変数を外部に設定することです。

(let ((foo (list 1)))
  (setf foo (not-so-futile-push 2 foo)))

複数の場所でそれを行う必要がある場合は、setfフォームに展開するマクロを作成する価値があります。pushまさにこれらの理由から、それ自体がマクロであることに注意してください 。

于 2013-05-06T10:57:43.133 に答える
8

If you write functions in Lisp it is preferable to think 'functionally'. A function takes values and returns values. A typical rule would be to avoid side effects. So your function should return a result value, not 'modify' a variable value.

Instead of:

(defparameter *result* '())

(defun foo (a)
   (push a *result*))

use:

(defparameter *result* '())

(defun foo (a result)
  (push a result)
  result)

(setf *result* (foo a *result*))

Note also that aggregate does not need the progn.

Slightly advanced (don't do that):

If you have a global list:

(defparameter *foo* '())

You can't push onto it, as we have seen, like this:

(defun foo (l)
   (push 1 l))

If you call foo the variable *foo* is unchanged. Reason: Lisp does not pass a variable reference, it passes the value of the variable.

But how can we pass a reference? Well, pass a reference: a cons cell would do it (or a structure, a vector, a CLOS object, ...):

CL-USER 38 > (defparameter *foo* (list '()))
*FOO*

CL-USER 39 > (defun foo (ref)
               (push 1 (first ref)))
FOO

CL-USER 40 > (foo *foo*)
(1)

CL-USER 41 > (foo *foo*)
(1 1)

さて、 を見ると*foo*、変わっています。しかし、実際には変数を変更していません。リストの最初のエントリを変更しました。

CL-USER 42 > *foo*
((1 1))

しかし、それをしないでください。機能的なスタイルでプログラムします。

于 2013-05-06T12:51:58.723 に答える
2

push in queuer を呼び出すと、結果が指しているコンス セルではなく、バインディングの「結果」の値が変更されます。

(push x list)

基本的に次と同等です。

(setq list (cons x list))

キュー関数が関数である限り、それ以外の方法はあり得ません。引数「my-queue」を付けて呼び出すと、その引数 (シンボル) が関数の呼び出し時に評価され、評価の結果 (コンス セル) が関数に渡されます。そのコンス セルを変更して、別のコンス セルを「先頭に追加」する必要があることを示す方法はありません。コンス セルは、コンス セルを指すものを追跡しません。

(少なくとも) 3 つの可能な解決策があります。

  • 引数が変更される (または「変更される」) ことを期待するのではなく、queuerが新しいキューを返すようにコードを記述します。

  • 可変の間接レイヤー内にキューをラップします。たとえば、車内のキューまたはコンス セルの cdr を保持できます。その後、たとえばプッシュを使用して、キュー関数で (car 結果) または (cdr 結果) を変更できます。

  • queuer を関数ではなくマクロに変換します。次に、キューラー マクロを使用する場合は常にコードに「挿入」される引数を変更するコードを記述できます。

私は個人的に最初の解決策をお勧めします。ミューティング キューアがある場合、次のように記述します。

(queuer-mutating data my-queue)

代わりに、次のように記述します。

(setf my-queue (queuer-not-mutating data my-queue))
于 2013-05-06T10:33:02.797 に答える
0

dataを使用して変数を初期化する(push (pop data) result)と、アイテムをコピーする代わりに から に移動します。dataresult

CL-USER> (setq data '(("1" "text1") ("2" "text2") ("3" "text3")))
(("1" "text1") ("2" "text2") ("3" "text3"))
CL-USER> (setq result nil)
NIL
CL-USER> (push (pop data) result)
;Compiler warnings :
;   In an anonymous lambda form: Undeclared free variable DATA (3 references)
(("1" "text1"))
CL-USER> (print data)

(("2" "text2") ("3" "text3")) 
(("2" "text2") ("3" "text3"))
CL-USER> (print result)

(("1" "text1")) 
(("1" "text1"))

代わりに使用したいのは(copy-list list)関数です:

CL-USER> (setq copy2 (copy-list data))
(("2" "text2") ("3" "text3"))
于 2013-05-06T10:39:51.843 に答える