Menetapkan nilai yang sama ke beberapa variabel?

12

Terkadang saya perlu menetapkan nilai yang sama ke beberapa variabel.

Dengan Python, saya bisa melakukannya

f_loc1 = f_loc2 = "/foo/bar"

Tetapi di Elisp, saya menulis

(setq f_loc1 "/foo/bar" f_loc2 "/foo/bar")

Saya bertanya-tanya apakah ada cara untuk mencapai ini menggunakan "/foo/bar"hanya sekali?

ChillarAnand
sumber
1
Hai, Chillar, bisakah Anda memilih jawaban @tarsius sebagai jawaban yang diterima? Jawaban saya menunjukkan solusi yang memberi Anda apa yang Anda inginkan, tetapi seperti yang saya katakan itu "latihan semacam" yang memecahkan tantangan buatan yang bisa dibilang ini tetapi tidak terlalu berguna dalam praktik. tarsuis telah berupaya keras untuk menunjukkan pertimbangan yang jelas ini dalam jawabannya, jadi saya pikir jawabannya pantas menjadi "yang benar", jadi semua orang senang lagi.
Mark Karpov
1
Saya berpendapat bahwa kebutuhan untuk menetapkan nilai yang sama untuk beberapa variabel menunjukkan cacat desain yang harus diperbaiki daripada mencari gula sintaksis untuk ditutup :)
lunaryorn
1
@Lunaryorn jika ingin mengatur source& targetke jalur yang sama di awal dan saya mungkin berubah targetnanti, bagaimana mengaturnya?
ChillarAnand
Tampilkan lebih banyak kode, sehingga kami dapat memberi tahu apa yang Anda maksud dengan "variabel" untuk use case Anda; yaitu, variabel apa yang ingin Anda atur. Lihat komentar saya untuk jawaban @ JordonBiondo.
Drew

Jawaban:

21

setq mengembalikan nilai, sehingga Anda bisa:

(setq f-loc1 (setq f-loc2 "/foo/bar"))

Jika Anda tidak ingin mengandalkan itu, gunakan:

(setq f-loc1 "/foo/bar" f-loc2 f-loc1)

Secara pribadi saya akan menghindari yang terakhir dan bukannya menulis:

(setq f-loc1 "/foo/bar"
      f-loc2 f-loc1)

atau bahkan

(setq f-loc1 "/foo/bar")
(setq f-loc2 f-loc1)

Dan pendekatan pertama yang hanya akan saya gunakan dalam situasi yang agak khusus di mana sebenarnya menekankan niat, atau ketika alternatifnya adalah menggunakan pendekatan kedua atau yang lain progn.


Anda dapat berhenti membaca di sini jika hal di atas membuat Anda bahagia. Tapi saya pikir ini adalah kesempatan baik untuk memperingatkan Anda dan orang lain untuk tidak menyalahgunakan makro.


Akhirnya, sementara tergoda untuk menulis makro seperti setq-every"karena ini adalah lisp dan kita bisa", saya akan sangat menyarankan untuk tidak melakukannya. Anda mendapatkan sangat sedikit dengan melakukannya:

(setq-every value a b)
   vs
(setq a (setq b value))

Tetapi pada saat yang sama Anda mempersulit pembaca lain untuk membaca kode dan memastikan apa yang terjadi. setq-everydapat diimplementasikan dengan berbagai cara dan pengguna lain (termasuk masa depan Anda) tidak dapat mengetahui mana yang penulis pilih, hanya dengan melihat kode yang menggunakannya. [Saya awalnya membuat beberapa contoh di sini tapi itu dianggap sarkastik dan kata-kata kasar oleh seseorang.]

Anda dapat menangani overhead mental itu setiap kali Anda melihatnya setq-everyatau Anda bisa hidup dengan satu karakter tambahan. (Setidaknya dalam kasus di mana Anda harus menetapkan hanya dua variabel, tetapi saya tidak ingat bahwa saya harus mengatur lebih dari dua variabel ke nilai yang sama sekaligus. Jika Anda harus melakukan itu, maka mungkin ada sesuatu yang salah dengan kode Anda.)

Di sisi lain jika Anda benar-benar akan menggunakan makro ini lebih dari mungkin setengah lusin kali, maka tipuan tambahan mungkin sepadan. Misalnya saya menggunakan macro seperti --when-letdari dash.elperpustakaan. Itu memang membuat lebih sulit bagi mereka yang pertama kali menemukannya dalam kode saya, tetapi mengatasi kesulitan dalam memahami itu sepadan karena digunakan lebih dari hanya beberapa kali dan, setidaknya menurut saya, itu membuat kode lebih mudah dibaca .

Orang bisa berpendapat bahwa hal yang sama berlaku untuk setq-every, tetapi saya pikir sangat tidak mungkin bahwa makro khusus ini akan digunakan lebih dari beberapa kali. Aturan praktis yang baik adalah bahwa ketika definisi makro (termasuk doc-string) menambahkan lebih banyak kode daripada penggunaan penghapusan makro, maka makro sangat mungkin berlebihan (kebalikannya belum tentu benar meskipun).

tarsius
sumber
1
setelah Anda menetapkan satu var di dalam setqAnda dapat referensi itu nilai baru, sehingga Anda dapat menggunakan monstrositas ini juga: (setq a 10 b a c a d a e a)yang menetapkan abcd dan e ke 10.
Jordon Biondo
1
@JordonBiondo, Ya, tapi saya pikir tujuan OP adalah mengurangi pengulangan dalam kode-nya :-)
Mark Karpov
3
Saya ingin memberi Anda +10 untuk peringatan terhadap penyalahgunaan makro!
lunaryorn
Baru saja membuat pencarian tentang praktik terbaik makro. emacs.stackexchange.com/questions/21015 . Hope @tarsius dan yang lainnya memberikan jawaban yang bagus.
Yasushi Shoji
13

