การกำหนดค่าเดียวกันให้กับหลาย ๆ ตัวแปร?


12

บางครั้งฉันต้องตั้งค่าเดียวกันเป็นตัวแปรหลายตัว

ใน Python ฉันสามารถทำได้

f_loc1 = f_loc2 = "/foo/bar"

แต่ในข้อเขียนฉันเขียน

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

ฉันสงสัยว่าจะมีวิธีในการบรรลุผลนี้โดยใช้"/foo/bar"เพียงครั้งเดียวหรือไม่?


1
สวัสดี Chillar คุณกรุณาเลือก @tarsius 'answer เป็นคำตอบที่ได้รับการยอมรับได้หรือไม่? คำตอบของฉันแสดงให้เห็นถึงวิธีการแก้ปัญหาที่ให้สิ่งที่คุณต้องการ แต่อย่างที่ฉันบอกว่ามันเป็น "การออกกำลังกายแบบ" ที่แก้ปัญหาความท้าทายที่ประดิษฐ์ขึ้นนี้ แต่ไม่มีประโยชน์ในทางปฏิบัติ tarsuis ใช้ความพยายามอย่างมากในการแสดงให้เห็นถึงการพิจารณาที่ชัดเจนในคำตอบของเขาดังนั้นฉันคิดว่าคำตอบของเขาควรจะเป็น "สิ่งที่ถูกต้อง" ดังนั้นทุกคนจึงมีความสุขอีกครั้ง
Mark Karpov

1
ฉันยืนยันว่าความต้องการกำหนดค่าเดียวกันให้กับตัวแปรหลายตัวบ่งชี้ว่ามีข้อบกพร่องในการออกแบบที่ควรได้รับการแก้ไขแทนที่จะมองหาน้ำตาล syntactic เพื่อปกปิด :)
lunaryorn

1
@ lunaryorn หากต้องการตั้งค่าsource& targetพา ธ เดียวกันที่จุดเริ่มต้นและฉันอาจเปลี่ยนแปลงในtargetภายหลังได้อย่างไรจะตั้งค่าอย่างไร
ChillarAnand

แสดงรหัสเพิ่มเติมเพื่อให้เราสามารถบอกได้ว่าคุณหมายถึงอะไรโดย "ตัวแปร" สำหรับกรณีการใช้งานของคุณ นั่นคือตัวแปรชนิดใดที่คุณพยายามตั้งค่า ดูความคิดเห็นของฉันสำหรับคำตอบของ @ JordonBiondo
ดึง

คำตอบ:


21

setq ส่งคืนค่าดังนั้นคุณสามารถ:

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

หากคุณไม่ต้องการพึ่งพาสิ่งนั้นให้ใช้:

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

ส่วนตัวฉันจะหลีกเลี่ยงหลังและแทนที่จะเขียน:

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

หรือแม้กระทั่ง

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

และวิธีการแรกที่ผมจะใช้ในสถานการณ์ที่ค่อนข้างพิเศษที่มันจริงเน้นprognความตั้งใจหรือเมื่อทางเลือกที่จะใช้วิธีการที่สองหรืออื่น ๆ


คุณสามารถหยุดอ่านได้ที่นี่หากสิ่งที่กล่าวมาข้างต้นทำให้คุณมีความสุข แต่ฉันคิดว่านี่เป็นโอกาสที่ดีที่จะเตือนคุณและคนอื่น ๆ ไม่ให้ใช้แมโครในทางที่ผิด


ในที่สุดแม้ว่ามันจะเป็นการดึงดูดให้เขียนมาโครเช่นsetq-every"เพราะนี่เป็นเสียงกระเพื่อมและเราสามารถทำได้" แต่ฉันขอแนะนำอย่างยิ่งให้ไม่ทำเช่นนั้น คุณได้รับน้อยมากโดยทำ:

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

แต่ในเวลาเดียวกันคุณทำให้ผู้อ่านรายอื่นอ่านรหัสยากขึ้นและทำให้แน่ใจว่าเกิดอะไรขึ้น setq-everyสามารถนำไปใช้ในรูปแบบต่างๆและผู้ใช้อื่น ๆ (รวมถึงอนาคตของคุณ) ไม่สามารถรู้ได้ว่าผู้เขียนคนใดเลือกเพียงแค่ดูรหัสที่ใช้งาน [ตอนแรกฉันทำตัวอย่างที่นี่ แต่สิ่งเหล่านั้นถูกพิจารณาว่าเป็นการเสียดสีและพูดจาโผงผางโดยบางคน]

