7

リストを Lisp の関数に渡して、元のリストに影響を与えずに関数内でそのリストの内容を変更しようとしています。Lisp は値渡しであると読んだことがありますが、それは本当ですが、よくわからないことが他にも起こっています。たとえば、次のコードは期待どおりに機能します。

(defun test ()
    (setf original '(a b c))
    (modify original)
    (print original))
(defun modify (n)
    (setf n '(x y z))
    n)

(test) を呼び出すと、(modify) が (xyz) を返しても (abc) が出力されます。

ただし、リストの一部だけを変更しようとすると、そのようには機能しません。これは、同じコンテンツがどこでもメモリ内で同じであるリストと関係があると思いますか?次に例を示します。

(defun test ()
    (setf original '(a b c))
    (modify original)
    (print original))
(defun modify (n)
    (setf (first n) 'x)
    n)

次に、(テスト) (xbc) を出力します。では、そのリストがその関数に対してローカルであるかのように、関数内のリスト パラメーターの一部の要素を変更するにはどうすればよいでしょうか?

4

4 に答える 4

14

Lisp リストはコンス セルに基づいています。変数は、コンス セル (または他の Lisp オブジェクト) へのポインタのようなものです。変数を変更しても、他の変数は変更されません。コンス セルの変更は、それらのコンス セルへの参照があるすべての場所で表示されます。

良い本は Touretzky, Common Lisp: A Gentle Introduction to Symbolic Computationです。

リストとコンスセルのツリーを描画するソフトウェアもあります。

次のような関数にリストを渡す場合:

(modify (list 1 2 3))

次に、リストを使用する 3 つの異なる方法があります。

コンスセルの破壊的改変

(defun modify (list)
   (setf (first list) 'foo)) ; This sets the CAR of the first cons cell to 'foo .

構造共有

(defun modify (list)
   (cons 'bar (rest list)))

上記は、渡されたリストと構造を共有するリストを返します。残りの要素は両方のリストで同じです。

コピーする

(defun modify (list)
   (cons 'baz (copy-list (rest list))))

上記の関数 BAZ は BAR に似ていますが、リストがコピーされるため、リスト セルは共有されません。

言うまでもなく、破壊的な変更は、それを行う本当の理由 (価値があるときにメモリを節約するなど) がない限り、避けるべきです。

ノート:

リテラル定数リストを破壊的に変更しない

してはいけないこと: (let ((l '(abc))) (setf (first l) 'bar))

理由: リストが書き込み保護されているか、(コンパイラによって調整された) 他のリストと共有されている可能性があります。

また:

変数を導入する

このような

(let ((original (list 'a 'b 'c)))
   (setf (first original) 'bar))

またはこのように

(defun foo (original-list)
   (setf (first original-list) 'bar))

未定義の変数を決して SETF しないでください。

于 2009-09-27T21:32:00.057 に答える
7

SETF は場所を変更します。 n場所になることができます。リストの最初の要素がn場所になることもあります。

どちらの場合も、 が保持するリストは、そのパラメータとして にoriginal渡されます。これは、関数内と関数内の両方が同じリストを保持するようになったことを意味します。つまり、両方とが最初の要素を指すようになりました。modifynoriginaltestnmodifyoriginaln

最初のケースでSETF が変更した後、SETF はnそのリストではなく、新しいリストを指します。が指すリストoriginalは影響を受けません。その後、新しいリストが によって返されmodifyますが、この値は何にも割り当てられていないため、存在しなくなり、すぐにガベージ コレクションが行われます。

2 番目のケースでは、SETF は を変更しませんnが、リストの最初の要素が をn指します。これは、original指しているリストと同じであるため、後で、この変数を介して変更されたリストを確認することもできます。

リストをコピーするには、COPY-LISTを使用します。

于 2009-09-27T20:18:16.853 に答える
5

これは、C の次の例とほぼ同じです。

void modify1(char *p) {
    p = "hi";
}

void modify2(char *p) {
    p[0] = 'h';
}

どちらの場合もポインターが渡されます。ポインターを変更すると、ポインター値のパラメーター コピー (スタック上にあること) が変更されます。内容を変更すると、ポイントされたオブジェクトの値が変更されます。 .

于 2009-09-28T09:46:20.530 に答える
2

Lisp は値渡しですが、Java や Python のようにオブジェクトへの参照が渡されるため、問題が発生する可能性があります。コンスセルには変更する参照が含まれているため、元のセルとローカルの両方を変更します。

IMO、このような問題を回避するには、より機能的なスタイルで関数を作成するようにしてください。Common Lisp はマルチパラダイムですが、関数型スタイルの方がより適切な方法です。

(defun modify (n) (cons 'x (cdr n))

于 2009-09-27T20:06:21.883 に答える