วิธีที่เหมาะสมในการใช้ macroexpand-1
สำหรับการทดสอบมาโคร Clojure เมื่อไม่ได้ทำงานที่ REPL คืออะไร
ฉันจะใช้ macroexpand-1 จากไฟล์ต้นฉบับแทน REPL ได้อย่างไร
คำตอบ (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)))))
สมมติว่าเราต้องการทดสอบมาโครที่บวก 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 ได้อย่างไร