คุณสามารถจัดการกับค่าใช้จ่ายทางจิตนั้นทุกครั้งที่คุณมองsetq-everyหรือคุณสามารถอยู่กับตัวละครพิเศษเพียงครั้งเดียว (อย่างน้อยในกรณีที่คุณต้องตั้งค่าตัวแปรสองตัวเท่านั้น แต่ฉันจำไม่ได้ว่าฉันต้องตั้งค่าตัวแปรมากกว่าสองตัวให้เป็นค่าเดียวกันในครั้งเดียวหากคุณต้องทำเช่นนั้นอาจมีบางอย่างผิดปกติ ด้วยรหัสของคุณ)

ในทางกลับกันถ้าคุณกำลังใช้แมโครนี้จริง ๆ อาจมากกว่าครึ่งโหลแล้วจริง ๆ แล้วการเปลี่ยนทิศทางเพิ่มเติมอาจคุ้มค่า ตัวอย่างเช่นฉันใช้มาโครเช่น--when-letจากdash.elห้องสมุด มันทำให้ยากขึ้นสำหรับผู้ที่เจอมันครั้งแรกในรหัสของฉัน แต่การเอาชนะความยากลำบากในการทำความเข้าใจนั้นคุ้มค่าเพราะมันถูกใช้มากกว่าแค่ไม่กี่ครั้งและอย่างน้อยในความคิดของฉันมันทำให้โค้ดอ่านง่ายขึ้น .

เราอาจโต้แย้งว่าสิ่งเดียวกันนี้ใช้ได้กับsetq-everyแต่ฉันคิดว่ามันไม่น่าเป็นไปได้มากที่แมโครนี้จะถูกใช้มากกว่าสองสามครั้ง กฎง่ายๆคือเมื่อคำจำกัดความของแมโคร (รวมถึง doc-string) เพิ่มรหัสมากกว่าการใช้งานของแมโครที่ถูกลบออกมาแล้วแมโครนั้นมีแนวโน้มที่จะ overkill มาก (ตรงกันข้ามไม่จำเป็นต้องเป็นจริง)


1
หลังจากที่คุณตั้งค่าหนึ่ง var ในsetqคุณสามารถอ้างอิงมันเป็นค่าใหม่ดังนั้นคุณสามารถใช้ monstrosity นี้เช่นกัน: (setq a 10 b a c a d a e a)ซึ่งกำหนด abcd และ e เป็น 10
Jordon Biondo

1
@JordonBiondo ใช่ แต่ฉันคิดว่าจุดมุ่งหมายของ OP คือการลดการซ้ำซ้อนในรหัสของเขา :-)
มาร์คคาร์ปอฟ

3
ฉันอยากจะให้ 10 คำเตือนเกี่ยวกับการใช้แมโครในทางที่ผิด!
Lunaryorn

เพิ่งสร้างเควสต์เกี่ยวกับแนวปฏิบัติที่ดีที่สุดของแมโคร emacs.stackexchange.com/questions/21015 Hope @tarsius และคนอื่น ๆ ให้คำตอบที่ดี
Yasushi Shoji

13

แมโครเพื่อทำสิ่งที่คุณต้องการ

เป็นการออกกำลังกายประเภท:

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

ตอนนี้ลอง:

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

มันทำงานยังไง

เนื่องจากผู้คนสงสัยว่ามันทำงานอย่างไร (ตามความคิดเห็น) นี่คือคำอธิบาย หากต้องการเรียนรู้วิธีเขียนแมโครให้เลือกหนังสือ Common Lisp ที่ดี (ใช่ Common LISP คุณจะสามารถทำสิ่งเดียวกันใน Emacs Lisp ได้ แต่ Common LISP นั้นมีพลังมากกว่าและมีหนังสือที่ดีกว่า IMHO)

แมโครทำงานบนรหัสดิบ มาโครไม่ได้ประเมินอาร์กิวเมนต์ของพวกเขา (ต่างจากฟังก์ชั่น) ดังนั้นเราจึงไม่ได้ประเมินค่าvalueและเก็บสะสมที่นี่varsสำหรับมาโครของเราเป็นเพียงสัญลักษณ์

