Inisialisasi ulang daftar? Apa yang terjadi di bawah tenda?

8

Saya belajar lebih banyak tentang elisp dan menemui masalah berikut:

Jika saya ingin mengatur ulang variabel daftar, itu tidak akan diperbarui setelah evaluasi pertama. Berikut ini beberapa contoh kode:

(defun initilize ()
  (setq example '(3)))

(defun modify ()
  (initilize)
  (message "%S" example)
  (setcar example 2))

; M-x eval-buffer RET
(modify) ; message --> (3)
(modify) ; message --> (2)
(modify) ; message --> (2)

Saya tertarik pada dua hal. Yang pertama adalah untuk mempelajari lebih lanjut tentang apa yang terjadi "di bawah tenda" jadi mengapa itu bekerja pertama kali dan gagal pada panggilan berikutnya?

Pertanyaan kedua dan lebih praktis adalah bagaimana menginisialisasi ulang daftar dengan benar atau adakah cara lain yang umum untuk melakukan sesuatu seperti itu?

Satu solusi yang saya temukan sendiri adalah dengan menggunakan daftar yang dikutip dan mengevaluasi konten seperti ini:

(setq example `(,3)) 
clemera
sumber
2
Ringkasan: Jangan berharap '(some list)untuk menjadi eqke '(some list)- pernah .Ada umumnya ada jaminan dalam Lips bahwa kode yang tampak mengutip daftar kembali struktur daftar baru setiap kali. Dalam beberapa implementasi Lisp mungkin, atau mungkin beberapa waktu. Pada orang lain, itu tidak pernah terjadi. Kode Anda seharusnya tidak tergantung pada perilaku seperti itu dari implementasi. Jika Anda ingin struktur baru daftar, penggunaan listatau consatau setara.
Drew
(Saya menduga bahwa pertanyaan ini adalah duplikat, tetapi saya tidak tahu di mana duplikat itu berada.)
Drew
1
Saya pikir masalahnya di sini adalah yang examplebelum pernah dinyatakan sebagai variabel, jadi setqharus bertindak seolah-olah mendeklarasikan variabel baru, tetapi nanti ketika Anda memanggil initializelagi variabel baru sedang dibuat, sementara modifymengingat yang lama ... dalam hal apa pun ini bukan perilaku yang diharapkan, namun, penggunaan setqdengan sesuatu yang belum diperkenalkan sebelumnya sebagai variabel mungkin juga tidak terdefinisi.
wvxvw
3
OK, saya tahu apa yang terjadi. '(3)diperlakukan sebagai nilai literal, sehingga setelah Anda (setcar '(3) 2), setiap kali Anda melakukan (defvar foo '(3))atau (let ((foo '(3)))dan sebagainya Anda mungkin akan mendapatkan nilai foosama dengan '(2). Saya katakan "mungkin" karena perilaku ini tidak dijamin, itu semacam optimisasi yang dilakukan penerjemah kapan pun rasanya, sesuatu yang dikenal sebagai konstanta penghapusan subekspresi (kasus tertentu). Jadi, apa yang ditulis abo-abo sebenarnya bukan alasannya. Ini lebih seperti memodifikasi string literal dalam C (yang biasanya menghasilkan peringatan).
wvxvw
2
Gandakan stackoverflow.com/q/16670989
phils

Jawaban:

5

Mungkin ini akan menghilangkan beberapa kebingungan:

  • Fungsi initilizeAnda tidak menginisialisasi variabel example. Ini menetapkannya ke sel kontra tertentu - sel kontra yang sama setiap kali dipanggil. Pertama kali initilizedipanggil, setqditugaskan exampleke sel kontra baru, yang merupakan hasil evaluasi '(3). Panggilan selanjutnya untuk initilizehanya menetapkan kembali exampleke sel kontra yang sama.

  • Karena initilizebaru saja menugaskan ulang sel kontra yang sama example, modifyhanya mengatur mobil sel kontra yang sama untuk 2setiap kali dipanggil.

  • Untuk menginisialisasi daftar, gunakan listatau cons(atau sexp backquote yang setara, seperti `(,(+ 2 1))atau `(,3)). Sebagai contoh, gunakan (list 3).

Kunci untuk memahami ini adalah untuk mengetahui bahwa sel kontra yang dikutip dievaluasi hanya sekali, dan setelah itu sel kontra yang sama dikembalikan. Ini belum tentu cara semua Lisps berperilaku, tetapi itulah cara Emacs Lisp berperilaku.

Secara umum, perilaku mengevaluasi objek yang dapat diubah yang dikutip bergantung pada implementasi, jika tidak bergantung pada bahasa. Dalam Common Lisp, misalnya, saya cukup yakin bahwa tidak ada dalam definisi (spek) bahasa yang mendefinisikan perilaku dalam hal ini - itu diserahkan kepada implementasinya.

Rangkuman: Jangan berharap '(beberapa daftar) menjadi sama dengan' (beberapa daftar) - selamanya. Pada umumnya tidak ada jaminan dalam Lisp bahwa kode yang dengan jelas mengutip daftar mengembalikan struktur daftar baru setiap kali. Dalam beberapa implementasi Lisp mungkin, atau mungkin beberapa waktu. Pada orang lain, itu tidak pernah terjadi. Kode Anda seharusnya tidak tergantung pada perilaku seperti itu dari implementasi. Jika Anda ingin struktur baru daftar, penggunaan listatau consatau setara.

Drew
sumber
1
Apakah nada merendahkan di dua baris pertama jawaban Anda perlu? Kalau tidak, jawaban yang mencerahkan.
asjo
@asjo: Tujuannya bukan untuk menyerah dengan cara apa pun; Maaf. Semoga lebih jelas sekarang.
Drew
Terima kasih untuk menjernihkan semuanya. Dengan istilah "argumen" maksud saya argumen `(, 3) untuk fungsi setq, meskipun saya mengerti itu agak tidak jelas karena saya benar-benar mengevaluasi 3 dalam daftar yang dikutip. Saya akan mengeditnya.
clemera
@ Sangat Bagus! Lebih mudah dibaca sekarang.
asjo
5

Anda dapat menggunakan (setq example (list 3))untuk menghindari kesalahan ini.

Apa yang terjadi adalah initpihak yang ditunjuk obyek yang awalnya berisi (3)ke example. Ini menetapkan nilai objek hanya sekali. Selanjutnya, Anda memodifikasi nilai ini.

Inilah contoh Anda dalam C ++, jika Anda memahami itu dengan lebih baik:

#include <stdio.h>
#include <string.h>
char* example;
char* storage = 0;
char* create_object_once (const char* str) {
  if (storage == 0) {
    storage = new char[strlen (str)];
    strcpy (storage, str);
  }
  return storage;
}
void init () {
  example = create_object_once ("test");
}
void modify () {
  init ();
  printf ("%s\n", example);
  example[0] = 'f';
}
int main (int argc, char *argv[]) {
  modify ();
  modify ();
  modify ();
  return 0;
}
abo-abo
sumber
1
Terima kasih, saya tidak tahu C ++ tapi saya mengerti contoh Anda. Satu hal yang masih mengganggu saya: Dalam contoh kode Anda, Anda memperkenalkan penyimpanan variabel yang membuat objek dengan nilai awal hanya jika belum ditetapkan. Bagaimana hal itu menerjemahkan apa yang sedang dilakukan oleh Penerjemah Emacs Lisp? Maksud saya jika saya mengevaluasi kembali initilizefungsi dan menelepon modifylagi itu akan muncul (3)lagi hanya karena saya mengevaluasi kembali fungsi.
clemera
1
Saya tidak bisa (3)menjelaskan secara spesifik (tidak tahu persis), tetapi anggap sebagai objek sementara yang menjadi bagiannya init. initTubuh diatur exampleke alamat objek sementara itu, tidak menyentuh nilainya.
abo-abo