อะไรทำให้มาโคร Lisp มีความพิเศษ?


297

การอ่านบทความของ Paul Graham เกี่ยวกับภาษาการเขียนโปรแกรมเราคิดว่ามาโคร Lispเป็นวิธีเดียวที่จะไป ในฐานะนักพัฒนาที่ยุ่งทำงานกับแพลตฟอร์มอื่นฉันไม่มีสิทธิ์ใช้งาน Lisp macros ในฐานะคนที่ต้องการเข้าใจเรื่องปากต่อปากโปรดอธิบายว่าอะไรทำให้คุณสมบัตินี้มีประสิทธิภาพมาก

โปรดเชื่อมโยงสิ่งนี้กับสิ่งที่ฉันจะเข้าใจจากโลกของการพัฒนา Python, Java, C # หรือ C


2
โดยวิธีการที่มีตัวประมวลผลแมโครสไตล์ LISP สำหรับ C # เรียกว่า LeMP คือ: ecsharp.net/lemp ... JavaScript ยังมีหนึ่งที่เรียกว่า Sweet.js: sweetjs.org
Qwertie

@Qwertie sweetjs ใช้งานได้แม้กระทั่งทุกวันนี้?
fredrik.hjarner

ฉันไม่ได้ใช้มัน แต่ความมุ่งมั่นล่าสุดคือเมื่อหกเดือนที่แล้ว ... ดีพอสำหรับฉัน!
Qwertie

คำตอบ:


308

เพื่อให้คำตอบสั้น ๆ แมโครจะใช้สำหรับการกำหนดส่วนขยายไวยากรณ์ภาษาเพื่อ Common Lisp หรือ Domain Specific Languages ​​(DSLs) ภาษาเหล่านี้ฝังอยู่ในรหัสเสียงกระเพื่อมที่มีอยู่ ตอนนี้ DSL สามารถมีไวยากรณ์คล้ายกับ Lisp (เช่นProlog Interpreterสำหรับ Lisp ของ Peter Norvig ) หรือแตกต่างอย่างสิ้นเชิง (เช่นInfix Notation Mathสำหรับ Clojure)

นี่เป็นตัวอย่างที่เป็นรูปธรรมมากขึ้น:
Python มีรายการความเข้าใจในภาษา สิ่งนี้จะให้ไวยากรณ์ที่ง่ายสำหรับกรณีทั่วไป เส้น

divisibleByTwo = [x for x in range(10) if x % 2 == 0]

ให้ผลลัพธ์รายการที่มีตัวเลขคู่ทั้งหมดตั้งแต่ 0 ถึง 9 ย้อนกลับไปในPython 1.5วันที่ไม่มีไวยากรณ์ดังกล่าว คุณจะใช้สิ่งนี้มากกว่านี้:

divisibleByTwo = []
for x in range( 10 ):
   if x % 2 == 0:
      divisibleByTwo.append( x )

สิ่งเหล่านี้มีหน้าที่เทียบเท่ากัน เราขอหยุดการไม่เชื่อและแกล้งทำเป็น Lisp มีมาโครวง จำกัด ที่ทำซ้ำและไม่มีวิธีง่ายๆในการทำรายการเทียบเท่าความเข้าใจ

ใน Lisp คุณสามารถเขียนสิ่งต่อไปนี้ ฉันควรทราบว่าตัวอย่างที่ถูกประดิษฐ์นี้ถูกเลือกให้เหมือนกับรหัส Python ไม่ใช่ตัวอย่างที่ดีของ Lisp code

;; the following two functions just make equivalent of Python's range function
;; you can safely ignore them unless you are running this code
(defun range-helper (x)
  (if (= x 0)
      (list x)
      (cons x (range-helper (- x 1)))))

(defun range (x)
  (reverse (range-helper (- x 1))))

;; equivalent to the python example:
;; define a variable
(defvar divisibleByTwo nil)

;; loop from 0 upto and including 9
(loop for x in (range 10)
   ;; test for divisibility by two
   if (= (mod x 2) 0) 
   ;; append to the list
   do (setq divisibleByTwo (append divisibleByTwo (list x))))

ก่อนที่ฉันจะไปต่อฉันควรอธิบายว่าแมโครคืออะไร มันคือการแปลงที่ดำเนินการกับรหัสด้วยรหัส นั่นคือส่วนหนึ่งของรหัสอ่านโดยล่าม (หรือคอมไพเลอร์) ซึ่งใช้เป็นรหัสในการโต้แย้งจัดการและผลตอบแทนที่ได้ซึ่งจะทำงานในสถานที่

แน่นอนว่าการพิมพ์จำนวนมากและโปรแกรมเมอร์นั้นขี้เกียจ ดังนั้นเราสามารถกำหนด DSL สำหรับทำรายการความเข้าใจ ในความเป็นจริงเราใช้มาโครหนึ่งตัวอยู่แล้ว (มาโครวนรอบ)

