ถ้ามี LISP จะทำให้การใช้ระบบมาโครง่ายขึ้นได้อย่างไร


21

ฉันเรียนรู้ Scheme จากSICPและฉันได้รับความประทับใจว่าส่วนใหญ่ของสิ่งที่ทำให้ Scheme และยิ่งกว่านั้น LISP พิเศษคือระบบมาโคร แต่เนื่องจากมาโครถูกขยายในเวลาคอมไพล์ทำไมคนไม่สร้างระบบมาโครที่เทียบเท่าสำหรับ C / Python / Java / อะไร ตัวอย่างเช่นเราสามารถผูกpythonคำสั่งexpand-macros | pythonหรืออะไรก็ได้ รหัสจะยังสามารถพกพาได้สำหรับผู้ที่ไม่ได้ใช้ระบบมาโครใครจะขยายมาโครก่อนที่จะเผยแพร่รหัส แต่ฉันไม่รู้อะไรเลยนอกจากเทมเพลตใน C ++ / Haskell ซึ่งฉันรวบรวมไม่เหมือนกันจริงๆ ถ้ามี LISP จะทำให้การใช้ระบบมาโครง่ายขึ้นได้อย่างไร


3
"รหัสจะยังคงพกพาได้สำหรับผู้ที่ไม่ได้ใช้ระบบมาโครใครจะขยายมาโครก่อนที่จะเผยแพร่รหัส" - เพียงเพื่อเตือนคุณสิ่งนี้มีแนวโน้มที่จะไม่ทำงานได้ดี คนอื่น ๆ เหล่านั้นจะสามารถเรียกใช้โค้ดได้ แต่ในทางปฏิบัติโค้ดที่ขยายมาโครมักยากที่จะเข้าใจและมักจะยากที่จะแก้ไข มันมีผล "เขียนไม่ดี" ในแง่ที่ว่าผู้เขียนไม่ได้ปรับแต่งรหัสที่ขยายออกสำหรับดวงตามนุษย์พวกเขาปรับแหล่งที่มาที่แท้จริง ลองบอกโปรแกรม Java คุณเรียกใช้รหัส Java ของคุณผ่าน preprocessor C และนาฬิกาสีอะไรที่พวกเขาเปิด ;-)
สตีฟเจสซอพ

1
มาโครจำเป็นต้องเรียกใช้งาน ณ จุดนั้นคุณกำลังเขียนล่ามภาษาอยู่แล้ว
Mehrdad

คำตอบ:


29

เสียงกระเพื่อมจำนวนมากจะบอกคุณว่าสิ่งที่ทำให้เสียงกระเพื่อมพิเศษคือhomoiconicityซึ่งหมายความว่าไวยากรณ์ของรหัสจะแสดงโดยใช้โครงสร้างข้อมูลเดียวกันกับข้อมูลอื่น ๆ ตัวอย่างเช่นต่อไปนี้เป็นฟังก์ชันอย่างง่าย (ใช้ Scheme syntax) สำหรับการคำนวณด้านตรงข้ามมุมฉากของสามเหลี่ยมมุมฉากที่มีความยาวด้านที่กำหนด:

(define (hypot x y)
  (sqrt (+ (square x) (square y))))

