เสียงกระเพื่อมจำนวนมากจะบอกคุณว่าสิ่งที่ทำให้เสียงกระเพื่อมพิเศษคือhomoiconicityซึ่งหมายความว่าไวยากรณ์ของรหัสจะแสดงโดยใช้โครงสร้างข้อมูลเดียวกันกับข้อมูลอื่น ๆ ตัวอย่างเช่นต่อไปนี้เป็นฟังก์ชันอย่างง่าย (ใช้ Scheme syntax) สำหรับการคำนวณด้านตรงข้ามมุมฉากของสามเหลี่ยมมุมฉากที่มีความยาวด้านที่กำหนด:
(define (hypot x y)
(sqrt (+ (square x) (square y))))
ตอนนี้ homoiconicity บอกว่ารหัสข้างต้นเป็นตัวแทนของโครงสร้างข้อมูล (โดยเฉพาะรายการของรายการ) ในรหัสเสียงกระเพื่อม ดังนั้นให้พิจารณารายการต่อไปนี้และดูว่าพวกเขา "กาว" ด้วยกัน:
(define #2# #3#)
(hypot x y)
(sqrt #4#)
(+ #5# #6#)
(square x)
(square y)
มาโครอนุญาตให้คุณจัดการกับซอร์สโค้ดได้เช่น: รายการของสิ่งต่างๆ แต่ละคน 6 "รายการย่อย" มีคำแนะนำอย่างใดอย่างหนึ่งไปยังรายการอื่น ๆ หรือสัญลักษณ์ (ในตัวอย่างนี้: define, hypot, x, y, sqrt, +, square)
ดังนั้นเราจะใช้ homoiconicity เพื่อ "แยก" ไวยากรณ์และทำแมโครได้อย่างไร นี่คือตัวอย่างง่ายๆ Let 's reimplement แมโครซึ่งเราจะเรียกlet my-letเหมือนเป็นการเตือนความจำ,
(my-let ((foo 1)
(bar 2))
(+ foo bar))
ควรขยายเข้าไป
((lambda (foo bar)
(+ foo bar))
1 2)
นี่คือการใช้งานโดยใช้มาโคร "การเปลี่ยนชื่อชัดเจน" Scheme Sch :
(define-syntax my-let
(er-macro-transformer
(lambda (form rename compare)
(define bindings (cadr form))
(define body (cddr form))
`((,(rename 'lambda) ,(map car bindings)
,@body)
,@(map cadr bindings)))))
พารามิเตอร์ถูกผูกไว้กับรูปแบบที่เกิดขึ้นจริงดังนั้นสำหรับตัวอย่างของเราก็จะเป็นform (my-let ((foo 1) (bar 2)) (+ foo bar))ดังนั้นเรามาดูตัวอย่าง:
- ก่อนอื่นเราดึงการเชื่อมจากแบบฟอร์ม
cadrคว้า((foo 1) (bar 2))ส่วนของแบบฟอร์ม
จากนั้นเราดึงร่างกายจากแบบฟอร์ม cddrคว้า((+ foo bar))ส่วนของแบบฟอร์ม (โปรดทราบว่าสิ่งนี้มีจุดประสงค์เพื่อดึงฟอร์มย่อยทั้งหมดหลังจากการรวมดังนั้นถ้าเป็นแบบฟอร์ม
(my-let ((foo 1)
(bar 2))
(debug foo)
(debug bar)
(+ foo bar))
จากนั้นร่างกายก็จะเป็น((debug foo) (debug bar) (+ foo bar)).)
- ตอนนี้เราสร้าง
lambdaนิพจน์ผลลัพธ์และการโทรโดยใช้การโยงและเนื้อหาที่เรารวบรวม backtick เรียกว่า "quasiquote" ซึ่งหมายถึงการรักษาทุกอย่างภายใน quasiquote เป็น datums ที่แท้จริงยกเว้นบิตหลังเครื่องหมายจุลภาค ("unquote")
(rename 'lambda)หมายถึงการใช้lambdaที่มีผลผูกพันในการบังคับใช้เมื่อมาโครนี้กำหนดไว้มากกว่าสิ่งที่lambdaมีผลผูกพันอาจจะมีรอบเมื่อมาโครนี้ใช้ (สิ่งนี้เรียกว่าสุขอนามัย )
(map car bindings)คืนค่า(foo bar): ตัวเลขแรกในแต่ละการรวม
(map cadr bindings)คืนค่า(1 2): ตัวเลขสองในแต่ละการรวม
,@ คือ "splicing" ซึ่งใช้สำหรับนิพจน์ที่ส่งคืนรายการ: ทำให้องค์ประกอบของรายการถูกวางลงในผลลัพธ์แทนที่จะเป็นรายการ
- วางทุกสิ่งที่ร่วมกันเราได้รับเป็นผลให้รายการ
(($lambda (foo bar) (+ foo bar)) 1 2)ที่นี่หมายถึงการเปลี่ยนชื่อ$lambdalambda
ตรงไปตรงมาใช่มั้ย ;-) (ถ้ามันไม่ตรงไปตรงมากับคุณแค่คิดว่ามันยากที่จะใช้ระบบมาโครสำหรับภาษาอื่น ๆ )
ดังนั้นคุณสามารถมีระบบมาโครสำหรับภาษาอื่น ๆ ได้หากคุณมีวิธีที่จะสามารถ "แยกส่วน" ซอร์สโค้ดในลักษณะที่ไม่ใช่แบบ clunky มีความพยายามบางอย่างในเรื่องนี้ ตัวอย่างเช่นsweet.jsใช้สำหรับ JavaScript
†สำหรับ Schemers ที่มีประสบการณ์การอ่านนี้ฉันตั้งใจเลือกที่จะใช้มาโครการเปลี่ยนชื่ออย่างชัดเจนว่าเป็นการประนีประนอมระหว่างผู้defmacroใช้ภาษา Lisp อื่น ๆ และsyntax-rules(ซึ่งจะเป็นวิธีมาตรฐานในการใช้แมโครดังกล่าวใน Scheme) ฉันไม่ต้องการที่จะเขียนในภาษาเสียงกระเพื่อมอื่น ๆ แต่ฉันไม่ต้องการที่จะโอนไม่ใช่ Schemers syntax-rulesที่ไม่ได้ใช้ในการ
สำหรับการอ้างอิงนี่คือmy-letมาโครที่ใช้syntax-rules:
(define-syntax my-let
(syntax-rules ()
((my-let ((id val) ...)
body ...)
((lambda (id ...)
body ...)
val ...))))
syntax-caseรุ่นที่เกี่ยวข้องมีลักษณะคล้ายกันมาก:
(define-syntax my-let
(lambda (stx)
(syntax-case stx ()
((_ ((id val) ...)
body ...)
#'((lambda (id ...)
body ...)
val ...)))))
ความแตกต่างระหว่างทั้งสองก็คือว่าทุกอย่างในการsyntax-rulesมีนัย#'นำมาใช้เพื่อให้คุณสามารถเพียงมีคู่รูปแบบ / แม่แบบในsyntax-rulesจึงเป็นที่เปิดเผยอย่างเต็มที่ ในทางตรงกันข้ามsyntax-caseบิตหลังรูปแบบคือรหัสจริงที่ในที่สุดจะต้องส่งคืนวัตถุไวยากรณ์ ( #'(...)) แต่สามารถมีรหัสอื่น ๆ ได้เช่นกัน