เสียงกระเพื่อมกำหนดสองรูปแบบไวยากรณ์พิเศษ เครื่องหมายคำพูด ( ') ระบุโทเค็นถัดไปเป็นตัวอักษร quasiquote หรือ backtick ( `) ระบุโทเค็นถัดไปเป็นตัวอักษรที่มีทางหนี Escapes ถูกระบุโดยโอเปอเรเตอร์จุลภาค ตัวหนังสือเทียบเท่าของงูหลามของ'(1 2 3) [1, 2, 3]คุณสามารถกำหนดให้กับตัวแปรอื่นหรือใช้แทน คุณสามารถคิดได้ว่า`(1 2 ,x)เทียบเท่ากับ Python [1, 2, x]ซึ่งxเป็นตัวแปรที่กำหนดไว้ก่อนหน้านี้ สัญลักษณ์รายการนี้เป็นส่วนหนึ่งของเวทย์มนตร์ที่เข้าสู่มาโคร ส่วนที่สองคือตัวอ่านเสียงกระเพื่อมซึ่งแทนที่มาโครอย่างชาญฉลาดสำหรับโค้ด แต่เป็นภาพที่ดีที่สุดด้านล่าง:

ดังนั้นเราสามารถกำหนดแมโครที่เรียกว่าlcomp(ย่อมาจาก list comprehension) ไวยากรณ์ของมันจะเหมือนกับงูหลามที่เราใช้ในตัวอย่าง[x for x in range(10) if x % 2 == 0] -(lcomp x for x in (range 10) if (= (% x 2) 0))

(defmacro lcomp (expression for var in list conditional conditional-test)
  ;; create a unique variable name for the result
  (let ((result (gensym)))
    ;; the arguments are really code so we can substitute them 
    ;; store nil in the unique variable name generated above
    `(let ((,result nil))
       ;; var is a variable name
       ;; list is the list literal we are suppose to iterate over
       (loop for ,var in ,list
            ;; conditional is if or unless
            ;; conditional-test is (= (mod x 2) 0) in our examples
            ,conditional ,conditional-test
            ;; and this is the action from the earlier lisp example
            ;; result = result + [x] in python
            do (setq ,result (append ,result (list ,expression))))
           ;; return the result 
       ,result)))

ตอนนี้เราสามารถดำเนินการได้ที่บรรทัดคำสั่ง:

CL-USER> (lcomp x for x in (range 10) if (= (mod x 2) 0))
(0 2 4 6 8)

ค่อนข้างเรียบร้อยใช่มั้ย ตอนนี้มันไม่หยุดแค่นั้น คุณมีกลไกหรือพู่กันถ้าคุณชอบ คุณสามารถมีไวยากรณ์ใด ๆ ที่คุณอาจต้องการ เช่นเดียวกับ Python หรือwithไวยากรณ์ของ C # หรือไวยากรณ์ LINQ ของ. NET ท้ายที่สุดนี่คือสิ่งที่ดึงดูดผู้คนสู่ Lisp - ความยืดหยุ่นสูงสุด


51
+1 สำหรับการนำความเข้าใจของรายการไปใช้ใน Lisp เพราะเหตุใด
ckb

9
@ckb จริงชัดแล้วมีแมโครรายการความเข้าใจในมาตรฐานห้องสมุด: (loop for x from 0 below 10 when (evenp x) collect x), ตัวอย่างเพิ่มเติมได้ที่นี่ แต่แท้จริงแล้วลูปคือ "เพียงมาโคร" (จริง ๆ แล้วฉันนำไปใช้ใหม่ตั้งแต่เริ่มต้นเมื่อไม่นานมานี้ )
Suzanne Dupéron