prognจัดกลุ่มหลายsetqรูปแบบให้เป็นหนึ่งเดียว สิ่งนี้:

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

เพียงสร้างรายการsetqแบบฟอร์มโดยใช้ตัวอย่างของ OP ซึ่งจะเป็น:

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

คุณจะเห็นรูปแบบคือภายในของแบบฟอร์ม backquote ,และจะนำหน้าด้วยเครื่องหมายจุลภาค รูปแบบ backquote ภายในทุกอย่างจะถูกยกมาตามปกติ แต่การประเมินแบบ, "เปิดเครื่อง" เป็นการชั่วคราวดังนั้นทั้งหมดmapcarจะถูกประเมินในเวลามหภาค

ในที่สุดก็@ลบวงเล็บด้านนอกออกจากรายการด้วยsetqs ดังนั้นเราจะได้รับ:

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

มาโครสามารถแปลงซอร์สโค้ดของคุณเองได้ไม่ดีใช่ไหม

ข้อแม้

นี่คือข้อแม้เล็ก ๆ อาร์กิวเมนต์แรกจะได้รับการประเมินหลายครั้งเพราะมาโครนี้จะขยายต่อไปนี้:

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

คุณจะเห็นว่าถ้าคุณมีตัวแปรหรือสตริงที่นี่มันก็โอเค แต่ถ้าคุณเขียนอะไรเช่นนี้:

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

จากนั้นฟังก์ชั่นของคุณจะถูกเรียกมากกว่าหนึ่งครั้ง สิ่งนี้อาจไม่พึงประสงค์ นี่คือวิธีแก้ไขด้วยความช่วยเหลือของonce-only(มีอยู่ใน แพ็คเกจ 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))))

และปัญหาก็หมดไป


1
จะดีถ้าคุณสามารถเพิ่มคำอธิบายบางวิธีการทำงานนี้และสิ่งที่รหัสนี้จะทำในรายละเอียดมากขึ้นเพื่อให้คนในครั้งต่อไปสามารถเขียนอะไรเช่นนี้ตัวเอง :)
clemera

คุณไม่ต้องการประเมินvalueในเวลาที่ขยายมาโคร
ทาร์ซิอุส

@tarsius ผมไม่ได้: ->(macroexpand '(setq-every user-emacs-directory f-loc1 f-loc2)) (progn (setq f-loc1 user-emacs-directory) (setq f-loc2 user-emacs-directory))หากvalueถูกประเมิน ณ เวลาที่ขยายตัวมหภาคมันจะถูกแทนที่ด้วยสตริงจริง คุณสามารถวางการเรียกใช้ฟังก์ชันโดยมีผลข้างเคียงแทนที่จะvalueไม่ถูกประเมินจนกว่าจะมีการเรียกใช้รหัสที่ขยายแล้วลองใช้งาน valueเนื่องจากอาร์กิวเมนต์ของแมโครไม่ได้รับการประเมินโดยอัตโนมัติ ปัญหาจริงคือvalueจะมีการประเมินหลายครั้งซึ่งอาจไม่พึงประสงค์once-onlyอาจช่วยได้ที่นี่
Mark Karpov

1
แทนที่จะmmt-once-only(ซึ่งจะต้องมี DEP ภายนอก), macroexp-let2คุณสามารถใช้
YoungFrog

2
ฮึ. คำถามร้องออกมาให้ใช้การวนซ้ำด้วยsetไม่ใช่ห่อsetqในมาโคร หรือเพียงใช้setqกับ args หลายรายการรวมถึงการซ้ำ args
ดึง

7

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))

2
วิธีนี้ใช้ไม่ได้กับตัวแปรที่ผูกไว้ด้วยคำศัพท์
npostavs

@npostavs: จริง แต่อาจเป็นสิ่งที่ OP ต้องการ / ต้องการ ถ้าเขาไม่ได้ใช้ตัวแปรคำศัพท์มันเป็นวิธีการที่แน่นอน อย่างไรก็ตามคุณพูดถูกว่า "ตัวแปร" นั้นคลุมเครือดังนั้นโดยทั่วไปคำถามอาจหมายถึงตัวแปรชนิดใดก็ได้ กรณีการใช้งานจริงไม่ได้ถูกนำเสนออย่างดี IMO
ดึง
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.