Makro untuk melakukan apa yang Anda inginkan

Sebagai latihan sejenis:

(defmacro setq-every (value &rest vars)
  "Set every variable from VARS to value VALUE."
  `(progn ,@(mapcar (lambda (x) (list 'setq x value)) vars)))

Sekarang coba:

(setq-every "/foo/bar" f-loc1 f-loc2)

bagaimana cara kerjanya

Karena orang ingin tahu bagaimana cara kerjanya (menurut komentar), berikut adalah penjelasannya. Untuk benar-benar mempelajari cara menulis makro, pilih buku Common Lisp yang baik (ya, Common Lisp, Anda akan dapat melakukan hal yang sama di Emacs Lisp, tetapi Common Lisp sedikit lebih kuat dan memiliki buku yang lebih baik, IMHO).

Macro beroperasi pada kode mentah. Makro tidak mengevaluasi argumen mereka (tidak seperti fungsi). Jadi kita di sini tidak dievaluasi valuedan koleksi vars, yang untuk makro kita hanya simbol.

prognkelompok beberapa setqbentuk menjadi satu. Hal ini:

(mapcar (lambda (x) (list 'setq x value)) vars)

Hanya menghasilkan daftar setqformulir, menggunakan contoh OP itu akan menjadi:

((setq f-loc1 "/foo/bar") (setq f-loc2 "/foo/bar"))

Anda lihat, formulir itu di dalam formulir backquote dan diawali dengan koma ,. Di dalam formulir backquoted semuanya dikutip seperti biasa, tetapi , evaluasi " mapcarnyalakan " sementara, sehingga keseluruhan dievaluasi pada waktu ekspansi makro.

Akhirnya @menghapus tanda kurung luar dari daftar dengan setqs, jadi kami mendapatkan:

(progn
  (setq f-loc1 "/foo/bar")
  (setq f-loc2 "/foo/bar"))

Macro dapat mengubah kode sumber Anda secara sewenang-wenang, bukankah hebat?

Peringatan

Berikut ini adalah peringatan kecil, argumen pertama akan dievaluasi beberapa kali, karena makro ini pada dasarnya diperluas sebagai berikut:

(progn
  (setq f-loc1 "/foo/bar")
  (setq f-loc2 "/foo/bar"))

Anda lihat, jika Anda memiliki variabel atau string di sini tidak apa-apa, tetapi jika Anda menulis sesuatu seperti ini:

(setq-every (my-function-with-side-effects) f-loc1 f-loc2)

Maka fungsi Anda akan dipanggil lebih dari sekali. Ini mungkin tidak diinginkan. Berikut ini cara memperbaikinya dengan bantuan once-only(tersedia dalam paket MMT ):

(defmacro setq-every (value &rest vars)
  "Set every variable from VARS to value VALUE.

VALUE is only evaluated once."
  (mmt-once-only (value)
    `(progn ,@(mapcar (lambda (x) (list 'setq x value)) vars))))

Dan masalahnya hilang.

Mark Karpov
sumber
1
Akan menyenangkan jika Anda dapat menambahkan beberapa penjelasan bagaimana ini bekerja dan apa yang dilakukan kode ini secara lebih rinci, jadi lain kali orang dapat menulis sesuatu seperti ini sendiri :)
clemera
Anda tidak ingin mengevaluasi valuepada waktu ekspansi makro.
tarsius
@tarsius, saya tidak: (macroexpand '(setq-every user-emacs-directory f-loc1 f-loc2))-> (progn (setq f-loc1 user-emacs-directory) (setq f-loc2 user-emacs-directory)). Jika valuedievaluasi pada waktu ekspansi makro itu akan diganti dengan string yang sebenarnya. Anda dapat menempatkan panggilan fungsi dengan efek samping, sebagai gantinya value, itu tidak akan dievaluasi sampai kode diperluas dijalankan, cobalah. valuesebagai argumen makro tidak dievaluasi secara otomatis. Masalah sebenarnya adalah bahwa valueakan dievaluasi beberapa kali, yang mungkin tidak diinginkan, once-onlydapat membantu di sini.
Mark Karpov
1
Alih-alih mmt-once-only(yang membutuhkan dep eksternal), Anda bisa menggunakan macroexp-let2.
YoungFrog
2
Ugh. Pertanyaannya adalah untuk menggunakan iterasi dengan set, bukan untuk membungkus setqmakro. Atau cukup gunakan setqdengan beberapa argumen, termasuk pengulangan argumen.
Drew
7

Karena Anda dapat menggunakan dan memanipulasi simbol dalam lisp, Anda bisa dengan mudah mengulang daftar simbol dan menggunakannya set.

(dolist (var '(foo bar baz)) (set var 10))

(mapc (lambda (var) (set var 10)) '(foo bar baz))

(loop for var in '(foo bar baz) do (set var 11))

(--each '(foo bar baz) (set it 10))
Jordon Biondo
sumber
2
Ini tidak bekerja untuk variabel yang terikat secara leksikal
npostavs
@npostavs: Benar, tapi mungkin itu yang diinginkan / dibutuhkan OP. Jika dia tidak menggunakan variabel leksikal maka sudah pasti cara untuk pergi. Anda benar, bahwa "variabel" itu ambigu, jadi secara umum pertanyaan bisa berarti segala jenis variabel. Kasing yang sebenarnya tidak disajikan dengan baik, IMO.
Drew