ปัญหาที่นี่เป็นเรื่องละเอียดอ่อนและอาจเป็นเรื่องยากหากปราศจากความเข้าใจเกี่ยวกับมาโครสักเล็กน้อยก่อน
แมโครจัดการไวยากรณ์ในลักษณะเดียวกับที่ฟังก์ชันจัดการค่า ที่จริงแล้ว มาโครเป็นเพียงฟังก์ชันที่มีจุดเชื่อมต่อซึ่งทำให้มีการประเมินในเวลาคอมไพล์ ข้อมูลเหล่านี้จะถูกส่งผ่านข้อมูลตามตัวอักษรที่คุณเห็นในซอร์สโค้ดและได้รับการประเมินจากบนลงล่าง มาสร้างฟังก์ชันและมาโครที่มีเนื้อหาเหมือนกันเพื่อให้คุณเห็นความแตกต่าง:
(defmacro print-args-m [& args]
(print "Your args:")
(prn args))
(defn print-args-f [& args]
(print "Your args:")
(prn args))
(print-args-m (+ 1 2) (str "hello" " sir!"))
; Your args: ((+ 1 2) (str "hello" " sir!"))
(print-args-f (+ 1 2) (str "hello" " sir!"))
; Your args: (3 "hello sir!")
แมโครจะถูกแทนที่ด้วยค่าที่ส่งคืน คุณสามารถตรวจสอบกระบวนการนี้ได้ด้วย macroexpand
(defmacro defmap [sym & args]
`(def ~sym (hash-map ~@args))) ; I won't explain these crazy symbols here.
; There are plenty of good tutorials around
(macroexpand
'(defmap people
"Steve" {:age 53, :gender :male}
"Agnes" {:age 7, :gender :female}))
; (def people
; (clojure.core/hash-map
; "Steve" {:age 53, :gender :male}
; "Agnes" {:age 7, :gender :female}))
ณ จุดนี้ ฉันควรจะอธิบายว่า '
ทำให้แบบฟอร์มต่อไปนี้เป็น quote
d ซึ่งหมายความว่าคอมไพเลอร์จะอ่านแบบฟอร์ม แต่จะไม่ดำเนินการหรือพยายามแก้ไขสัญลักษณ์และอื่นๆ เช่น 'conj
ประเมินเป็นสัญลักษณ์ ในขณะที่ conj
ประเมินเป็นฟังก์ชัน (eval 'conj)
เทียบเท่ากับ (eval (quote conj))
เทียบเท่ากับ conj
ด้วยเหตุนี้ โปรดทราบว่าคุณไม่สามารถแก้ไขสัญลักษณ์เป็นเนมสเปซได้จนกว่าจะถูกนำเข้าสู่เนมสเปซของคุณอย่างน่าอัศจรรย์ นี่คือสิ่งที่ฟังก์ชัน require
ทำ ใช้สัญลักษณ์และค้นหาเนมสเปซที่ตรงกัน ทำให้สามารถใช้ได้ในเนมสเปซปัจจุบัน
มาดูกันว่ามาโคร ns
ขยายเป็นอะไรบ้าง:
(macroexpand
'(ns sample.core
(:require clojure.set clojure.string)))
; (do
; (clojure.core/in-ns 'sample.core)
; (clojure.core/with-loading-context
; (clojure.core/refer 'clojure.core)
; (clojure.core/require 'clojure.set 'clojure.string)))
ดูว่ามันอ้างอิงสัญลักษณ์ clojure.set
และ clojure.string
สำหรับเราอย่างไร วิธีที่สะดวก! แต่จะมีประโยชน์อะไรเมื่อคุณใช้ require
แทน :require
(macroexpand
'(ns sample.core
(require clojure.set clojure.string)))
; (do
; (clojure.core/in-ns 'sample.core)
; (clojure.core/with-loading-context
; (clojure.core/refer 'clojure.core)
; (clojure.core/require 'clojure.set 'clojure.string)))
ดูเหมือนว่าใครก็ตามที่เขียนมาโคร ns
ก็ยินดีพอที่จะให้เราทำทั้งสองวิธี เนื่องจากผลลัพธ์นี้ยังคงเหมือนเดิมทุกประการ นีโต้!
แก้ไข: tvachon ถูกต้องเกี่ยวกับการใช้ :require
เท่านั้น เนื่องจากเป็นรูปแบบเดียวที่ได้รับการสนับสนุนอย่างเป็นทางการ
แต่การจัดการกับวงเล็บคืออะไร?
(macroexpand
'(ns sample.core
(:require [clojure.set]
[clojure.string])))
; (do
; (clojure.core/in-ns 'sample.core)
; (clojure.core/with-loading-context
; (clojure.core/refer 'clojure.core)
; (clojure.core/require '[clojure.set] '[clojure.string])))
ปรากฎว่าพวกเขาได้รับการยกมาเหมือนกัน เช่นเดียวกับที่เราทำถ้าเราเขียนการโทรแบบสแตนด์อโลนไปที่ require
ปรากฎว่า ns
ไม่สนใจว่าเราจะใส่รายการ (พาเรนต์) หรือเวกเตอร์ (วงเล็บ) ให้มันใช้งานหรือไม่ มันแค่มองว่าข้อโต้แย้งเป็นลำดับของสิ่งต่าง ๆ ตัวอย่างเช่น วิธีนี้ได้ผล:
(ns sample.core
[:gen-class]
[:require [clojure.set]
[clojure.string]])
require
ตามที่ amalloy ชี้ให้เห็นในความคิดเห็น มีความหมายที่แตกต่างกันสำหรับเวกเตอร์และรายการ ดังนั้นอย่าปะปนกัน!
สุดท้ายแล้วเหตุใดสิ่งต่อไปนี้จึงใช้งานไม่ได้
(ns sample.core
(:require 'clojure.string 'clojure.test))
เนื่องจาก ns
ทำการยกมาให้เรา สัญลักษณ์เหล่านี้จึงถูกยกมาสองครั้ง ซึ่งแตกต่างทางความหมายจากการยกมาเพียงครั้งเดียวและยังเป็นความบ้าคลั่งอีกด้วย
conj ; => #<core$conj clojure.core$conj@d62a05c>
'conj ; => conj
''conj ; => (quote conj)
'''conj ; => (quote (quote conj))
ฉันหวังว่านี่จะช่วยได้ และฉันขอแนะนำให้เรียนรู้วิธีเขียนมาโครอย่างแน่นอน พวกเขาสนุกสุด ๆ
person
d.j.sheldrick
schedule
09.04.2013