เสียงกระเพื่อมจำนวนมากจะบอกคุณว่าสิ่งที่ทำให้เสียงกระเพื่อมพิเศษคือ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)
ที่นี่หมายถึงการเปลี่ยนชื่อ$lambda
lambda
ตรงไปตรงมาใช่มั้ย ;-) (ถ้ามันไม่ตรงไปตรงมากับคุณแค่คิดว่ามันยากที่จะใช้ระบบมาโครสำหรับภาษาอื่น ๆ )
ดังนั้นคุณสามารถมีระบบมาโครสำหรับภาษาอื่น ๆ ได้หากคุณมีวิธีที่จะสามารถ "แยกส่วน" ซอร์สโค้ดในลักษณะที่ไม่ใช่แบบ 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
บิตหลังรูปแบบคือรหัสจริงที่ในที่สุดจะต้องส่งคืนวัตถุไวยากรณ์ ( #'(...)
) แต่สามารถมีรหัสอื่น ๆ ได้เช่นกัน