8
ฉันรู้ว่ามันค่อนข้างไม่เกี่ยวข้อง แต่ฉันสงสัยเกี่ยวกับไวยากรณ์และวิธีการแยกวิเคราะห์ใช้งานได้จริง ... สมมติว่าฉันเรียก lcomp ด้วยวิธีนั้น (เปลี่ยนรายการที่สามจาก "เป็น" เป็น "azertyuiop"): (lcomp x azertyuiop x ใน ( ช่วง 10) ถ้า (= (% (2 x 2) 0)) แมโครจะยังคงทำงานตามที่คาดไว้หรือไม่ หรือเป็นพารามิเตอร์ "for" ที่ใช้ในการวนซ้ำเพื่อให้มันต้องเป็นสตริง "สำหรับ" เมื่อเรียก?
dader

2
@dader ดังนั้น loop เป็นจริงอีกแมโครหนึ่งและสำหรับเป็นสัญลักษณ์พิเศษที่ใช้ในแมโครนั้น ฉันสามารถกำหนดแมโครได้ง่ายโดยไม่ต้องใช้วนรอบ อย่างไรก็ตามถ้าฉันมีโพสต์จะนานกว่านี้มาก แมโครห่วงและไวยากรณ์ที่ถูกกำหนดไว้ในCLtL
gte525u

3
สิ่งหนึ่งที่ฉันสับสนเกี่ยวกับมาโครของภาษาอื่นคือมาโครของพวกเขาถูก จำกัด โดยไวยากรณ์ของภาษาโฮสต์ Lispy แมโครสามารถตีความไวยากรณ์ที่ไม่ใช่ Lispy ได้หรือไม่ ฉันหมายถึงชอบจินตนาการว่าการสร้างฮาเซลเช่นไวยากรณ์ (ไม่มี paranthese) และตีความมันโดยใช้มาโคร Lisp สิ่งนี้เป็นไปได้หรือไม่และข้อดีข้อเสียของการใช้มาโครเมื่อเปรียบเทียบกับการใช้ lexer และ parser โดยตรงคืออะไร
CMCDragonkai

105

คุณจะพบกับการถกเถียงที่ครอบคลุมรอบ ๆแมโครที่ชัดแจ้ง

ส่วนย่อยที่น่าสนใจของบทความนั้น:

ในภาษาโปรแกรมส่วนใหญ่ไวยากรณ์นั้นซับซ้อน มาโครต้องแยกไวยากรณ์ของโปรแกรมวิเคราะห์และประกอบใหม่ พวกเขาไม่สามารถเข้าถึงตัวแยกวิเคราะห์ของโปรแกรมดังนั้นพวกเขาจึงต้องพึ่งพาการวิเคราะห์พฤติกรรมและการคาดเดาที่ดีที่สุด บางครั้งการวิเคราะห์อัตราการตัดของพวกเขาผิดและจากนั้นก็หยุด

แต่เสียงกระเพื่อมแตกต่างกัน แมโครชัดไม่ได้มีการเข้าถึงแยกวิเคราะห์และมันเป็น parser ง่ายจริงๆ แมโคร Lisp ไม่ได้ส่งสตริง แต่ชิ้นส่วนของซอร์สโค้ดที่เตรียมไว้แล้วในรูปแบบของรายการเนื่องจากซอร์สของโปรแกรม Lisp ไม่ใช่สตริง มันเป็นรายการ และโปรแกรมเสียงกระเพื่อมนั้นดีมากในการแยกรายการและทำให้พวกเขากลับมารวมกัน พวกเขาทำสิ่งนี้ได้อย่างน่าเชื่อถือทุกวัน

นี่คือตัวอย่างเพิ่มเติม Lisp มีมาโครเรียกว่า "setf" ซึ่งจะทำการมอบหมาย รูปแบบที่ง่ายที่สุดของ setf คือ

  (setf x whatever)

ซึ่งกำหนดค่าของสัญลักษณ์ "x" เป็นค่าของนิพจน์ "อะไรก็ตาม"

เสียงกระเพื่อมยังมีรายการ; คุณสามารถใช้ฟังก์ชั่น "car" และ "cdr" เพื่อรับองค์ประกอบแรกของรายการหรือส่วนที่เหลือของรายการตามลำดับ

ตอนนี้ถ้าคุณต้องการแทนที่องค์ประกอบแรกของรายการด้วยค่าใหม่ มีฟังก์ชั่นมาตรฐานสำหรับการทำเช่นนั้นและชื่อของมันนั้นแย่ยิ่งกว่า "รถยนต์" มันคือ "rplaca" แต่คุณไม่จำเป็นต้องจำ "rplaca" เพราะคุณสามารถเขียนได้

  (setf (car somelist) whatever)

เพื่อตั้งค่ารถของนักโซลิสต์

สิ่งที่เกิดขึ้นจริงที่นี่คือ "setf" เป็นมาโคร ในเวลารวบรวมมันจะตรวจสอบข้อโต้แย้งของตนและจะเห็นว่าคนแรกมีรูปแบบ (รถยนต์บางครั้ง) มันบอกกับตัวเองว่า "โอ้โปรแกรมเมอร์กำลังพยายามทำให้รถของทุกอย่างฟังก์ชั่นที่ใช้สำหรับนั่นคือ 'rplaca'" และมันจะเขียนรหัสอย่างเงียบ ๆ เพื่อ:

  (rplaca somelist whatever)

5
setf เป็นตัวอย่างที่ดีเกี่ยวกับพลังของมาโครขอบคุณที่รวมมัน
Joel

ฉันชอบไฮไลต์.. เพราะแหล่งที่มาของโปรแกรมเสียงกระเพื่อมไม่ใช่สตริง; มันเป็นรายการ ! มันเป็นเหตุผลหลักว่าทำไมมาโคร LISP นั้นยอดเยี่ยมกว่าส่วนใหญ่ของคนอื่น ๆ เนื่องจากวงเล็บ?
นักศึกษา

@ นักเรียนฉันคิดเช่นนั้น: books.google.fr/…ขอแนะนำให้คุณพูดถูก
VonC

55

มาโครเสียงกระเพื่อมสามัญเป็นหลักขยาย "ไวยากรณ์ดั้งเดิม" ของรหัสของคุณ

ตัวอย่างเช่นใน C การสร้างสวิตช์ / ตัวพิมพ์ใหญ่นั้นทำงานได้กับประเภทที่สำคัญเท่านั้นและถ้าคุณต้องการใช้มันสำหรับการลอยหรือสตริงคุณจะถูกปล่อยให้ซ้อนกับคำสั่งและการเปรียบเทียบที่ชัดเจน นอกจากนี้ยังไม่มีวิธีที่คุณสามารถเขียนแมโคร C เพื่อทำงานให้คุณ

แต่เนื่องจากแมโคร lisp คือ (โดยหลัก) โปรแกรม lisp ที่ใช้ตัวอย่างโค้ดเป็นอินพุตและโค้ดส่งคืนเพื่อแทนที่ "การเรียกใช้" ของแมโครคุณสามารถขยายรายการ "ดั้งเดิม" ได้เท่าที่คุณต้องการซึ่งมักจะลงท้ายด้วย ด้วยโปรแกรมที่อ่านได้มากขึ้น

ในการทำเช่นเดียวกันใน C คุณจะต้องเขียนตัวประมวลผลล่วงหน้าที่กำหนดเองซึ่งกินแหล่งข้อมูลเริ่มต้น (ไม่ใช่แบบค่อนข้าง -C) ของคุณและแยกแยะสิ่งที่คอมไพเลอร์ C เข้าใจได้ มันไม่ใช่วิธีที่ผิดที่จะไปเกี่ยวกับเรื่องนี้ แต่ไม่จำเป็นต้องง่ายที่สุด


41

แมโคร Lisp ช่วยให้คุณตัดสินใจได้ว่าเมื่อใดที่ส่วนใดส่วนหนึ่งหรือนิพจน์จะได้รับการประเมิน หากต้องการยกตัวอย่างง่ายๆให้นึกถึง C:

expr1 && expr2 && expr3 ...

สิ่งที่กล่าวนี้คือ: ประเมินexpr1และควรเป็นจริงประเมินexpr2ฯลฯ

ทีนี้ลองทำให้นี่&&เป็นฟังก์ชั่น ... ใช่แล้วคุณทำไม่ได้ กำลังโทรหาบางสิ่งเช่น:

and(expr1, expr2, expr3)

จะประเมินทั้งสามexprsก่อนที่จะให้คำตอบโดยไม่คำนึงว่าexpr1เป็นเท็จ

ด้วยมาโคร lisp คุณสามารถเขียนโค้ดดังนี้:

(defmacro && (expr1 &rest exprs)
    `(if ,expr1                     ;` Warning: I have not tested
         (&& ,@exprs)               ;   this and might be wrong!
         nil))

