【问题标题】:Clojure GridBag macro and print-dupClojure GridBag 宏和打印副本
【发布时间】:2011-09-22 14:14:01
【问题描述】:

我正在编写一个 Clojure 宏,它接受使用 java.awt.GridBagLayout 的面板描述并在编译时生成等效代码(使用 (doto ...))。我知道seesaw,但我正在努力学习宏编写的来龙去脉。

我的问题:

  • 在编译过程中的什么时候,Java 方法(如 (Insets. 5 5 5 5))被编译(生成字节码)?
  • 为什么从宏中返回这些会出现问题?
  • 编译器不应该像手动扩展宏一样“看到”并编译相同的东西吗?
  • 是否有任何可以从辅助函数返回来改善这种情况的方法,可能使用(eval ...)#=(...),因为没有相关的运行时惩罚?

我知道这可以写成(defn ...) 并且(很容易)解决问题。我想用宏实现相同的结果,因为我可以看到函数的运行时性能损失可能是不可接受的其他情况(在这种情况下是 not,因为这是 GUI 代码)。我写这个宏的原因是因为我相信结果比手动扩展的版本更容易阅读和维护。

我已经包含了两个 (print-dup...) 多方法的定义,以满足编译器的要求并消除(不成功)运行时错误消息“无法在代码中嵌入对象,可能未定义 print-dup:java.awt .Insets[top=5,left=5,bottom=5,right=5]"

宏是这样调用的:

(grid-bag-container (JPanel. (GridBagLayout.))
  [(JButton "Monday") :gridwidth 2 :weightx 1.0 :fill :HORIZONTAL]
  [(JCheckBox "Vacation")]
  [[(JLabel. "Arrive:")] [(JTextField. 6) :fill :HORIZONTAL]]
  [[(JLabel. "Depart:")] [(JTextField. 6) :fill :HORIZONTAL]])

这是预期的扩展(为了便于阅读,印刷精美:-)):

(doto (JPanel. (GridBagLayout.))
  (.add (JButton "Monday")
        (GridBagConstraints. 0 0 2 1 1.0 0
                             (. GridBagConstraints WEST)
                             (. GridBagConstraints HORIZONTAL)
                             (Insets. 2 2 2 2) 0 0))
  (.add (JCheckBox "Vacation")
        (GridBagConstraints. 0 1 1 1 0 0
                             (. GridBagConstraints WEST)
                             (. GridBagConstraints NONE)
                             (Insets. 2 2 2 2) 0 0))
  (.add (JLabel. "Arrive:")
        (GridBagConstraints. 0 2 1 1 0 0
                             (. GridBagConstraints WEST)
                             (. GridBagConstraints NONE)
                             (Insets. 2 2 2 2) 0 0))
  (.add (JTextField. 6)
        (GridBagConstraints. 1 2 1 1 0 0
                             (. GridBagConstraints WEST)
                             (. GridBagConstraints HORIZONTAL)
                             (Insets. 2 2 2 2) 0 0))
  (.add (JLabel. "Depart:")
        (GridBagConstraints. 0 3 1 1 0 0
                             (. GridBagConstraints WEST)
                             (. GridBagConstraints NONE)
                             (Insets. 2 2 2 2) 0 0))
  (.add (JTextField. 6)
        (GridBagConstraints. 1 3 1 1 0 0
                             (. GridBagConstraints WEST)
                             (. GridBagConstraints HORIZONTAL)
                             (Insets. 2 2 2 2) 0 0)))

代码如下:

(defmethod print-dup java.awt.GridBagConstraints [args writer]
  "A multimethod for converting java.awt.GridBagConstraints to a compiled form.
  @param args a collection of constructor arguments
  @param writer the Writer to which the output should be generated"
  (.write writer "#=(java.awt.GridBagConstraints. ")
  (.write writer (apply str (interpose " " (map str args))))
  (.write writer ")"))

(defmethod print-dup java.awt.Insets [args writer]
  "A multimethod for converting java.awt.Insets to a compiled form.
  @param args a collection of (Integer) constructor arguments
  @param writer the Writer to which the output should be generated"
  (.write writer "#=(java.awt.Insets. ")
  (.write writer (apply str (interpose " " (map str args))))
  (.write writer ")"))

