6

私はLispの関数にリストを渡そうとしていて、元のリストに影響を与えずに関数内のそのリストの内容を変更しようとしています。私はLispが値渡しであることを読んできました。それは本当ですが、私はそれほど理解していないことがあります。例えば、このコードは期待どおりに動作:common-lispでは、元のリストを変更せずに、関数内からリストパラメータの一部を変更するにはどうすればよいですか?

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

あなたが(テスト)を呼び出す場合は、それは(変更)にもかかわらず、(B c)は戻り(のxのy z)を印刷します。

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

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

次に(テスト)(x b c)を印刷します。では、関数内のリストパラメータのいくつかの要素を、その関数のローカルであるかのように、どのように変更するのですか?

+3

リテラル定数の変更の結果は未定義であることに注意してください。それをしないでください。決して。 '(a b c)はコード内のリテラル定数です。あなたはそれを変更すべきではありません。 LIST関数(list 'a' b 'c)のように作成されたリストを変更することができます。 –

+4

注意してください(SETFオリジナル '(a b c))意味がありません。 SETFは変数を導入しません。変数 'original'はどこにも定義されていません。 LET、DEFUN、DEFVAR、DEFPARAMETERを介して導入された変数を設定することができます。 –

答えて

7

SETFは場所を修正) (コンス「×(CDRのN))N(変更。nが発生することができる。リストの最初の要素をn点も可能な場所。どちらの場合も

は、originalで開催されたリストには、そのパラメータnとしてmodifyに渡されます。これは、関数modifyの両方の機能testoriginalnは今と同じリストを保持することを意味していますoriginalnの両方が最初の要素を指していることを意味します。

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

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

リストをコピーするには、COPY-LISTを使用してください。

2

LispがJavaやPythonのようにオブジェクトへの値渡しの参照が渡されても、おそらく問題があるでしょう。あなたのコンスセルにはあなたが変更する参照が含まれているので、元のものとローカルのものの両方を変更します。

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

(関数定義

+0

これは私の最初のlispプロジェクトですが、まだ関数型プログラミングを理解していません。私は私のプログラムを構成した方法を再考する必要があると思いますか? 私は状態を変更せずにゲーム状態の潜在的な変更を調べようとしています。そこで、makechangeとundochangeのための関数を作る代わりに、代わりに状態の "コピー"を変更するだけでいいと思っていました。 – Ross

+0

一般的に、不要な状態の変更を避けることが賢明です。しばしばそれらを必要とせず、プログラムをデバッグするのが難しくなり、このようなミスにつながります。 – freiksenet

11

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

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

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

あなたはこのような関数にリストを渡す場合:コンスセル

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

破壊的な変更:

(modify (list 1 2 3)) 

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

構造体共有

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

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

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

をコピー

はBARと同様であるが、リストがコピーされているので何のリストセルは、共有されていません。

破壊的な改変は、それを行う本当の理由がない限り(メモリを節約する価値がある場合など)、しばしば避けるべきです。

注:

破壊的

いけないリテラル定数のリストを変更することはありません 'を実行します(((Lみましょう')(ABC)))(setfの(最初リットル)「バー)

理由:リストが書き込み保護されていてもよい、または他のリスト(コンパイラによって配置)と共有することができる、等

また:

この

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

またはこの

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

などのような変数

を紹介未定義の変数をSETFことはありません。ポインタはあなたがポインタを変更した場合、あなたはそれが上だと(ポインタ値のパラメータのコピーを変更している、渡され、いずれの場合にも

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

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

+0

ありがとうございます。これは私にとって非常に役に立ちました。 – Ross

4

それはCで、この例とほぼ同じですスタックを参照)、内容を変更すると、どのオブジェクトが指し示されていたかの値が変更されます。

関連する問題