ฉันจะใช้ macroexpand-1 จากไฟล์ต้นฉบับแทน REPL ได้อย่างไร

วิธีที่เหมาะสมในการใช้ macroexpand-1 สำหรับการทดสอบมาโคร Clojure เมื่อไม่ได้ทำงานที่ REPL คืออะไร


person Alan Thompson    schedule 06.05.2017    source แหล่งที่มา


คำตอบ (2)


ปัญหาคือนิพจน์ของคุณภายนอก deftest กำลังทำงานในเวลาคอมไพล์ ในขณะที่ *ns* ถูกผูกไว้ และภายใน deftest นิพจน์เหล่านั้นกำลังถูกเรียกใช้ในภายหลัง ณ รันไทม์ โดยที่ *ns* ไม่ได้ถูกผูกไว้

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

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

(deftest t-stuff
  (println "(macroexpand-1 '(my.ns/iiinc 2))  =>" (macroexpand-1 '(my.ns/iiinc 2)))))

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

(deftest t-stuff
  (println "(macroexpand-1 `(iiinc 2))  =>" (macroexpand-1 `(iiinc 2)))))
person amalloy    schedule 06.05.2017

สมมติว่าเราต้องการทดสอบมาโครที่บวก 3 เข้ากับค่าใดๆ:

(defmacro iiinc [x]
  `(+ 3 ~x))

แทนที่จะทำงานที่ REPL ฉันมักจะชอบใช้โปรแกรมแก้ไขข้อความ/IDE ที่ฉันชื่นชอบเพื่อพัฒนาโค้ด และใช้ ปลั๊กอิน lein test-refresh เพื่อรันการทดสอบหน่วยของฉันอย่างต่อเนื่อง อย่างไรก็ตาม การดำเนินการนี้ใช้ไม่ได้เมื่อพยายามใช้ macroexpand-1 เพื่อพัฒนาแมโครใหม่ซ้ำๆ

ดูเหมือนว่าปัญหาจะเกิดข้อขัดแย้งระหว่าง macroexpand-1 และมาโคร deftest ดังนั้น วิธีแก้ไขคือหลีกเลี่ยงการใช้ macroexpand-1 ภายในแบบฟอร์ม (deftest ...) อย่างไรก็ตาม มันใช้งานได้ดีภายนอก deftest แม้ว่าจะยังอยู่ในไฟล์ต้นฉบับการทดสอบหน่วยก็ตาม นี่คือตัวอย่าง:

; source file tst.clj.core

(newline)
(println "This works when not in (deftest ...)")
(println "(macroexpand-1 '(iiinc 2))  =>" (macroexpand-1 '(iiinc 2)))
    
(deftest t-stuff
  (newline)
  (println "But it is broken inside (deftest ...)")
  (println "(macroexpand-1 '(iiinc 2))  =>" (macroexpand-1 '(iiinc 2)))

  (newline)
  (println "However, we can use the macro itself fine in our tests")
  (println "  (iiinc 2) =>" (iiinc 2))
  (is (= 5 (iiinc 2))))  ; unit test works fine

ผลลัพธ์ข้างต้นคือ:

This works when not in (deftest ...)
(macroexpand-1 '(iiinc 2))  => (clojure.core/+ 3 2)

Testing tst.clj.core

But it is broken inside (deftest ...)
(macroexpand-1 '(iiinc 2))  => (iiinc 2)

However, we can use the macro itself fine in our tests
  (iiinc 2) => 5

Ran 1 tests containing 1 assertions.
0 failures, 0 errors.

คำลงท้าย

โปรดดูคำตอบต่อไปนี้สำหรับการอภิปรายที่สมบูรณ์ยิ่งขึ้นเกี่ยวกับวิธีเขียนแมโครใน Clojure:

ฉันจะเขียนมาโครเธรด Clojure ได้อย่างไร

person Alan Thompson    schedule 06.05.2017
comment
คำตอบที่ดี แต่ฉันคิดว่าเนื้อหาบางส่วนควรอยู่ในคำถาม เพราะมันอธิบายสิ่งที่คุณพยายามทำซึ่งไม่ได้ผล หากไม่มีบริบทของคำตอบนี้ จะไม่มีใครสามารถตอบคำถามแบบย่อของคุณได้ - person amalloy; 06.05.2017
comment
เมื่อมองย้อนกลับไปสี่ปีต่อมา ตอนนี้ฉันคิดว่าคำตอบทั้งหมดควรถูกย้ายไปยังคำถาม คุณไม่ได้ให้คำตอบสำหรับคำถามของคุณ ฉันจะทดสอบแมโครด้วย macroexpand ได้อย่างไร; คุณแค่สรุปว่ามันใช้งานไม่ได้ เป็นคำถามที่ละเอียดมากและฉันคิดว่าคำตอบของฉันให้คำตอบที่ดี ฉันแค่ไม่เห็นว่านี่เป็นคำตอบอย่างไร - person amalloy; 02.02.2021