(defmacro grid-bag-container [container & args]
  "Fill a container having a GridBagLayout with the given components.
   The args can start with an optional default-constraints map (see the
   doc-string for build-gbc (below) for details on the constraints map).
   Following the optional default-constraints are zero or more rows.
   Each row is a vector containing either a single component specification
   or multiple vectors of component specifications. Each component specification
   is a component (e.g.: JButton) followed by one or more key-value constraints
   of the same form as the default-constraints. Note that these key-value
   pairs are NOT contained in a map. Each row vector will be placed in
   the next gridy position (starting with 0). If a row vector contains only
   one component specification, that component will be placed at gridx=0.
   If a row vector contains vectors, each will be placed at the next gridx
   position (starting with 0). The default values for the constraints are as
   follows:
     :gridwidth 1
     :gridheight 1
     :weightx 0
     :weighty 0
     :anchor :WEST
     :fill :NONE
     :insets (Insets. 5 5 5 5)
     :ipadx 0
     :ipady 0
   For example:
     (grid-bag-container
       (JPanel.)
       {:insets (Insets. 2 2 2 2)}              ; Override the default (Insets. 5 5 5 5)
       [button :gridwidth 2 :weightx 1]         ; Add a button at (gridx=0, gridy=0) with the
                                                ; gridwidth=2 (overriding the default 1),
                                                ; and weightx=1 (overriding the default 0)
       [[label] [textfield :fill :HORIZONTAL]]) ; Add a label at (gridx=0, gridy=1)
                                                ; and a textfield at (gridx=1, gridy=1),
                                                ; with fill=GridBagContraints.CENTER
                                                ; (overriding the default GridBagContraints.WEST)
   This example will expand to
     (doto container
       (.add button (build-gbc {:gridx 0 :gridwidth 2 :ipadx 0 :ipady 0 :anchor :WEST :weighty 0
                                :gridheight 1 :weightx 1 :fill :NONE :insets (Insets. 2 2 2 2)
                                :gridy 0}))
       (.add label (build-gbc {:gridx 0 :gridwidth 1 :ipadx 0 :ipady 0 :anchor :WEST :weighty 0
                               :gridheight 1 :weightx 0 :fill :NONE :insets (Insets. 2 2 2 2)
                               :gridy 1}))
       (.add textfield (build-gbc {:gridx 1 :gridwidth 1 :ipadx 0 :ipady 0 :anchor :WEST :weighty 0
                                   :gridheight 1 :weightx 0 :fill :HORIZONTAL :insets (Insets. 2 2 2 2)
                                   :gridy 1})))
   @param container the java.awt.Container to fill
   @args an optional default-constraints map followed by zero or more row specifications
   @returns the container

   build-gbc:
   Build and return a GridBagConstraints containing the given constraints map.
   Each constraint is a (:key value) pair where the name of the key is a
   GridBagConstraints field (e.g.: gridwidth) and the value is either a keyword
   (e.g.: :CENTER), in which case the GridBagConstraints constant of the same name
   (e.g.: GridBagConstraints.CENTER) is used, or anything else, in which case the
   corresponding field is set to that value.
   Example:
     (build-gbc {:gridx 0
                 :gridy 0
                 :gridheight 1
                 :gridwidth 2
                 :weightx 1
                 :weighty 0
                 :anchor :CENTER
                 :fill :NONE
                 :insets (Insets. 2 2 2 2)
                 :ipadx 0
                 :ipady 0})
   will build and return a GridBagConstraints containing the following field values:
     gridx 0
     gridy 0
     gridheight 1
     gridwidth 2
     weightx 1
     weighty 0
     anchor GridBagConstraints.CENTER
     fill GridBagConstraints.NONE
     insets (Insets. 2 2 2 2)
     ipadx 0
     ipady 0.
   @param constraints a map containing the GridBagConstraints constraint values
   @returns a new GridBagConstraints
   @see http://stuartsierra.com/2010/01/05/taming-the-gridbaglayout"
  (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
        (if (map? (first args))
          (first args)
          {})

        args
        (into []
          (if (map? (first args))
            (rest args)
            args))

        build-gbc
        (fn [constraints]
          (let [process-value
                #(if (nil? %)
                  nil
                  (if (keyword? %)
                    `(. GridBagConstraints ~(symbol (name %)))
                    %))]
            `(GridBagConstraints.
              ~(process-value (:gridx constraints))
              ~(process-value (:gridy constraints))
              ~(process-value (:gridwidth constraints))
              ~(process-value (:gridheight constraints))
              ~(process-value (:weightx constraints))
              ~(process-value (:weighty constraints))
              ~(process-value (:anchor constraints))
              ~(process-value (:fill constraints))
              ~(process-value (:insets constraints))
              ~(process-value (:ipadx constraints))
              ~(process-value (:ipady constraints)))))]
    `(doto ~container
      ~@(loop [end (count args)
               gridy 0
               ret []]
        (if (= end gridy)
          ret
          (let [row (nth args gridy)
                process-item
                (fn [component gridx gridy constraints]
                  (let [constraints
                        (reduce into global-defaults
                          [{:gridx gridx :gridy gridy}
                           defaults
                           (vec (map vec (partition 2 constraints)))])]
                    `(.add ~component ~(build-gbc constraints))))]
            (if (vector? (first row))
              (recur end
                (inc gridy)
                (into ret (for [gridx (range (count row))
                                :let [item (nth row gridx)
                                      component (first item)
                                      constraints (rest item)]]
                  (process-item component gridx gridy constraints))))
              (recur end
                (inc gridy)
                (conj ret (let [component (first row)
                                constraints (rest row)]
                  (process-item component 0 gridy constraints)))))))))))

【问题讨论】:

    标签: macros clojure


    【解决方案1】:

    AFAICS,您遇到的问题是您在宏中生成 Inset 对象,而不是生成 Inset 对象的代码。 IOW,你在 global-defaults 中的 :insets (Insets. 5 5 5 5) 应该是 :insets '(Insets. 5 5 5 5) 或类似的东西。

    print-dup 代码令人困惑且不需要,因此请忽略它。

    【讨论】:

    • 谢谢!我要试试。我的宏还支持一个可选的初始参数,其中包含对全局默认值的覆盖:(grid-bag-container panel {:insets (Insets 2 2 2 2)} ...)。你知道这些是否也应该被引用吗? (我假设是这样)。
    • 是否需要给宏引用参数取决于宏的实现;在这种情况下,您可能希望接受未引用的 (Inset..) 代码。您必须记住的是,宏的输出是代码(不是实例化的 GUI 对象),宏的参数也是(未评估的)代码。
    • 您的示例调用还包括 (JButton "blah") 而不是 (JButton. "blah"),但我认为 @Joost 的真正问题得到了控制。
    猜你喜欢
    • 1970-01-01
    • 2022-08-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多