ตอนนี้ homoiconicity บอกว่ารหัสข้างต้นเป็นตัวแทนของโครงสร้างข้อมูล (โดยเฉพาะรายการของรายการ) ในรหัสเสียงกระเพื่อม ดังนั้นให้พิจารณารายการต่อไปนี้และดูว่าพวกเขา "กาว" ด้วยกัน:

  1. (define #2# #3#)
  2. (hypot x y)
  3. (sqrt #4#)
  4. (+ #5# #6#)
  5. (square x)
  6. (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))ดังนั้นเรามาดูตัวอย่าง:

  1. ก่อนอื่นเราดึงการเชื่อมจากแบบฟอร์ม cadrคว้า((foo 1) (bar 2))ส่วนของแบบฟอร์ม
  2. จากนั้นเราดึงร่างกายจากแบบฟอร์ม cddrคว้า((+ foo bar))ส่วนของแบบฟอร์ม (โปรดทราบว่าสิ่งนี้มีจุดประสงค์เพื่อดึงฟอร์มย่อยทั้งหมดหลังจากการรวมดังนั้นถ้าเป็นแบบฟอร์ม

    (my-let ((foo 1)
             (bar 2))
      (debug foo)
      (debug bar)
      (+ foo bar))
    

    จากนั้นร่างกายก็จะเป็น((debug foo) (debug bar) (+ foo bar)).)

  3. ตอนนี้เราสร้างlambdaนิพจน์ผลลัพธ์และการโทรโดยใช้การโยงและเนื้อหาที่เรารวบรวม backtick เรียกว่า "quasiquote" ซึ่งหมายถึงการรักษาทุกอย่างภายใน quasiquote เป็น datums ที่แท้จริงยกเว้นบิตหลังเครื่องหมายจุลภาค ("unquote")
    • (rename 'lambda)หมายถึงการใช้lambdaที่มีผลผูกพันในการบังคับใช้เมื่อมาโครนี้กำหนดไว้มากกว่าสิ่งที่lambdaมีผลผูกพันอาจจะมีรอบเมื่อมาโครนี้ใช้ (สิ่งนี้เรียกว่าสุขอนามัย )
    • (map car bindings)คืนค่า(foo bar): ตัวเลขแรกในแต่ละการรวม
    • (map cadr bindings)คืนค่า(1 2): ตัวเลขสองในแต่ละการรวม
    • ,@ คือ "splicing" ซึ่งใช้สำหรับนิพจน์ที่ส่งคืนรายการ: ทำให้องค์ประกอบของรายการถูกวางลงในผลลัพธ์แทนที่จะเป็นรายการ
  4. วางทุกสิ่งที่ร่วมกันเราได้รับเป็นผลให้รายการ(($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บิตหลังรูปแบบคือรหัสจริงที่ในที่สุดจะต้องส่งคืนวัตถุไวยากรณ์ ( #'(...)) แต่สามารถมีรหัสอื่น ๆ ได้เช่นกัน


2
ข้อได้เปรียบที่คุณไม่ได้กล่าวถึง: ใช่มีความพยายามในภาษาอื่นเช่น sweet.js สำหรับ JS อย่างไรก็ตามใน lisps การเขียนแมโครทำได้ในภาษาเดียวกับการเขียนฟังก์ชัน
Margian Florian

ขวาคุณสามารถเขียนแมโคร (เปรียบเทียบกับที่ประกาศ) ในภาษา Lisp ซึ่งเป็นสิ่งที่ช่วยให้คุณสามารถทำสิ่งที่ทันสมัยจริงๆ BTW นี่คือสิ่งที่ฉันชอบเกี่ยวกับระบบมาโคร Scheme: มีหลายแบบให้เลือก สำหรับมาโครแบบง่ายฉันใช้syntax-rulesซึ่งเป็นการประกาศอย่างหมดจด สำหรับมาโครที่ซับซ้อนฉันสามารถใช้syntax-caseซึ่งเป็นส่วนหนึ่งที่เปิดเผยและขั้นตอนบางส่วน แล้วมีการเปลี่ยนชื่ออย่างชัดเจนซึ่งเป็นขั้นตอนอย่างหมดจด (การดำเนินการตามโครงการส่วนใหญ่จะให้อย่างใดอย่างหนึ่งsyntax-caseหรือ ER ฉันไม่เคยเห็นคนที่ให้ทั้งสองพวกเขามีอำนาจเทียบเท่า)
Chris Jester-Young

ทำไมมาโครต้องแก้ไข AST ทำไมพวกเขาถึงทำงานในระดับที่สูงขึ้นไม่ได้?
Elliot Gorokhovsky

1
แล้วเหตุใด LISP จึงดีกว่า อะไรทำให้ LISP พิเศษ? หากใครสามารถใช้มาโครใน js ได้ก็สามารถนำมาใช้ในภาษาอื่นได้เช่นกัน
Elliot Gorokhovsky

3
@ RenéGตามที่ฉันพูดในความคิดเห็นแรกของฉันข้อได้เปรียบที่สำคัญคือคุณยังคงเขียนในภาษาเดียวกัน
Florian Margaine

23

ความเห็นที่ไม่เห็นด้วย: การกระเพื่อมของ Lisp นั้นมีประโยชน์น้อยกว่าแฟน ๆ Lisp ส่วนใหญ่ที่คุณเชื่อ

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

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

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

สิ่งที่ต้องจำเกี่ยวกับ Lisp ก็คือมันเก่ามาก. ในบรรดาภาษาการเขียนโปรแกรมมีเพียง FORTRAN ที่เก่ากว่า Lisp ย้อนกลับไปในวันการแยกวิเคราะห์ (ส่วนที่ช้าของการรวบรวม) ถือเป็นศิลปะที่มืดและลึกลับ เอกสารต้นฉบับของ John McCarthy เกี่ยวกับทฤษฎี Lisp (ย้อนกลับไปเมื่อมันเป็นเพียงความคิดที่ว่าเขาไม่เคยคิดว่าสามารถนำไปใช้เป็นภาษาโปรแกรมคอมพิวเตอร์จริงได้) อธิบายไวยากรณ์ที่ซับซ้อนและแสดงออกได้ค่อนข้างซับซ้อนกว่า S-expressions ทุกหนทุกแห่งสำหรับทุกสิ่ง สัญกรณ์ เรื่องนี้เกิดขึ้นในภายหลังเมื่อผู้คนพยายามนำไปใช้จริง เนื่องจากการแยกวิเคราะห์ไม่เป็นที่เข้าใจกันในตอนนั้นพวกเขาจึงเขวี้ยงมันลงไปและทำให้โครงสร้างของต้นไม้เป็น homoiconic เพื่อทำให้งานของ parser กลายเป็นเรื่องเล็กน้อย ผลลัพธ์ที่ได้คือคุณ (ผู้พัฒนา) ต้องทำโปรแกรมแยกวิเคราะห์จำนวนมาก ' ทำงานโดยการเขียน AST อย่างเป็นทางการลงในรหัสของคุณโดยตรง การทำให้เป็นเนื้อเดียวกันไม่ได้ "ทำให้มาโครง่ายขึ้น" เท่าที่ทำให้การเขียนทุกอย่างอื่นยากขึ้นกว่าเดิมมาก!

ปัญหาเกี่ยวกับเรื่องนี้คือโดยเฉพาะอย่างยิ่งกับการพิมพ์แบบไดนามิกมันเป็นเรื่องยากมากที่ S-expressions จะนำข้อมูลเชิงความหมายจำนวนมากไปด้วย เมื่อไวยากรณ์ทั้งหมดของคุณเป็นประเภทเดียวกัน (รายการของรายการ) มีไม่มากในบริบทของการจัดทำโดยไวยากรณ์และดังนั้นระบบแมโครมีน้อยมากที่จะทำงานกับ

ทฤษฎีคอมไพเลอร์ได้มานานตั้งแต่ทศวรรษ 1960 เมื่อ Lisp ถูกประดิษฐ์ขึ้นและในขณะที่สิ่งที่สำเร็จก็น่าประทับใจสำหรับวันนั้นพวกเขาดูค่อนข้างดั้งเดิม สำหรับตัวอย่างของระบบ metaprogramming ที่ทันสมัยให้ดูที่ภาษา Boo ที่น่าผิดหวัง Boo ถูกพิมพ์แบบคงที่เชิงวัตถุและโอเพนซอร์สดังนั้นทุกโหนด AST มีประเภทที่มีโครงสร้างที่กำหนดไว้อย่างดีที่นักพัฒนาแมโครสามารถอ่านรหัสได้ ภาษามีไวยากรณ์ที่ค่อนข้างง่ายซึ่งได้รับแรงบันดาลใจจาก Python โดยมีคำสำคัญต่าง ๆ ที่ให้ความหมายที่แท้จริงกับโครงสร้างต้นไม้ที่สร้างขึ้นจากพวกเขาและ metaprogramming มีไวยากรณ์ quasiquote ที่ใช้งานง่ายเพื่อทำให้การสร้างโหนด AST ใหม่

นี่คือมาโครที่ฉันสร้างขึ้นเมื่อวานนี้เมื่อฉันรู้ว่าฉันกำลังใช้รูปแบบเดียวกันกับหลาย ๆ ที่ในรหัส GUI ที่ซึ่งฉันจะเรียกBeginUpdate()ใช้ตัวควบคุม UI ทำการอัปเดตในtryบล็อกจากนั้นโทรEndUpdate():

macro UIUpdate(value as Expression):
    return [|
        $value.BeginUpdate()
        try:
            $(UIUpdate.Body)
        ensure:
            $value.EndUpdate()
    |]

จริง ๆ แล้วmacroคำสั่งคือแมโครตัวหนึ่งที่ใช้เนื้อความแมโครเป็นอินพุตและสร้างคลาสเพื่อประมวลผลแมโคร มันใช้ชื่อของแมโครเป็นตัวแปรที่ยืนสำหรับMacroStatementโหนด AST ที่แสดงถึงการร้องขอแมโคร The [| ... |] เป็นบล็อก quasiquote สร้าง AST ที่สอดคล้องกับรหัสภายในและภายในบล็อก quasiquote สัญลักษณ์ $ จะให้ความช่วยเหลือ "unquote" แทนโหนดในโหนดตามที่ระบุ

ด้วยสิ่งนี้มันเป็นไปได้ที่จะเขียน:

UIUpdate myComboBox:
   LoadDataInto(myComboBox)
   myComboBox.SelectedIndex = 0

และขยายให้เป็น:

myComboBox.BeginUpdate()
try:
   LoadDataInto(myComboBox)
   myComboBox.SelectedIndex = 0
ensure:
   myComboBox.EndUpdate()

การแสดงมาโครด้วยวิธีนี้นั้นง่ายกว่าและง่ายกว่าที่เป็นใน Lisp macro เนื่องจากผู้พัฒนารู้โครงสร้างMacroStatementและรู้วิธีการทำงานของคุณสมบัติArgumentsและBodyคุณสมบัติและความรู้โดยธรรมชาตินั้นสามารถใช้เพื่อแสดงแนวคิดที่เกี่ยวข้องในสัญชาตญาณ ทาง นอกจากนี้ยังปลอดภัยกว่าเนื่องจากคอมไพเลอร์รู้ถึงโครงสร้างของMacroStatementและหากคุณลองโค้ดบางอย่างที่ไม่ถูกต้องสำหรับMacroStatementคอมไพเลอร์จะตรวจจับทันทีและรายงานข้อผิดพลาดแทนที่จะไม่ทราบจนกว่าจะมีบางอย่างเกิดขึ้นกับคุณที่ รันไทม์

การต่อกิ่งมาโครลงบน Haskell, Python, Java, Scala และอื่น ๆ ไม่ใช่เรื่องยากเพราะภาษาเหล่านี้ไม่เหมือนกัน มันยากเพราะภาษาไม่ได้ถูกออกแบบมาสำหรับพวกมันและมันจะทำงานได้ดีที่สุดเมื่อลำดับชั้น AST ของภาษาของคุณได้รับการออกแบบตั้งแต่ต้นจนจบเพื่อตรวจสอบและจัดการโดยระบบมาโคร เมื่อคุณทำงานกับภาษาที่ได้รับการออกแบบโดยคำนึงถึงการใช้โปรแกรมตั้งแต่ต้นมาโครนั้นง่ายและสะดวกกว่ามากสำหรับการใช้งาน!


4
ดีใจที่ได้อ่านขอขอบคุณ! มาโครที่ไม่ใช่เสียงกระเพื่อมจะเปลี่ยนไปตามการเปลี่ยนแปลงทางไวยากรณ์หรือไม่? เพราะหนึ่งในจุดแข็งของเสียงกระเพื่อมคือไวยากรณ์เหมือนกันทั้งหมดดังนั้นจึงเป็นการง่ายที่จะเพิ่มฟังก์ชันคำสั่งแบบมีเงื่อนไขสิ่งใดก็ตามเพราะมันเหมือนกันหมด ในขณะที่มีภาษาที่ไม่ใช่เสียงกระเพื่อมสิ่งหนึ่งที่แตกต่างจากที่อื่น - if...ดูเหมือนว่าฟังก์ชั่นการโทรเช่น ฉันไม่รู้จัก Boo แต่คิดว่า Boo ไม่มีการจับคู่รูปแบบคุณสามารถแนะนำด้วยไวยากรณ์ของตัวเองเป็นมาโครได้หรือไม่ ประเด็นของฉันคือ - มาโครใหม่ใน Lisp รู้สึกเป็นธรรมชาติ 100% ในภาษาอื่นที่ใช้งานได้ แต่คุณสามารถเห็นรอยเย็บ
greenoldman

4
เรื่องที่ฉันอ่านมาตลอดมันต่างออกไปเล็กน้อย มีการวางแผนไวยากรณ์ทางเลือกสำหรับ s-expression แต่ทำงานล่าช้าเนื่องจากโปรแกรมเมอร์ได้เริ่มใช้ s-expressions แล้วและพบว่าสะดวก ดังนั้นการทำงานกับไวยากรณ์ใหม่จึงถูกลืมในที่สุด คุณช่วยอ้างอิงแหล่งที่ระบุข้อบกพร่องของทฤษฎีคอมไพเลอร์เป็นเหตุผลในการใช้ s-expressions ได้หรือไม่? นอกจากนี้ครอบครัว Lisp ยังคงพัฒนามานานหลายทศวรรษ (Scheme, Common Lisp, Clojure) และภาษาส่วนใหญ่ตัดสินใจที่จะยึดติดกับการแสดงออก
Giorgio

5
"เรียบง่ายและเข้าใจง่าย": ขอโทษ แต่ฉันไม่เห็นว่าจะทำอย่างไร "กำลังอัปเดต.Aruments [0]" ไม่มีความหมายฉันควรมีอาร์กิวเมนต์ที่มีชื่อและให้คอมไพเลอร์ตรวจสอบตัวเองหากจำนวนข้อโต้แย้งตรงกัน: pastebin.com/YtUf1FpG
coredump

8
"จากมุมมองด้านประสิทธิภาพนี่เป็นงานที่ง่ายกว่ามากผู้เรียบเรียงภาษาระดับสูงหลายคนใช้เวลา 75% หรือมากกว่าในการแยกวิเคราะห์" ฉันคาดว่าจะมองหาและใช้การเพิ่มประสิทธิภาพเพื่อใช้เวลาส่วนใหญ่ (แต่ฉันไม่เคยเขียนคอมไพเลอร์ตัวจริง ) ฉันทำอะไรบางอย่างหายไปหรือเปล่า
Doval

5
น่าเสียดายที่ตัวอย่างของคุณไม่แสดงว่า มันเป็นพื้นฐานที่จะนำมาใช้ใน Lisp กับแมโคร อันที่จริงนี่เป็นหนึ่งในมาโครแบบดั้งเดิมที่สุดที่จะใช้ นี่ทำให้ฉันสงสัยว่าคุณไม่ค่อยรู้เรื่องมาโครใน Lisp มากนัก "ไวยากรณ์ของ Lisp ติดอยู่ในช่วงปี 1960": จริง ๆ แล้วระบบมหภาคใน LISP มีความก้าวหน้าอย่างมากตั้งแต่ปี 1960 (ในปี 1960 Lisp ไม่มีแม้แต่มาโคร!)
Rainer Joswig

3

ฉันเรียนรู้ Scheme จาก SICP และฉันได้รับความประทับใจว่าส่วนใหญ่ของสิ่งที่ทำให้ Scheme และยิ่งกว่านั้น LISP พิเศษคือระบบมาโคร

งั้นเหรอ รหัสทั้งหมดใน SICP เขียนในรูปแบบที่ไม่มีมาโคร ไม่มีมาโครใน SICP เฉพาะในเชิงอรรถในหน้า 373 เท่านั้นที่กล่าวถึงมาโคร

แต่เนื่องจากมีการขยายมาโครในเวลารวบรวม

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

เรามาทดสอบว่าการใช้ SBCL เป็นการใช้งาน Common LISP

มาเปลี่ยน SBCL เป็นล่าม:

* (setf sb-ext:*evaluator-mode* :interpret)

:INTERPRET

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

* (defmacro my-and (a b)
    (print "macro my-and used")
    `(if ,a
         (if ,b t nil)
         nil))

ตอนนี้ขอใช้มาโคร:

MY-AND
* (defun foo (a b) (my-and a b))

FOO

ดู. ในกรณีข้างต้นเสียงกระเพื่อมไม่ทำอะไรเลย แมโครไม่ถูกขยายในเวลาที่กำหนด

* (foo t nil)

"macro my-and used"
NIL

แต่เมื่อรันไทม์เมื่อใช้รหัสแมโครจะถูกขยาย

* (foo t t)

"macro my-and used"
T

อีกครั้งที่รันไทม์เมื่อใช้รหัสแมโครจะถูกขยาย

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

ทำไมมาโครถึงง่ายใน Lisp พวกมันไม่ใช่เรื่องง่าย เฉพาะใน Lisps และมีมากมายที่มีการรองรับแมโครในตัวเนื่องจาก Lisps จำนวนมากมาพร้อมกับเครื่องจักรมากมายสำหรับมาโครดูเหมือนว่าง่าย แต่กลไกมหภาคอาจซับซ้อนอย่างมาก


ฉันได้อ่านเกี่ยวกับโครงการในเว็บเป็นจำนวนมากรวมทั้งอ่าน SICP ด้วย นอกจากนี้ยังมีการแสดงออก Lisp ไม่ได้รวบรวมก่อนที่พวกเขาจะตีความ? อย่างน้อยต้องแยกวิเคราะห์ ดังนั้นฉันคิดว่า "เวลาคอมไพล์" ควรเป็น "การวิเคราะห์เวลา"
Elliot Gorokhovsky

@ จุด Rener ของ Rainer ผมเชื่อว่าถ้าคุณevalหรือloadรหัสในภาษา Lisp ใด ๆ แมโครในนั้นจะได้รับการประมวลผลด้วย ในกรณีที่คุณใช้ระบบ preprocessor ตามที่เสนอในคำถามของคุณevalและสิ่งที่คล้ายกันจะไม่ได้รับประโยชน์จากการขยายตัวของแมโคร
Chris Jester-Young

@ RenéGนอกจากนี้ "การแยกวิเคราะห์" เรียกว่าreadLisp ความแตกต่างนี้มีความสำคัญเนื่องจากevalทำงานในโครงสร้างรายการข้อมูลจริง ๆ (ดังที่ได้กล่าวไว้ในคำตอบของฉัน) ไม่ใช่ในรูปแบบข้อความ ดังนั้นคุณสามารถใช้(eval '(+ 1 1))และรับกลับ 2 แต่ถ้าคุณ(eval "(+ 1 1)")คุณได้รับกลับ"(+ 1 1)"(สตริง) คุณใช้readเพื่อรับจาก"(+ 1 1)"(สตริง 7 ตัวอักษร) ถึง(+ 1 1)(รายการที่มีหนึ่งสัญลักษณ์และสอง fixnums)
Chris Jester-Young

@ RenéGด้วยความเข้าใจว่าแมโครไม่ทำงานreadเรียลไทม์ พวกเขาทำงานในเวลาคอมไพล์ในแง่ที่ว่าถ้าคุณมีรหัสเช่น(and (test1) (test2))นั้นมันจะได้รับการขยายเข้าไปใน(if (test1) (test2) #f)(ใน Scheme) เพียงครั้งเดียวเมื่อรหัสถูกโหลดมากกว่าทุกครั้งที่มีการเรียกใช้รหัส แต่ถ้าคุณทำอะไรเช่น(eval '(and (test1) (test2)))ที่จะรวบรวม (และขยายแมโคร) การแสดงออกที่เหมาะสมในขณะใช้งานจริง
Chris Jester-Young

@ RenéG Homoiconicity เป็นสิ่งที่ช่วยให้ภาษา Lisp สามารถประเมินในโครงสร้างรายการแทนแบบฟอร์มต้นฉบับและสำหรับโครงสร้างรายการเหล่านั้นจะถูกเปลี่ยน (ผ่านแมโคร) ก่อนดำเนินการ ภาษาส่วนใหญ่ใช้evalงานได้กับสตริงข้อความเท่านั้นและความสามารถในการปรับแก้ไวยากรณ์นั้นน่าเบื่อและ / หรือยุ่งยากกว่ามาก
Chris Jester-Young

1

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

ภาษาที่ไม่มี S-Expressions หรือ Homoiconicity จะพบปัญหาในการติดตั้งมาโคร Syntactic แม้ว่าจะสามารถทำได้อย่างแน่นอน โครงการเคปเลอร์พยายามที่จะแนะนำให้รู้จักกับ Scala เช่น

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

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