Apa cara yang tepat untuk menggunakan macroexpand-1
untuk menguji makro Clojure saat tidak bekerja di REPL?
Bagaimana saya bisa menggunakan macroexpand-1 dari file sumber alih-alih REPL
Jawaban (2)
Masalahnya adalah ekspresi Anda di luar deftest
dijalankan pada waktu kompilasi, sementara *ns*
terikat, dan di dalam deftest
dijalankan kemudian, saat runtime, di mana *ns*
tidak terikat.
Mengapa ini penting? Karena macroexpand
perlu menyelesaikan simbol iiinc
di namespace saat ini untuk memutuskan apakah itu makro atau bukan, dan untuk menemukan definisinya apakah itu makro, untuk memanggilnya. Jadi, Anda melihat macroexpand
Anda berfungsi pada waktu kompilasi tetapi tidak pada waktu proses.
Apa solusinya? Tentunya untuk tidak menjalankan pengujian Anda pada waktu kompilasi! Sebaliknya, Anda harus mengkualifikasi namespace formulir Anda dengan benar, sehingga formulir tersebut tidak bergantung pada kenyamanan waktu kompilasi *ns*
. Anda dapat melakukannya dengan tangan, dengan menulis
(deftest t-stuff
(println "(macroexpand-1 '(my.ns/iiinc 2)) =>" (macroexpand-1 '(my.ns/iiinc 2)))))
Namun, solusi yang tepat adalah melakukan apa yang harus Anda selalu lakukan ketika mengutip formulir yang dimaksudkan untuk evaluasi nanti, sama seperti ketika Anda menulis makro: gunakan kutipan sintaksis, bukan kutipan biasa. Dengan cara ini kompiler mengetahui namespace yang dimaksudkan untuk Anda pada waktu kompilasi, dan menyisipkannya ke dalam formulir sehingga masih ada saat runtime:
(deftest t-stuff
(println "(macroexpand-1 `(iiinc 2)) =>" (macroexpand-1 `(iiinc 2)))))
Misalkan kita ingin menguji makro yang menambahkan 3 pada nilai apa pun:
(defmacro iiinc [x]
`(+ 3 ~x))
Daripada bekerja di REPL, saya biasanya lebih suka menggunakan editor teks/IDE favorit saya untuk mengembangkan kode, dan menggunakan plugin lein test-refresh
untuk terus menjalankan pengujian unit saya. Namun, ini tidak berhasil ketika mencoba menggunakan macroexpand-1
untuk mengembangkan makro baru secara berulang.
Masalahnya sepertinya ada konflik antara makro macroexpand-1
dan deftest
. Jadi, solusinya adalah menghindari penggunaan macroexpand-1
di dalam formulir (deftest ...)
. Namun, ini berfungsi dengan baik di luar deftest
, meskipun masih dalam file sumber pengujian unit. Berikut ini contohnya:
; 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
Hasil di atas adalah:
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.
Nota bene
Silakan lihat jawaban berikut untuk pembahasan lebih lengkap tentang cara menulis makro di Clojure:
Bagaimana cara menulis makro threading Clojure?