ตอนนี้คุณมี&&, ซึ่งคุณสามารถเรียกได้เหมือนฟังก์ชั่นและมันจะไม่ประเมินรูปแบบใด ๆ ที่คุณส่งไปให้เว้นแต่ว่ามันจะเป็นจริงทั้งหมด

เพื่อดูว่าสิ่งนี้มีประโยชน์อย่างไร

(&& (very-cheap-operation)
    (very-expensive-operation)
    (operation-with-serious-side-effects))

และ:

and(very_cheap_operation(),
    very_expensive_operation(),
    operation_with_serious_side_effects());

สิ่งอื่น ๆ ที่คุณสามารถทำได้กับมาโครกำลังสร้างคำหลักใหม่และ / หรือมินิภาษา (ลองดู(loop ...)ตัวอย่าง), รวมภาษาอื่น ๆ เข้ากับเสียงกระเพื่อมเช่นคุณสามารถเขียนมาโครที่ให้คุณพูดสิ่งต่อไปนี้:

(setvar *rows* (sql select count(*)
                      from some-table
                     where column1 = "Yes"
                       and column2 like "some%string%")

และ thats ไม่ได้รับเข้ามาในแมโครอ่าน

หวังว่านี่จะช่วยได้


ฉันคิดว่ามันควรจะเป็น: "(ใช้ &&, @ exprs) สิ่งนี้และอาจผิด!"
Svante

1
@svante - นับเป็นสองครั้ง: ลำดับแรก && เป็นมาโครไม่ใช่ฟังก์ชัน ใช้งานได้เฉพาะในฟังก์ชั่น อันดับที่สองใช้รายการของอาร์กิวเมนต์ที่จะผ่านดังนั้นคุณต้องการหนึ่งใน "(funcall fn, @ exprs)", "(ใช้ fn (รายการ, @ exprs)" หรือ "(ใช้ fn, @ exprs nil)" ไม่ใช่ "(ใช้ fn, @ exprs)".
Aaron

(and ...จะประเมินนิพจน์จนกว่าจะมีการประเมินว่าเป็นเท็จโปรดทราบว่าผลข้างเคียงที่เกิดขึ้นจากการประเมินที่ผิดจะเกิดขึ้นเฉพาะนิพจน์ที่ตามมาเท่านั้นที่จะถูกข้าม
ocodo

31

ฉันไม่คิดว่าฉันเคยเห็นมาโคร Lisp มาก่อนอธิบายได้ดีกว่านี้: http://www.defmacro.org/ramblings/lisp.html


3
โดยเฉพาะอย่างยิ่งถ้าคุณมีพื้นหลัง Java / XML
sunsations

1
ช่างเป็นความสุขที่ได้อ่านเรื่องนี้บนโซฟาของฉันในบ่ายวันเสาร์! เขียนและจัดระเบียบอย่างชัดเจนมาก
โคลิน

10

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

  • ไวยากรณ์มาโคร / แม่แบบที่ จำกัด จำกัด การใช้งาน ตัวอย่างเช่นคุณไม่สามารถเขียนแม่แบบที่ขยายไปยังสิ่งอื่นนอกเหนือจากชั้นเรียนหรือฟังก์ชั่น มาโครและเทมเพลตไม่สามารถรักษาข้อมูลภายในได้อย่างง่ายดาย
  • ไวยากรณ์ที่ซับซ้อนและผิดปกติอย่างมากของ C และ C ++ ทำให้ยากต่อการเขียนมาโครทั่วไป

มาโคร Lisp และ Lisp แก้ปัญหาเหล่านี้ได้

  • มาโคร Lisp เขียนด้วย Lisp คุณมีพลังเต็มที่ในการเขียนแมโคร
  • เสียงกระเพื่อมมีไวยากรณ์ปกติมาก

พูดคุยกับทุกคนที่มีความเชี่ยวชาญในภาษา C ++ และถามพวกเขาว่าพวกเขาใช้เวลานานแค่ไหนในการเรียนรู้เทมเพลต fudgery ที่พวกเขาต้องใช้ในการทำเทมเพลต metaprogramming หรือลูกเล่นที่บ้าคลั่งในหนังสือ (ยอดเยี่ยม) เช่นModern C ++ Designซึ่งยังยากที่จะทำการ debug และในทางปฏิบัติไม่สามารถพกพาได้ระหว่างคอมไพเลอร์ในโลกแห่งความจริงแม้ว่าภาษานั้นจะเป็นมาตรฐานสำหรับทศวรรษ สิ่งเหล่านี้จะละลายไปถ้าหากคุณใช้ภาษาเมตาสำหรับโปรแกรมเมตาโพแกรมคือภาษาเดียวกับที่ใช้ในการเขียนโปรแกรม!


11
เพื่อความเป็นธรรมปัญหาของ C ++ template metaprogramming ไม่ใช่ว่าภาษา metaprogramming นั้นแตกต่างกันแต่มันก็น่ากลัว - มันไม่ได้ออกแบบมามากนักเมื่อค้นพบในสิ่งที่ตั้งใจจะใช้กับฟังก์ชั่นเทมเพลตที่เรียบง่ายกว่า
Brooks Moses

@ บรูคชัวร์ คุณสมบัติที่เกิดขึ้นไม่ได้เลวร้ายเสมอไป น่าเสียดายในภาษาที่ขับเคลื่อนโดยคณะกรรมการที่เคลื่อนไหวช้ามันยากที่จะแก้ไขเมื่อมี มันเป็นความอัปยศของคุณลักษณะใหม่ที่มีประโยชน์ทันสมัยของ C ++ ที่เขียนในภาษาที่น้อยมากที่สามารถอ่านได้และมีช่องว่างขนาดใหญ่ระหว่างโปรแกรมเมอร์ทั่วไปกับ "มหาปุโรหิต"
Matt Curtis

2
@downvoter: หากมีสิ่งผิดปกติกับคำตอบของฉันโปรดแสดงความคิดเห็นเพื่อให้เราทุกคนสามารถแบ่งปันความรู้
Matt Curtis

10

แมโคร lisp ใช้แฟรกเมนต์ของโปรแกรมเป็นอินพุต แฟรกเมนต์ของโปรแกรมนี้แสดงโครงสร้างข้อมูลที่สามารถจัดการและแปลงได้ตามที่คุณต้องการ ในท้ายที่สุดแมโครจะแสดงส่วนย่อยของโปรแกรมอื่นและส่วนนี้คือสิ่งที่เรียกใช้งาน ณ รันไทม์

C # ไม่มีสิ่งอำนวยความสะดวกของแมโครอย่างไรก็ตามสิ่งที่เทียบเท่าจะเป็นถ้าคอมไพเลอร์แยกวิเคราะห์โค้ดเป็นต้นไม้ CodeDOM และส่งผ่านไปยังวิธีการซึ่งเปลี่ยนสิ่งนี้เป็น CodeDOM อื่นซึ่งจะถูกรวบรวมเป็น IL

นี้สามารถนำมาใช้ในการดำเนินการ "น้ำตาล" ไวยากรณ์เช่นfor each-statement using-clause, LINQ select-expressions และอื่น ๆ เป็นแมโครที่เปลี่ยนเป็นรหัสอ้างอิง

หาก Java มีมาโครคุณสามารถใช้ไวยากรณ์ของ Linq ใน Java ได้โดยไม่ต้องใช้ Sun เพื่อเปลี่ยนภาษาพื้นฐาน

นี่คือโค้ดหลอกสำหรับวิธีที่แมโครสไตล์เสียงกระเพื่อมใน C # สำหรับการใช้งานusingสามารถดูได้:

define macro "using":
    using ($type $varname = $expression) $block
into:
    $type $varname;
    try {
       $varname = $expression;
       $block;
    } finally {
       $varname.Dispose();
    }

ตอนนี้ที่มีจริงคือชัดสไตล์ประมวลผลแมโครสำหรับ C # ผมจะชี้ให้เห็นว่าแมโครสำหรับusingจะมีลักษณะเช่นนี้ ;)
Qwertie

9

ตั้งแต่คำตอบที่มีอยู่ให้ตัวอย่างที่เป็นรูปธรรมที่ดีอธิบายสิ่งที่แมโครบรรลุและวิธีการที่บางทีมันอาจจะช่วยในการเก็บรวบรวมกันบางส่วนของความคิดเกี่ยวกับเหตุผลที่สิ่งอำนวยความสะดวกแมโครเป็นกำไรอย่างมีนัยสำคัญในความสัมพันธ์กับภาษาอื่น ๆ ; แรกจากคำตอบเหล่านี้แล้วอันที่ดีจากที่อื่น:

... ใน C คุณจะต้องเขียนตัวประมวลผลล่วงหน้าที่กำหนดเอง [ซึ่งอาจถือว่าเป็น โปรแกรม C ที่ซับซ้อนพอ ] ...

- Vatine

พูดคุยกับทุกคนที่มีความเชี่ยวชาญในภาษา C ++ และถามพวกเขาว่าพวกเขาใช้เวลานานแค่ไหนในการเรียนรู้เทมเพลตทั้งหมดที่ต้องใช้ในการทำเทมเพลต metaprogramming [ซึ่งยังไม่มีประสิทธิภาพ]

- แมตต์เคอร์ติส

... ใน Java คุณต้องแฮ็คทางของคุณด้วยการทอผ้าด้วย bytecode แม้ว่าเฟรมเวิร์กบางอย่างเช่น AspectJ ช่วยให้คุณทำสิ่งนี้ได้โดยใช้วิธีการที่แตกต่างกัน

- มิเกลปิง

DOLIST นั้นคล้ายกับ foreach ของ Perl หรือ Python สำหรับ Java ได้เพิ่มโครงสร้างลูปที่คล้ายกันกับ "Enhanced" สำหรับ Loop ใน Java 1.5 ซึ่งเป็นส่วนหนึ่งของ JSR-201 สังเกตุสิ่งที่มาโครสร้างความแตกต่าง โปรแกรมเมอร์ Lisp ที่สังเกตเห็นรูปแบบทั่วไปในรหัสของพวกเขาสามารถเขียนแมโครเพื่อให้นามธรรมระดับแหล่งที่มาของรูปแบบนั้น โปรแกรมเมอร์ภาษาจาวาที่สังเกตเห็นรูปแบบเดียวกันนั้นต้องโน้มน้าวให้ซุนว่าสิ่งที่เป็นนามธรรมนี้มีค่าเพิ่มในภาษา จากนั้นซันจะต้องเผยแพร่ JSR และเรียกประชุม "กลุ่มผู้เชี่ยวชาญ" ทั่วทั้งอุตสาหกรรมเพื่อแฮ็กข้อมูลทุกอย่างออกไป กระบวนการดังกล่าว - ตามดวงอาทิตย์ - ใช้เวลาเฉลี่ย 18 เดือน หลังจากนั้นผู้เขียนคอมไพเลอร์ทุกคนต้องไปอัปเกรดคอมไพเลอร์เพื่อสนับสนุนคุณสมบัติใหม่ และแม้กระทั่งเมื่อคอมไพเลอร์คนโปรดของโปรแกรมเมอร์ Java รองรับ Java เวอร์ชันใหม่ พวกเขาอาจ '' ยัง '' ไม่สามารถใช้คุณสมบัติใหม่จนกว่าพวกเขาจะได้รับอนุญาตให้แบ่งความเข้ากันได้ของแหล่งที่มากับ Java รุ่นเก่ากว่า ดังนั้นความรำคาญที่โปรแกรมเมอร์ Common LISP สามารถแก้ไขได้ด้วยตนเองภายในห้านาทีทำให้เกิดภัยพิบัติโปรแกรมเมอร์ Java เป็นเวลาหลายปี

- Peter Seibel ใน "Common Common LISP"


8

ฉันไม่แน่ใจว่าฉันสามารถเพิ่มข้อมูลเชิงลึกให้กับโพสต์ (ยอดเยี่ยม) ของทุกคนได้ แต่ ...

แมโคร Lisp ใช้งานได้ดีเนื่องจากลักษณะไวยากรณ์ของ Lisp

เสียงกระเพื่อมเป็นภาษาที่ปกติมาก (คิดว่าทุกอย่างเป็นรายการ ); แมโครช่วยให้คุณสามารถจัดการข้อมูลและรหัสได้เหมือนกัน (ไม่จำเป็นต้องแยกสตริงหรือแฮ็กอื่น ๆ เพื่อแก้ไขนิพจน์เสียงกระเพื่อม) คุณรวมคุณสมบัติทั้งสองนี้เข้าด้วยกันและคุณมีวิธีการแก้ไขโค้ดที่สะอาดมาก

แก้ไข:สิ่งที่ฉันพยายามจะพูดคือ Lisp เป็นhomoiconicซึ่งหมายความว่าโครงสร้างข้อมูลสำหรับโปรแกรม lisp นั้นเขียนด้วย lisp เอง

ดังนั้นคุณจะจบลงด้วยวิธีการสร้างตัวสร้างโค้ดของคุณเองโดยใช้ภาษาที่มีพลังทั้งหมด (เช่นใน Java คุณต้องแฮ็ควิธีของคุณด้วยการทอผ้า bytecode แม้ว่ากรอบบางอย่างเช่น AspectJ ช่วยให้คุณ ทำสิ่งนี้โดยใช้วิธีอื่นมันเป็นพื้นฐานของการแฮ็ค)

ในทางปฏิบัติด้วยมาโครคุณจะสร้างมินิภาษาของคุณเองบนเสียงกระเพื่อมโดยไม่จำเป็นต้องเรียนรู้ภาษาหรือเครื่องมือเพิ่มเติมและด้วยการใช้พลังของภาษาอย่างเต็มที่


1
มันเป็นความคิดเห็นที่ลึกซึ้ง แต่ความคิดนี้ว่า "ทุกอย่างเป็นรายการ" อาจทำให้ผู้มาใหม่หวาดกลัว เพื่อทำความเข้าใจกับรายการคุณต้องเข้าใจข้อเสียรถยนต์ cdrs เซลล์ แม่นยำยิ่งขึ้นเสียงกระเพื่อมที่ทำจากS-expressionsไม่ใช่รายการ
ribamar

6

Lisp macros แสดงถึงรูปแบบที่เกิดขึ้นในเกือบทุกโครงการการเขียนโปรแกรมขนาดใหญ่ ในที่สุดในโปรแกรมขนาดใหญ่คุณมีบางส่วนของรหัสที่คุณรู้ว่ามันจะง่ายขึ้นและมีข้อผิดพลาดน้อยลงสำหรับคุณที่จะเขียนโปรแกรมที่ส่งออกซอร์สโค้ดเป็นข้อความที่คุณสามารถวางได้

ในหลามวัตถุมีสองวิธีการและ __repr__ เป็นเพียงตัวแทนที่มนุษย์อ่านได้ ส่งคืนการแทนค่าที่เป็นรหัส Python ที่ถูกต้องซึ่งก็คือสิ่งที่สามารถป้อนลงในล่ามเป็น Python ที่ถูกต้อง วิธีนี้คุณสามารถสร้างตัวอย่างของ Python ขนาดเล็กที่สร้างรหัสที่ถูกต้องที่สามารถวางลงในแหล่งที่มาจริงของคุณ__str____str____repr__

ในเสียงกระเพื่อมกระบวนการทั้งหมดนี้ได้รับการกรงเล็บโดยระบบมาโคร แน่นอนว่ามันช่วยให้คุณสามารถสร้างส่วนขยายให้กับไวยากรณ์และทำสิ่งต่าง ๆ ที่เป็นแฟนซี แต่มันมีประโยชน์จริงโดยสรุปข้างต้น แน่นอนว่ามันช่วยให้ระบบมาโคร Lisp ให้คุณสามารถจัดการ "ตัวอย่าง" เหล่านี้ด้วยพลังเต็มรูปแบบของภาษาทั้งหมด


1
ย่อหน้าแรกของคุณชัดเจนสำหรับคนนอก Lisp ซึ่งสำคัญมาก
Wildcard

5

กล่าวโดยย่อแมโครคือการแปลงรหัส พวกเขาอนุญาตให้แนะนำการสร้างไวยากรณ์ใหม่จำนวนมาก เช่นพิจารณา LINQ ใน C # ในเสียงกระเพื่อมมีส่วนขยายภาษาที่คล้ายกันที่นำมาใช้โดยแมโคร (เช่นการสร้างวงวนซ้ำในตัว, ทำซ้ำ) แมโครลดการทำสำเนารหัสลงอย่างมาก มาโครอนุญาตให้ฝัง«ภาษาเล็ก ๆ น้อย ๆ » (เช่นใน c # / java หนึ่งจะใช้ xml เพื่อกำหนดค่าใน lisp สิ่งเดียวกันสามารถทำได้ด้วยมาโคร) มาโครอาจซ่อนปัญหาในการใช้งานไลบรารี

เช่นใน lisp คุณสามารถเขียน

(iter (for (id name) in-clsql-query "select id, name from users" on-database *users-database*)
      (format t "User with ID of ~A has name ~A.~%" id name))

และสิ่งนี้จะซ่อนเนื้อหาของฐานข้อมูลทั้งหมด (ธุรกรรมการปิดการเชื่อมต่อที่เหมาะสมการดึงข้อมูล ฯลฯ ) ในขณะที่ C # ต้องสร้าง SqlConnections, SqlCommands, การเพิ่ม SqlParameters ไปยัง SqlCommands, วนซ้ำบน


3

ในขณะที่ข้างต้นทั้งหมดอธิบายว่าแมโครคืออะไรและมีตัวอย่างที่ยอดเยี่ยม แต่ฉันคิดว่าความแตกต่างที่สำคัญระหว่างแมโครและฟังก์ชั่นปกติคือ LISP จะประเมินพารามิเตอร์ทั้งหมดก่อนที่จะเรียกใช้ฟังก์ชัน ด้วยแมโครมันเป็นสิ่งที่ตรงกันข้าม LISP ผ่านพารามิเตอร์ที่ไม่ได้ประเมินค่าไปยังแมโคร ตัวอย่างเช่นหากคุณผ่าน (+ 1 2) ไปยังฟังก์ชันฟังก์ชันจะได้รับค่า 3 หากคุณส่งสิ่งนี้ไปยังแมโครแมโครนั้นจะได้รับ List (+ 1 2) สามารถใช้ทำสิ่งที่มีประโยชน์อย่างเหลือเชื่อทุกชนิด

  • การเพิ่มโครงสร้างการควบคุมใหม่เช่น loop หรือ deconstruction ของรายการ
  • วัดเวลาที่ใช้ในการเรียกใช้ฟังก์ชั่นที่ส่งผ่านเข้ามาด้วยฟังก์ชั่นพารามิเตอร์จะถูกประเมินก่อนที่จะส่งผ่านการควบคุมไปยังฟังก์ชัน ด้วยแมโครคุณสามารถต่อรหัสระหว่างจุดเริ่มต้นและจุดหยุดของนาฬิกาจับเวลาได้ ด้านล่างมีรหัสเดียวกันที่แน่นอนในแมโครและฟังก์ชั่นและการส่งออกแตกต่างกันมาก หมายเหตุ: นี่เป็นตัวอย่างที่วางแผนไว้และมีการเลือกการนำไปใช้เพื่อให้เหมือนกันเพื่อเน้นความแตกต่างให้ดียิ่งขึ้น

    (defmacro working-timer (b) 
      (let (
            (start (get-universal-time))
            (result (eval b))) ;; not splicing here to keep stuff simple
        ((- (get-universal-time) start))))
    
    (defun my-broken-timer (b)
      (let (
            (start (get-universal-time))
            (result (eval b)))    ;; doesn't even need eval
        ((- (get-universal-time) start))))
    
    (working-timer (sleep 10)) => 10
    
    (broken-timer (sleep 10)) => 0

Btw, Scala ได้เพิ่มมาโครลงในภาษา ในขณะที่พวกเขาขาดความสวยงามของมาโคร Lisp เพราะภาษาไม่ใช่ homoiconic พวกเขาควรจะดูมันอย่างแน่นอนและต้นไม้ไวยากรณ์ที่พวกเขาให้บริการอาจจะใช้งานได้ง่ายกว่าในท้ายที่สุด มันเร็วเกินไปที่ฉันจะบอกว่าฉันต้องการระบบมาโครแบบใด
Joerg Schmuecker

2
"LISP ผ่านพารามิเตอร์ที่ไม่ได้ประเมินค่าไปยังมาโคร"ในที่สุดก็มีคำตอบที่บอกว่ามันชัดแจ้ง แต่คุณลืมครึ่งหลังของสิ่งนั้น: และผลลัพธ์ของแมโครคือโค้ดที่แปลงแล้วซึ่งจะถูกประเมินโดยระบบทั้งหมดแทนที่ต้นฉบับเหมือนกับว่ามันอยู่ในตำแหน่งแรก (เว้นแต่จะเรียกตัวเองอีกครั้งว่า แมโครซึ่งยังจะได้รับการเปลี่ยนโดยที่แมโครในครั้งนี้)
Will Ness

0

ฉันได้รับสิ่งนี้จากตำราอาหาร lisp ทั่วไป แต่ฉันคิดว่ามันอธิบายได้ว่าทำไมมาโคร lisp จึงเป็นวิธีที่ดี

"แมโครเป็นรหัส Lisp ทั่วไปที่ทำงานบนรหัส Lisp สมมุติอีกชิ้นแปลเป็น Lisp ที่ปฏิบัติการได้ (ใกล้กับรุ่น) ซึ่งอาจฟังดูซับซ้อนเล็กน้อยดังนั้นขอยกตัวอย่างง่ายๆสมมติว่าคุณต้องการ รุ่น setq ที่ตั้งค่าตัวแปรสองตัวเป็นค่าเดียวกันดังนั้นหากคุณเขียน

(setq2 x y (+ z 3))

เมื่อz=8ทั้ง x และ y ถูกตั้งค่าเป็น 11 (ฉันไม่สามารถคิดถึงการใช้งานนี้ แต่เป็นเพียงตัวอย่าง)

มันควรจะชัดเจนว่าเราไม่สามารถกำหนด setq2 เป็นฟังก์ชั่น ถ้าx=50และy=-5, ฟังก์ชันนี้จะได้รับค่า 50, -5, และ 11; มันจะไม่มีความรู้ว่าควรจะตั้งค่าตัวแปรใด สิ่งที่เราอยากจะพูดก็คือเมื่อคุณ (ระบบเสียงกระเพื่อม) ดู(setq2 v1 v2 e), (progn (setq v1 e) (setq v2 e))รักษามันเป็นเทียบเท่ากับ อันที่จริงมันไม่ถูกต้องนัก แต่มันจะทำเพื่อตอนนี้ แมโครช่วยให้เราสามารถทำสิ่งนี้ได้อย่างแม่นยำโดยการระบุโปรแกรมสำหรับการแปลงรูปแบบการป้อนข้อมูล(setq2 v1 v2 e)"เป็นรูปแบบการส่งออก(progn ...)"

หากคุณคิดว่าสิ่งนี้ดีคุณสามารถอ่านต่อได้ที่นี่: http://cl-cookbook.sourceforge.net/macros.html


1
เป็นไปได้ที่จะกำหนดsetq2เป็นฟังก์ชันถ้าxและyถูกส่งผ่านโดยการอ้างอิง อย่างไรก็ตามฉันไม่รู้ว่ามันเป็นไปได้ใน CL หรือไม่ ดังนั้นสำหรับคนที่ไม่รู้จัก Lisps หรือ CL โดยเฉพาะนี่ไม่ใช่ตัวอย่างที่ชัดเจนมาก IMO
neoascetic

@neoascetic การส่งผ่านอาร์กิวเมนต์ CL เป็นค่าเท่านั้น (นั่นเป็นเหตุผลว่าทำไมจึงต้องมีแมโครในตอนแรก) ค่าบางอย่างเป็นตัวชี้แม้ว่า (เช่นรายการ)
Will Ness

-5

ในไพ ธ อนคุณมีมัณฑนากรโดยทั่วไปคุณมีฟังก์ชั่นที่รับฟังก์ชั่นอื่นเป็นอินพุต คุณสามารถทำสิ่งที่คุณต้องการ: เรียกใช้ฟังก์ชั่นทำอย่างอื่นห่อการเรียกใช้ฟังก์ชั่นในทรัพยากรที่ได้รับการปล่อย ฯลฯ แต่คุณไม่ต้องมองเข้าไปในฟังก์ชั่นนั้น สมมติว่าเราต้องการทำให้มันมีประสิทธิภาพยิ่งขึ้นมัณฑนากรของคุณได้รับโค้ดของฟังก์ชั่นเป็นรายการจากนั้นคุณไม่เพียง แต่สามารถเรียกใช้ฟังก์ชั่นได้ตามที่เป็นอยู่ แต่คุณสามารถรันบางส่วนของมันได้

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.