2011-06-25 35 views
2

我正在編寫一個Clojure宏,它接受使用java.awt.GridBagLayout的面板描述並在編譯時生成等效代碼(使用(doto ...))。我知道seesaw,但我正在努力學習宏觀寫作的細節。Clojure GridBag宏和print-dup

我的問題:

  • 在什麼時候在編譯期間是Java方法(如(Insets. 5 5 5 5))編譯(生成的字節碼)?
  • 爲什麼從宏返回這些問題?
  • 編譯器不應該「看到」並編譯相同的東西,就像我手動擴展宏一樣?
  • 是否有任何可以從幫助程序函數返回的內容,可以改善這種情況,可能使用(eval ...)#=(...),因爲沒有相關的運行時間損失?

我知道這可以寫成(defn ...)和(很容易)解決問題。我想用宏實現相同的結果,因爲我可以看到其他情況下,運行時性能損失可能不可接受(在這種情況下爲而不是,因爲這是GUI代碼)。我編寫這個宏的原因是因爲我相信結果比手動擴展版本更容易閱讀和維護。

我已經包含了兩個(print-dup...)多方法的定義,以努力滿足編譯器並消除(不成功)運行時錯誤消息「無法在代碼中嵌入對象,也許未定義print-dup:java.awt。插圖[頂= 5,左= 5,底部= 5,右側= 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 
     [email protected](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))))))))))) 

回答

4

AFAICS,你正在運行到的問題是,你產生的插圖對象宏,而不是代碼生成插圖的對象。 IOW,全局默認設置中的:insets (Insets. 5 5 5 5)應改爲:insets '(Insets. 5 5 5 5)或類似內容。

print-dup代碼令人困惑,並且不需要,因此請將其忽略。

+0

謝謝!我要去嘗試一下。我的宏還支持一個可選的初始參數,其中包含覆蓋全局默認值:'(grid-bag-container panel {:insets(Insets 2 2 2 2)} ...)''。你知道這些也應該引用嗎? (我是這麼認爲的)。 – Ralph

+0

是否需要引用宏的參數取決於宏的實現;在這種情況下,您可能想要接受未加引號(Inset ..)的代碼。你必須記住的是宏的輸出是代碼(沒有實例化的GUI對象),宏的參數也是(未評估的)代碼。 –

+0

您的示例調用還包括(JButton「blah」)而不是(JButton。「blah」),但我認爲@Joost的真正問題在控制之下。 – amalloy