开发者

Declare Member Variables with gen-class in Clojure

I'm 开发者_开发知识库learning how to extend Java classes in Clojure, but I don't see a way declare new member variables; I only see a way for methods.

(ns test.aclass
  (:gen-class
    :methods [[foo [] String]]))

Is there a :members keyword or another way of declaring member variables?


:state name

If supplied, a public final instance field with the given name will be created. You must supply an :init function in order to provide a value for the state. Note that, though final, the state can be a ref or agent, supporting the creation of Java objects with transactional or asynchronous mutation semantics.

There is an example on the website of how it can be used.


I was having some trouble with this too. The example below is not elegant but it's pretty simple for writing dumb little glue classes in Clojure rather than Java. Note all I did for thread safety is to ensure that the field updates are atomic -- I didn't do any other concurrency stuff, and that may make a real difference.

The init method creates the instance variables for the object. The setfield and getfield macros abbreviate the bookkeeping of the atomic update.

(ns  #^{:doc "A simple class with instance vars"
    :author "David G. Durand"}
   com.tizra.example )

(gen-class
  :name com.tizra.example.Demo
  :state state
  :init init
  :prefix "-"
  :main false
  :methods [[setLocation [String] void]
            [getLocation [] String]]
)

(defn -init []
  "store our fields as a hash"
  [[] (atom {:location "default"})])

(defmacro setfield
  [this key value]
  `(swap! (.state ~this) into {~key ~value}))

(defmacro getfield
  [this key]
  `(@(.state ~this) ~key))

(defn -setLocation [this ^java.lang.String loc]
  (setfield this :location loc))

(defn ^String -getLocation
  [this]
  (getfield this :location))

You have to compile this, and make sure the stub class produced is on your classpath, and then you can make instances, etc. just like any other java class.

=> (com.tizra.example.Demo.)
#<Demo com.tizra.example.Demo@673a95af>

=> (def ex (com.tizra.example.Demo.))
#'user/ex

=> (.getLocation ex)
"default"

=> (.setLocation ex "time")
nil

=> (.getLocation ex)
"time"

I found the longer summary at this blog quite helpful: http://kotka.de/blog/2010/02/gen-class_how_it_works_and_how_to_use_it.html


The body of a proxy is a lexical closure, so you can just close around whatever variables you need. If, God forbid, you need to mutate them, then close around an atom:

(defn lying-list [init-size]
  (let [the-size (atom init-size)]
    (proxy [java.util.ArrayList] []
      (size [] @the-size)
      (remove [idx] (reset! the-size idx), true))))

There's really no need for actual Java fields here.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