แมโคร Lisp มีประโยชน์อย่างไร


22

Common Lisp ช่วยให้คุณสามารถเขียนมาโครที่ทำสิ่งแปลงแหล่งที่คุณต้องการ

Scheme ช่วยให้คุณมีระบบจับคู่รูปแบบที่ถูกสุขลักษณะที่ให้คุณทำการแปลงได้เช่นกัน มาโครมีประโยชน์อย่างไรในทางปฏิบัติ Paul Graham กล่าวในการเอาชนะค่าเฉลี่ยที่:

ซอร์สโค้ดของเครื่องมือแก้ไข Viaweb น่าจะเป็นมาโครประมาณ 20-25%

สิ่งที่ผู้คนทำลงเอยด้วยมาโครจริง ๆ แล้วมีอะไรบ้าง?


ฉันคิดว่านี่เป็นอัตนัยที่ดีแน่นอนฉันได้แก้ไขคำถามของคุณสำหรับการจัดรูปแบบ นี่อาจ เป็นสิ่งที่ซ้ำกัน แต่ฉันไม่สามารถหาได้
Tim Post

1
ทุกอย่างซ้ำไปซ้ำมาซึ่งดูเหมือนจะไม่เข้ากับหน้าที่ฉันคิดว่า

2
คุณสามารถใช้แมโครเพื่อเปิดเสียงกระเพื่อมเข้าใด ๆ ภาษาอื่น ๆ ที่มีไวยากรณ์ใด ๆ และความหมายใด ๆ : bit.ly/vqqvHU
SK-ตรรกะ

programmers.stackexchange.com/questions/81202/…มีค่าดูที่นี่ แต่มันไม่ซ้ำกัน
David Thornley

คำตอบ:


15

ดูการโพสต์นี้โดย Matthias Felleisen ในรายการสนทนา LL1 ในปี 2002 เขาแนะนำการใช้งานหลักสามประการสำหรับแมโคร:

  1. ภาษาย่อยของข้อมูล : ฉันสามารถเขียนนิพจน์ที่ดูง่ายและสร้างรายการ / อาร์เรย์ / ตารางที่ซ้อนกันที่ซับซ้อนพร้อมคำพูด unquote ฯลฯ ตกแต่งอย่างเรียบร้อยด้วยมาโคร
  2. โครงสร้างการเชื่อมโยง: ฉันสามารถแนะนำโครงสร้างการโยงใหม่ด้วยแมโคร นั่นช่วยฉันกำจัดลูกแกะและวางสิ่งต่าง ๆ เข้าด้วยกันที่อยู่ด้วยกัน
  3. การจัดเรียงการประเมินผลใหม่ : ฉันสามารถแนะนำโครงสร้างที่ล่าช้า / เลื่อนการประเมินผลของนิพจน์ตามต้องการ คิดว่าลูปเงื่อนไขใหม่ความล่าช้า / แรง ฯลฯ [หมายเหตุ: ใน Haskell หรือภาษาขี้เกียจสิ่งนี้ไม่จำเป็น]

18

ฉันส่วนใหญ่ใช้มาโครในการเพิ่มโครงสร้างภาษาใหม่ที่ช่วยประหยัดเวลาไม่เช่นนั้นจะต้องใช้รหัสสำเร็จรูปมากมาย

ตัวอย่างเช่นเมื่อเร็ว ๆ นี้ฉันพบว่าตัวเองต้องการคำสั่งที่for-loopคล้ายกับ C ++ / Java อย่างไรก็ตามการเป็นภาษาที่ใช้งานได้ Clojure ไม่ได้มาพร้อมกับกล่องเดียว ดังนั้นฉันเพิ่งใช้มันเป็นมาโคร:

(defmacro for-loop [[sym init check change :as params] & steps]
  `(loop [~sym ~init value# nil]
     (if ~check
       (let [new-value# (do ~@steps)]
         (recur ~change new-value#))
       value#)))

และตอนนี้ฉันสามารถทำได้:

 (for-loop [i 0 , (< i 10) , (inc i)] 
   (println i))

และที่นั่นคุณก็มี - ภาษาคอมไพล์เวลาทั่วไปที่สร้างใหม่ในรหัสหกบรรทัด


13

สิ่งที่ผู้คนทำลงเอยด้วยมาโครจริง ๆ แล้วมีอะไรบ้าง?

การเขียนนามสกุลภาษาหรือ DSL

ในการทำความเข้าใจกับสิ่งนี้ในภาษาที่เหมือนเสียงกระเพื่อมให้ศึกษาไม้แร็กเก็ตซึ่งมีหลากหลายภาษา: Typed Racket, R6RS และ Datalog

ดูเพิ่มเติมที่Boo languageซึ่งให้คุณเข้าถึงไพพ์ไลน์คอมไพเลอร์สำหรับวัตถุประสงค์เฉพาะในการสร้าง Domain-Specific Languages ​​ผ่านมาโคร


4

นี่คือตัวอย่างบางส่วน:

โครงการ:

  • defineสำหรับนิยามฟังก์ชั่น โดยทั่วไปจะทำให้วิธีการกำหนดฟังก์ชั่นสั้นลง
  • let สำหรับการสร้างตัวแปรที่กำหนดขอบเขตศัพท์

Clojure:

  • defnตามเอกสาร:

    เช่นเดียวกับ (ชื่อ def (fn [params *] exprs *)) หรือ (def ชื่อ (fn ([params *] exprs *) +)) ด้วย doc-string หรือ attrs ใด ๆ ที่เพิ่มลงในข้อมูลเมตาของ var

  • for: รายการความเข้าใจ
  • defmacro: แดกดัน?
  • defmethod, defmulti: ทำงานกับหลายวิธี
  • ns

มาโครจำนวนมากทำให้การเขียนโค้ดในระดับนามธรรมง่ายขึ้น ฉันคิดว่ามาโครมีความคล้ายคลึงกันในหลาย ๆ ด้านกับไวยากรณ์ในแบบไม่พิมพ์

การพล็อตไลบรารีIncanterจัดเตรียมแมโครสำหรับการปรับเปลี่ยนข้อมูลที่ซับซ้อน


4

แมโครมีประโยชน์ในการฝังรูปแบบบางอย่าง

ยกตัวอย่างเช่น Common LISP ไม่ได้กำหนดwhileวง แต่มีdo ที่สามารถใช้ในการกำหนดมัน

นี่คือตัวอย่างจาก ในเสียงกระเพื่อม

(defmacro while (test &body body)
  `(do ()
       ((not ,test))
     ,@body))

(let ((a 0))
  (while (< a 10)
    (princ (incf a))))

สิ่งนี้จะพิมพ์ "12345678910" และหากคุณพยายามดูว่าเกิดอะไรขึ้นกับ macroexpand-1:

(macroexpand-1 '(while (< a 10) (princ (incf a))))

สิ่งนี้จะส่งคืน:

(DO () ((NOT (< A 10))) (PRINC (INCF A)))

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

loopแมโครเป็นตัวอย่างที่ดีของสิ่งที่สามารถทำแมโคร

(loop for i from 0 to 10
      if (and (= (mod i 2) 0) i)
        collect it)
=> (0 2 4 6 8 10)
(loop for i downfrom 10 to 0
      with a = 2
      collect (* a i))
=> (20 18 16 14 12 10 8 6 4 2 0)               

Common Lisp มีมาโครชนิดอื่นที่เรียกว่ามาโครสำหรับอ่านซึ่งสามารถใช้เพื่อแก้ไขวิธีที่ผู้อ่านตีความรหัสเช่นคุณสามารถใช้เพื่อใช้ # {และ #} มีตัวคั่นเช่น # (และ #)


3

นี่คือสิ่งที่ฉันใช้สำหรับการแก้ไขข้อบกพร่อง (ใน Clojure):

user=> (defmacro print-var [varname] `(println ~(name varname) "=" ~varname))
#'user/print-var
=> (def x (reduce * [1 2 3 4 5]))
#'user/x
=> (print-var x)
x = 120
nil

ฉันต้องจัดการกับตารางแฮชแบบรีดด้วยมือใน C ++ ซึ่งgetวิธีนี้ใช้การอ้างอิงสตริงที่ไม่ใช่ const เป็นอาร์กิวเมนต์ซึ่งหมายความว่าฉันไม่สามารถเรียกมันด้วยตัวอักษรได้ เพื่อให้ง่ายต่อการจัดการกับฉันเขียนสิ่งต่อไปนี้:

#define LET(name, value, body)  \
    do {                        \
        string name(value);     \
        body;                   \
        assert(name == value);  \
    } while (false)

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

ฉันยังหันไปใช้แฮ็คที่น่าเกลียดอย่างยิ่งในการห่อสิ่งต่าง ๆ ในแบบdo ... while (false)ที่คุณสามารถใช้ในส่วนนั้นของ if และยังคงมีงานส่วนอื่นตามที่คาดไว้ คุณไม่ต้องการสิ่งนี้ในเสียงกระเพื่อมซึ่งเป็นฟังก์ชั่นของแมโครที่ทำงานบนต้นไม้ไวยากรณ์มากกว่าสตริง (หรือลำดับโทเค็นฉันคิดว่าในกรณีของ C และ C ++) ซึ่งได้รับการแยกวิเคราะห์

มีมาโครในตัวอยู่สองสามตัวซึ่งสามารถใช้ในการจัดระเบียบโค้ดของคุณใหม่เพื่อให้อ่านได้อย่างสะอาดยิ่งขึ้น ('เธรด' เหมือนกับใน 'การหว่านรหัสของคุณเข้าด้วยกัน' ไม่ใช่การขนานกัน) ตัวอย่างเช่น:

(->> (range 6) (filter even?) (map inc) (reduce *))

มันใช้รูปแบบแรก(range 6)และทำให้มันเป็นอาร์กิวเมนต์สุดท้ายของรูปแบบต่อไป(filter even?)ซึ่งจะทำให้อาร์กิวเมนต์สุดท้ายของรูปแบบต่อไปและอื่น ๆ เช่นที่ได้รับการเขียนด้านบน

(reduce * (map inc (filter even? (range 6))))

ฉันคิดว่าคนแรกอ่านได้ชัดเจนยิ่งขึ้น: "เอาข้อมูลเหล่านี้ไปทำมันจากนั้นทำอย่างนั้นทำสิ่งอื่นแล้วเราก็ทำเสร็จ" แต่นั่นเป็นเรื่องส่วนตัว สิ่งที่เป็นความจริงอย่างเป็นกลางคือคุณอ่านการดำเนินการตามลำดับที่แสดง

นอกจากนี้ยังมีตัวแปรที่แทรกรูปแบบก่อนหน้านี้เป็นอาร์กิวเมนต์แรก (แทนล่าสุด) กรณีใช้งานหนึ่งค่าคือเลขคณิต:

(-> 17 (- 2) (/ 3))

อ่านเป็น "รับ 17, ลบ 2 แล้วหารด้วย 3"

เมื่อพูดถึงเลขคณิตคุณสามารถเขียนแมโครที่ใช้การแยกวิเคราะห์สัญกรณ์เพื่อให้คุณสามารถพูดเช่น(infix (17 - 2) / 3)และจะคายออก(/ (- 17 2) 3)ซึ่งมีข้อเสียของการอ่านน้อยลงและข้อดีของการเป็นนิพจน์เสียงกระเพื่อมที่ถูกต้อง นั่นคือส่วนย่อยของ DSL / data


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