Macros 强制Clojure宏中的参数以捕获命名空间
我正在开发Clojure宏,以帮助构建基于GridBagLayout的JPanel。我可以在宏内的默认映射中获取Java类,以限定名称空间,但不能将这些类作为参数传入。我需要什么样的反报价、报价、tildas或其他东西的神奇组合Macros 强制Clojure宏中的参数以捕获命名空间,macros,clojure,quoting,Macros,Clojure,Quoting,我正在开发Clojure宏,以帮助构建基于GridBagLayout的JPanel。我可以在宏内的默认映射中获取Java类,以限定名称空间,但不能将这些类作为参数传入。我需要什么样的反报价、报价、tildas或其他东西的神奇组合 (import [java.awt GridBagConstraints GridBagLayout Insets] [javax.swing JButton JPanel]) (defmacro make-constraints [gridx gri
(import [java.awt GridBagConstraints GridBagLayout Insets]
[javax.swing JButton JPanel])
(defmacro make-constraints [gridx gridy & constraints]
(let [defaults
{:gridwidth 1 :gridheight 1 :weightx 0 :weighty 0
:anchor 'GridBagConstraints/WEST :fill 'GridBagConstraints/NONE
:insets `(Insets. 5 5 5 5) :ipadx 0 :ipady 0}
values
(assoc (merge defaults (apply hash-map constraints))
:gridx gridx :gridy gridy)]
`(GridBagConstraints. ~@(map (fn [value]
(if
(or
(number? value)
(string? value)
(char? value)
(true? value)
(false? value)
(nil? value))
value
`~value))
(map values
[:gridx :gridy :gridwidth :gridheight
:weightx :weighty :anchor :fill
:insets :ipadx :ipady])))))
当我使用默认映射中定义的插入时,它将被限定为(java.awt.Insets…
):
但当我把它作为一个论点来传递时,它并没有:
user=> (macroexpand-1 '(make-constraints 1 1 :insets (Insets. 2 2 2 2)))
(java.awt.GridBagConstraints.
1 1 1 1 0 0
GridBagConstraints/WEST GridBagConstraints/NONE
(Insets. 2 2 2 2) 0 0)
我不只是想成为一个固执的人。我遇到了编译器错误,无法找到合适的GridBagConstraints
构造函数。这是我的解决方案。我正在编写的Swing应用程序中使用它。它已经为我节省了许多行代码编写(针对两个不同的面板),速度将与手写代码一样快
(defmacro grid-bag-container [container & args]
"Fill and return a java.awt.Container that uses the GridBagLayout.
The macro defines a set of default constraints for the GridBagConstraints:
:gridwidth 1
:gridheight 1
:weightx 0
:weighty 0
:anchor :WEST
:fill :NONE
:insets (Insets. 5 5 5 5)
:ipadx 0
:ipady 0
These defaults can be overridden in the call to the macro in two way:
- If the first argument is a hash-map of constraint names and values
(e.g.: {:weightx 1}), these will override the defaults for the
entire container.
- Each individual item (see below) can override the global defaults
and container defaults for itself.
The constraints consist of constraint name (as a keyword with the same
name as the GridBagConstraints field), and a value, which can also be
a keyword, in which case the appropriate constant from GridBagConstraints
will be substituted (e.g.: :NONE == GridBagConstraints.NONE), or the value
can be an expression (e.g.: 0 or (Insets. 2 2 2 2)).
Following the optional container default overrides hash-map are one or
more row specification vectors. Each vector represents one row and
increments gridy (starting from 0). Each vector contains one or more
item vectors representing the individual components to be added to the
container. Each item vector has the component as its first value,
followed by zero or more constraint overrides as keyword-value pairs.
(e.g.: [myButton :gridwidth 2 :weightx 1]). The values may be keywords
and are expanded to GridBagConstraints constants as described above.
Each item vector gets the next value of gridx (starting with 0) in that
row.
For example:
(grid-bag-container panel
{:insets (Insets. 1 1 1 1)}
[[button :gridwidth 2 :weightx 1.0 :fill :HORIZONTAL]]
[[check-box :gridwidth 2 :weightx 1.0 :anchor :CENTER]]
[[arrive-label] [arrive-text-field :fill :HORIZONTAL]]
[[depart-label] [depart-text-field :fill :HORIZONTAL]])
will expand to the hand-written equivalent:
(doto panel
(.add button
(GridBagConstraints. 0 0 2 1 1.0 0 ; gridx: 0 gridy: 1
GridBagConstraints/WEST
GridBagConstraints/HORIZONTAL
(Insets. 1 1 1 1) 0 0))
(.add check-box
(GridBagConstraints. 0 1 2 1 1.0 0 ; gridx: 0 gridy: 1
GridBagConstraints/CENTER
GridBagConstraints/NONE
(Insets. 1 1 1 1) 0 0))
(.add arrive-label
(GridBagConstraints. 0 2 1 1 0 0 ; gridx: 0 gridy: 2
GridBagConstraints/WEST
GridBagConstraints/NONE
(Insets. 1 1 1 1) 0 0))
(.add arrive-text-field
(GridBagConstraints. 1 2 1 1 0 0 ; gridx: 1 gridy: 2
GridBagConstraints/WEST
GridBagConstraints/HORIZONTAL
(Insets. 1 1 1 1) 0 0))
(.add depart-label
(GridBagConstraints. 0 3 1 1 0 0 ; gridx: 0 gridy: 3
GridBagConstraints/WEST
GridBagConstraints/NONE
(Insets. 1 1 1 1) 0 0))
(.add depart-text-field
(GridBagConstraints. 1 3 1 1 0 0 ; gridx: 1 gridy: 3
GridBagConstraints/WEST
GridBagConstraints/HORIZONTAL
(Insets. 1 1 1 1) 0 0))
@param container the java.awt.Container to fill
@param args the components and GridBagContraints speicifcations
@returns the filled Container"
(let [global-defaults
{:gridwidth 1
:gridheight 1
:weightx 0
:weighty 0
:anchor :WEST
:fill :NONE
:insets `(Insets. 5 5 5 5)
:ipadx 0
:ipady 0}
[defaults rows]
(if (map? (first args))
[(into global-defaults (first args)) (rest args)]
[global-defaults args])]
`(doto ~container
~@(loop [gridy 0 rows rows ret []]
(if (seq rows)
(recur (inc gridy) (rest rows)
(into ret
(let [row (first rows)]
(loop [gridx 0 row row ret []]
(if (seq row)
(recur (inc gridx) (rest row)
(conj ret
(let [item
(first row)
component
(first item)
constraints
(assoc (merge defaults
(apply hash-map (rest item)))
:gridx gridx :gridy gridy)
constraint-values
(map (fn [value]
(if (keyword? value)
`(. GridBagConstraints
~(symbol (name value)))
`~value))
(map constraints
[:gridx :gridy :gridwidth :gridheight
:weightx :weighty :anchor :fill
:insets :ipadx :ipady]))]
`(.add ~component (new GridBagConstraints
~@constraint-values)))))
ret)))))
ret)))))
谢谢,谢谢你的帮助。我不知道GridBagLayout
,但是下面的工作原理应该与你的宏类似。如果组件的:height
大于1,则必须在其下方的列中添加nil
,以保持列计数器同步。例如,您的到达文本字段
的高度为2,而您必须在离开标签
行之前添加一行nil
,以保持计数器正确。这只是一个快速的破解
(def default-opts
{:insets (Insets. 0 0 0 0)
:width 1
:height 1
:weight-x 0.0
:weight-y 0.0
:fill GridBagConstraints/NONE
:anchor GridBagConstraints/WEST
:ipadx 0
:ipady 0})
(defn grid-bag-constraints
[x y global-opts opts]
(let [{:keys [insets width height weight-x weight-h
fill anchor ipadx ipady]}
(merge default-opts global-opts opts)]
(GridBagConstraints. x y width height weight-x weight-h
anchor fill insets ipadx ipady)))
(defn grid-bag-container
[panel global-opts & rows]
(doseq [[row-idx row] (map-indexed identity rows)
[col-idx [target & {:as opts}]] (map-indexed identity row)
:when target]
(let [constraints (grid-bag-constraints col-idx row-idx global-opts opts)]
(.add panel target constraints))))
用法与以前一样。如果调用宏的客户机在其命名空间中导入了Insets
,则您的宏不需要对其进行限定(通常需要做大量工作)。如果他没有导入插图
,他就做错了。在我看来,如果用户有权导入
,那么这两个选项要么都应该编译,要么都不编译。迎合那些没有导入权限的用户只会让所有相关人员流泪;如果它仍然损坏,您能提供更多信息吗?Ralph,您使用的是哪个Java版本和哪个Clojure版本?这两种make约束的调用,包括传递Insets的调用,都适用于我。我正在使用Clojure 1.2.0和Sun Java 1.6.0_24。另一方面,你当地人身上的#
是误导性的。这意味着它们在扩展上下文中被用作gensym,而实际上它们只是本地人,在宏运行时使用了一个有趣的名称。@amalloy:the#
s是因为我对gensym
的理解很差。在您提到它之后,我再次思考并意识到,我需要gensym的唯一符号是那些绑定在宏扩展中的符号(在本例中没有)。我更新了这个问题。@user100464:Java6在OsX上构建26,Clojure1.2。实际上,这个宏是一个更复杂的宏的简化,它实际上构建了整个JPanel。我可能在简化时无意中解决了自己的问题。我再看看我的原稿。
(def default-opts
{:insets (Insets. 0 0 0 0)
:width 1
:height 1
:weight-x 0.0
:weight-y 0.0
:fill GridBagConstraints/NONE
:anchor GridBagConstraints/WEST
:ipadx 0
:ipady 0})
(defn grid-bag-constraints
[x y global-opts opts]
(let [{:keys [insets width height weight-x weight-h
fill anchor ipadx ipady]}
(merge default-opts global-opts opts)]
(GridBagConstraints. x y width height weight-x weight-h
anchor fill insets ipadx ipady)))
(defn grid-bag-container
[panel global-opts & rows]
(doseq [[row-idx row] (map-indexed identity rows)
[col-idx [target & {:as opts}]] (map-indexed identity row)
:when target]
(let [constraints (grid-bag-constraints col-idx row-idx global-opts opts)]
(.add panel target constraints))))