เมื่อใดที่ฉันควรใช้มาโครในโปรแกรมหรือไม่
คำถามนี้ได้รับแรงบันดาลใจจากคำตอบที่ให้ข้อมูลโดย @tarsius ฉันเชื่อว่าหลายคนมีประสบการณ์ที่ยอดเยี่ยมในการแบ่งปันผู้อื่น ฉันหวังว่าคำถามนี้กลายเป็นหนึ่งในคำถามอัตนัยที่ดีและช่วยโปรแกรมเมอร์ของ Emacs
เมื่อใดที่ฉันควรใช้มาโครในโปรแกรมหรือไม่
คำถามนี้ได้รับแรงบันดาลใจจากคำตอบที่ให้ข้อมูลโดย @tarsius ฉันเชื่อว่าหลายคนมีประสบการณ์ที่ยอดเยี่ยมในการแบ่งปันผู้อื่น ฉันหวังว่าคำถามนี้กลายเป็นหนึ่งในคำถามอัตนัยที่ดีและช่วยโปรแกรมเมอร์ของ Emacs
คำตอบ:
ใช้มาโครสำหรับไวยากรณ์แต่ไม่ต้องมีความหมาย
มันเป็นเรื่องดีที่จะใช้แมโครสำหรับน้ำตาลประโยค แต่มันเป็นความคิดที่ดีที่จะใส่ตรรกะลงในร่างกายของแมโครโดยไม่คำนึงว่ามันจะอยู่ในแมโครหรือในการขยายตัว หากคุณรู้สึกว่าจำเป็นต้องทำเช่นนั้นให้เขียนฟังก์ชันแทนและ "มาโครคู่หู" สำหรับน้ำตาลประโยค พูดง่ายๆก็คือถ้าคุณมีlet
มาโครอยู่แล้วคุณกำลังมองหาปัญหา
มาโครมีข้อเสียมากมาย:
จากประสบการณ์การอ่านการดีบักและการแก้ไขโค้ด Elisp ของฉันและของผู้อื่นฉันแนะนำให้หลีกเลี่ยงมาโครให้มากที่สุด
สิ่งที่ชัดเจนเกี่ยวกับมาโครคือพวกเขาไม่ได้ประเมินข้อโต้แย้ง ซึ่งหมายความว่าหากคุณมีการแสดงออกที่ซับซ้อนห่อในแมโครคุณไม่มีความคิดว่าจะได้รับการประเมินหรือไม่และในบริบทใดเว้นแต่คุณจะค้นหาคำนิยามแมโคร นี่อาจไม่ใช่เรื่องใหญ่สำหรับตัวคุณเองเนื่องจากคุณรู้คำจำกัดความของมาโครของคุณ (ในปัจจุบันอาจจะไม่กี่ปีต่อมา)
ข้อเสียนี้ใช้ไม่ได้กับฟังก์ชั่น: args ทั้งหมดจะถูกประเมินก่อนการเรียกใช้ฟังก์ชัน ซึ่งหมายความว่าคุณสามารถสร้างความซับซ้อน eval ในสถานการณ์การดีบักที่ไม่รู้จัก คุณสามารถข้ามคำจำกัดความของฟังก์ชั่นที่คุณไม่รู้จักได้ตราบใดที่พวกเขาส่งคืนข้อมูลที่ตรงไปตรงมาและคุณคิดว่าพวกเขาไม่ได้สัมผัสกับสถานะโลก
หากต้องการใส่รหัสใหม่ด้วยวิธีอื่นแมโครจะกลายเป็นส่วนหนึ่งของภาษา หากคุณกำลังอ่านโค้ดที่มีมาโครที่ไม่รู้จักคุณเกือบจะอ่านโค้ดในภาษาที่คุณไม่รู้
แมโครมาตรฐานชอบpush
, pop
, dolist
ทำจริงๆมากสำหรับคุณและเป็นที่รู้จักกันเขียนโปรแกรมอื่น ๆ พวกมันเป็นส่วนหนึ่งของข้อมูลจำเพาะภาษา แต่มันเป็นไปไม่ได้ที่จะสร้างแมโครของคุณให้เป็นมาตรฐาน ไม่เพียง แต่จะยากที่จะคิดหาสิ่งที่ง่ายและมีประโยชน์อย่างตัวอย่างด้านบน แต่ยิ่งยากขึ้นคือการโน้มน้าวให้โปรแกรมเมอร์ทุกคนเรียนรู้
นอกจากนี้ผมไม่ทราบเหมือนแมโครsave-selected-window
: พวกเขาเก็บและเรียกคืนรัฐและประเมินผลการศึกษาของพวกเขา args ทางเดียวกันprogn
ไม่ ตรงไปตรงมาสวยทำให้รหัสง่ายขึ้น
ตัวอย่างของแมโครตกลงอื่นได้สิ่งที่ต้องการหรือdefhydra
use-package
มาโครเหล่านี้มีภาษามินิเป็นของตัวเอง progn
และพวกเขารักษาข้อมูลง่ายทั้งยังคงทำหน้าที่เหมือนหรือ รวมทั้งพวกมันมักจะอยู่ที่ระดับบนสุดดังนั้นจึงไม่มีตัวแปรใน call stack สำหรับอาร์กิวเมนต์ของแมโครที่ต้องพึ่งพาทำให้ง่ายขึ้นเล็กน้อย
ตัวอย่างของมาโครที่แย่ในความคิดของฉันคือmagit-section-action
และmagit-section-case
ใน Magit อันแรกถูกเอาออกไปแล้ว แต่อันที่สองยังคงอยู่
ฉันจะทื่อ: ฉันไม่เข้าใจว่า "ใช้สำหรับไวยากรณ์ไม่ใช่เพื่อความหมาย" นี่คือเหตุผลที่ส่วนหนึ่งของคำตอบนี้จะจัดการกับคำตอบของLunaryonและส่วนอื่น ๆ จะเป็นความพยายามของฉันในการตอบคำถามเดิม
เมื่อคำว่า "ซีแมนทิกส์" ใช้ในการเขียนโปรแกรมมันหมายถึงวิธีที่ผู้เขียนภาษาเลือกที่จะตีความภาษาของพวกเขา โดยทั่วไปแล้วสิ่งนี้จะลดลงเป็นชุดของสูตรที่แปลงกฎไวยากรณ์ของภาษาเป็นกฎอื่น ๆ ที่ผู้อ่านคาดว่าจะคุ้นเคย ตัวอย่างเช่น Plotkin ในหนังสือของเขาApproach Approach ต่อ Operational Semanticsใช้ภาษาที่ได้จากตรรกะเพื่ออธิบายว่าไวยากรณ์ที่ประเมินได้คืออะไร (หมายถึงอะไรในการรันโปรแกรม) กล่าวอีกนัยหนึ่งถ้าไวยากรณ์คือสิ่งที่ก่อให้เกิดภาษาการเขียนโปรแกรมในบางระดับแล้วมันจะต้องมีความหมายบางอย่างมิฉะนั้นมันคือ ... ไม่ใช่ภาษาโปรแกรม
ทีนี้เรามาลืมคำจำกัดความที่เป็นทางการกันสักหน่อยมันสำคัญที่คุณต้องเข้าใจความตั้งใจ ดังนั้นดูเหมือนว่า Lunaryon จะกระตุ้นให้ผู้อ่านของเขาใช้มาโครเมื่อไม่มีการเพิ่มความหมายใหม่ลงในโปรแกรม แต่เป็นเพียงตัวย่อบางอย่าง สำหรับฉันนี่ฟังดูแปลกและตรงกันข้ามกับการใช้งานมาโครจริง ๆ ด้านล่างเป็นตัวอย่างที่สร้างความหมายใหม่อย่างชัดเจน:
defun
และเพื่อนซึ่งเป็นส่วนสำคัญของภาษาไม่สามารถอธิบายได้ในเงื่อนไขเดียวกับที่คุณจะอธิบายถึงการเรียกใช้ฟังก์ชัน
setf
(setf (aref x y) z)
กฎอธิบายถึงวิธีการฟังก์ชั่นการทำงานมีไม่เพียงพอที่จะอธิบายถึงผลกระทบของการแสดงออกเช่น
with-output-to-string
นอกจากนี้ยังเปลี่ยนความหมายของรหัสภายในแมโคร ในทำนองเดียวกันwith-current-buffer
และwith-
มาโครอื่น ๆ
dotimes
และที่คล้ายกันไม่สามารถอธิบายได้ในแง่ของฟังก์ชั่น
ignore-errors
เปลี่ยนซีแมนทิกส์ของโค๊ดที่มันแรป
มีมาโครจำนวนมากที่กำหนดไว้ในcl-lib
แพคเกจeieio
แพ็คเกจและห้องสมุดอื่น ๆ ที่ใช้กันอย่างแพร่หลายใน Emacs Lisp ซึ่งในขณะที่อาจดูเหมือนว่ารูปแบบฟังก์ชั่นมีการตีความที่แตกต่างกัน
ดังนั้นหากมีอะไรมาโครเป็นเครื่องมือในการแนะนำความหมายใหม่ให้เป็นภาษา ฉันไม่สามารถคิดวิธีอื่นในการทำเช่นนั้น (อย่างน้อยก็ไม่ใช่ใน Emacs Lisp)
เมื่อฉันจะไม่ใช้มาโคร:
เมื่อพวกเขาไม่แนะนำโครงสร้างใหม่ใด ๆ ด้วยความหมายใหม่ (เช่นฟังก์ชั่นจะทำงานได้ดีเหมือนกัน)
เมื่อนี่เป็นเพียงความสะดวกสบาย (เช่นการสร้างแมโครที่มีชื่อmvb
ซึ่งขยายเป็นcl-multiple-value-bind
เพียงเพื่อทำให้สั้นลง)
เมื่อฉันคาดว่ารหัสข้อผิดพลาดจำนวนมากจะถูกซ่อนไว้โดยแมโคร (ตามที่ได้รับการบันทึกไว้แมโครจะยากต่อการแก้ไขข้อผิดพลาด)
เมื่อฉันต้องการมากมาโครมากกว่าฟังก์ชั่น:
เมื่อแนวคิดเฉพาะโดเมนถูกบดบังด้วยการเรียกใช้ฟังก์ชัน ตัวอย่างเช่นหากต้องการส่งcontext
อาร์กิวเมนต์เดียวกันไปยังฟังก์ชันที่ต้องเรียกใช้อย่างต่อเนื่องฉันต้องการล้อมรอบด้วยแมโครซึ่งcontext
อาร์กิวเมนต์นั้นถูกซ่อนไว้
เมื่อรหัสทั่วไปในรูปแบบฟังก์ชั่นเป็น verbose มากเกินไป (ซ้ำซ้ำสร้างใน Emacs Lisp เกินไป verbose กับรสนิยมของฉันและไม่จำเป็นต้องเปิดเผยโปรแกรมเมอร์เพื่อรายละเอียดการใช้งานในระดับต่ำ)
ด้านล่างนี้เป็นเวอร์ชั่นที่ได้รับการรดน้ำซึ่งมีจุดประสงค์เพื่อให้สัญชาตญาณว่าสิ่งที่มีความหมายโดยความหมายในวิทยาการคอมพิวเตอร์
สมมติว่าคุณมีภาษาการเขียนโปรแกรมอย่างง่าย ๆ ด้วยกฎไวยากรณ์เหล่านี้:
variable := 1 | 0
operation := +
expression := variable | (expression operation expression)
กับภาษานี้เราสามารถสร้าง "โปรแกรม" ชอบ(1+0)+1
หรือ0
หรือ((0+0))
ฯลฯ
แต่ตราบใดที่เรายังไม่มีกฎความหมายโปรแกรม "เหล่านี้ไม่มีความหมาย
สมมติว่าตอนนี้เราติดตั้งภาษาของเราด้วยกฎ (semantic):
0+0 0+1 1+0 1+1 (exp)
---, ---, ---, ---, -----
0 1+0 1 0 exp
ตอนนี้เราสามารถคำนวณได้โดยใช้ภาษานี้ นั่นคือเราสามารถนำเสนอการสร้างประโยคเข้าใจมันอย่างเป็นนามธรรม (ในคำอื่น ๆ เปลี่ยนเป็นไวยากรณ์ที่เป็นนามธรรม) จากนั้นใช้กฎความหมายเพื่อจัดการกับการเป็นตัวแทนนี้
เมื่อเราพูดถึงตระกูลภาษา Lisp กฎความหมายพื้นฐานของการประเมินฟังก์ชั่นคร่าวๆคือ lambda-แคลคูลัส:
(f x) (lambda x y) x
-----, ------------, ---
fx ^x.y x
การอ้างถึงและกลไกการขยายตัวของแมโครนั้นรู้จักกันในชื่อเครื่องมือการเขียนโปรแกรมเมตาคือพวกมันยอมให้ใครคนหนึ่งพูดถึงโปรแกรมแทนที่จะเขียนโปรแกรม พวกเขาประสบความสำเร็จโดยการสร้างความหมายใหม่โดยใช้ "เมตาเลเยอร์" ตัวอย่างของแมโครอย่างง่ายคือ:
(a . b)
-------
(cons a b)
นี่ไม่ใช่แมโครใน Emacs Lisp แต่อาจเป็นได้ ฉันเลือกเพื่อความเรียบง่ายเท่านั้น โปรดสังเกตว่าไม่มีกฎความหมายที่กำหนดไว้ข้างต้นจะใช้กับกรณีนี้เนื่องจากการ(f x)
ตีความตัวเลือกที่ใกล้เคียงที่สุดf
เป็นฟังก์ชั่นในขณะที่a
ไม่จำเป็นต้องเป็นฟังก์ชัน
(x y)
คือประมาณ "ประเมิน y แล้วจึงเรียก x ด้วยผลลัพธ์ของการประเมินก่อนหน้า" เมื่อคุณเขียน(defun x y)
y จะไม่ถูกประเมินและไม่มี x ไม่ว่าคุณจะสามารถเขียนโค้ดที่มีเอฟเฟ็กต์เทียบเท่าโดยใช้ซีแมนทิกส์ที่แตกต่างกันไม่ได้ปฏิเสธโค้ดชิ้นนี้ที่มีซีแมนทิกส์เป็นของตัวเอง
(defun x y)
แอปพลิเคชัน vs ฟังก์ชัน