2017-08-31 8 views
1

は、プッシュは、このようなマクロな外観です:なぜlispマクロpushはシンボルのみを変更するのですか?多くのLispの実装で

(push new list) 
;; equal to 
(setf list (cons new list)) 

しかしsetfのは、引数のように変更することはできません:関数の引数は、元のシンボルではないので

(defun add-item (new list) 
    (push new list)) 

は、動作しません。

このような作業をプッシュしない理由:

(defun my-push (new list) 
    (setcdr list (cons (car list) 
        (cdr list))) 
    (setcar list new) 
    list) 

を次にプッシュは、関数の引数に仕事ができます。 lispをこのように動作させる理由はありますか?

私はemacs lispとsicpスキームの初心者です。

答えて

5

破壊的なpush関数の1つの問題は、空のリストnilでは機能しないということです。それはちょっとした "売り切れ"です。

pushマクロは、リストの先頭を保持する格納場所の値を変更する必須の構成要素ですが、そのリストの構造の変更を避けることに注意してください。

pushpopをローカル変数に使用するリスト処理コードについては、理由が簡単です。突然変異したリスト構造によって起こりうるバグを気にする必要はありません。

+0

何も追加テストで回避することができます。 – gholk

+0

@gholk:どうすれば変数にアクセスできないのですか? –

+0

@gholk問題は、 'nil'はあなたが変更できない原子だということです。 'mypush'は'(mypush 1 nil) 'が' nil'を '(1)'に変えるように書くことはできません。この関数は確実にそのオブジェクトを返すことができますが、呼び出し側はその戻り値を取得し、リストを保持するすべての場所を更新する必要があります。 – Kaz

2

複数のシンボル(または他の値)がそのリストまたはそのサブリストを指していることを覚えておいてください。

pushがあなたの提案通りに機能していれば、指定したシンボル以外の値を変更することができます。

は考えてみましょう:

(setq l1 '(foo bar)) 
(setq l2 (append '(baz) l1)) 

それは、あなたもl2の値を変更するだろうを指すコンスセルのcarcdrを操作することによりl1にあなたは今「プッシュ」した場合。

もちろん、それが正確に何をしたいのかという時があるかもしれません。 pushはそれを行う方法ではありません。そして、明らかに、他のコードで望ましくない副作用を引き起こすことなく、そのように動作するようにpushを再定義することはできません。グーグル後

0

とより多くのコード、 読んで(のようなhttps://www.emacswiki.org/emacs/ListModificationを。) 私はlispのプログラマーは、通常、元のリストをコピーして変更することを発見し、 その後、元のリストのシンボルに割り当てます。 これは多分lispのスタイルです。

私は元の配列 を常に変更しますが、ほとんどそれをコピーするjavascriptからです。 あなたの答えに感謝します。

+0

その振る舞いが必要な場合、Lispにも配列があります – coredump

+0

元の値を変更しても問題はありません。言語は関連していないようです。 – phils

1

突然変異にはいくつかの種類があります。 1つはオブジェクトで、carまたはcdrまたはconsを別のものに変更できます。consは前後に同じアドレスを持つので、それを指す変数はすべて同じものを指しますが、オブジェクトは変更されます。

(defparameter *mutating-obj* (list 1)) 
(defparameter *mutating-obj2* *mutating-obj*) 
(setf (cdr *mutating-obj*) '(2)) 

ここでは、両方の変数が変更された同じ値を指しているようにオブジェクトを変更します。いずれかを評価するときは(1 2)と表示されます。 突然変異を起こすことができるcarcdrのものではないため、オブジェクトを突然変異させるので、値は最初に()になることはありません。

可変記号にsetfを指定すると、変数は値のアドレスの場所と考えることができます。したがって、setfは値自体ではなく、その位置を変更します。

(defparameter *var1* '(1 2 3)) 
(defparameter *var2* *var1*) 

ここでは、同じリストを指す2つの変数があります。私はこれを行う場合:

(push 0 *var2*) 

その後*var2*はそれが0から始まる新しいリストを指し、前の値の尾を持っているように、そのポインタが変更されました。これは、以前の値*var2*が依然として指している*var1*を変更しません。

値を持つ関数を呼び出すと、値は新しい変数としてバインドされ、その上でpushを実行すると同じことが実行され、その変数が変更され、同じ値を指す他の変数は決して変更されません。

pushの一般的な使用方法は、空のリストから始めて要素を追加することです。 carcdrを設定すると、空のリストを特定の値を持つ1つの要素リストに変更することはできません。

(defun make-stack() 
    (list 'stack-head)) 

(defun push-stack (element stack) 
    (assert (eq (car stack) 'stack-head)) 
    (setf (cdr stack) (cons element (cdr stack))) 
    stack) 

(defun pop-stack (stack) 
    (let ((popped (cadr stack))) 
    (setf (cdr stack) (cddr stack)) 
    popped)) 

あなたが具体的に設計しない限り、しかしこれは動作しません:nilを指しているすべての変数は、データ構造が使用されることはありませんヘッド要素を持っていた場合rplacd(setf (cdr var) ...))を用いる方法が機能するように変更されていません。ですから、pushは本当に変数を変更する必要があります。 (値を変更すると思う初心者を除きます)

関連する問題