CLOVER🍀

That was when it all began.

Clojureのクラスに、コンストラクタとStateを定義する

続いて、今度はClojureでコンストラクタを持つクラスを作成してみます。
foo.clj

(ns foo
  (:gen-class
   :init init
   :state state
   :constructors {[String] []}
   :methods [[decorate [char] String]]))

(defn -init [val]
  [[] val])

(defn -decorate [this c]
  (let [decoration (reduce #(str %1 "" %2) (repeat 3 c))]
    (str decoration (.state this) decoration)))

少し解説。

initキーワードで、初期化関数を指定します。

   :init init

定義する関数は、今回はこのようにしました。

(defn -init [val]
  [[] val])

関数の引数には、コンストラクタの引数が渡ってきます。

ここで、コンストラクタの定義は以下のようにしているので

   :constructors {[String] []}

引数には、String型の値が渡ってきます。

doc-stringより、initの方は

If supplied, names a function that will be called with the arguments
to the constructor. Must return [ [superclass-constructor-args] state]
If not supplied, the constructor args are passed directly to
the superclass constructor and the state will be nil

と書かれているので、initキーワードで指定した関数の戻り値は親クラスのコンストラクタ引数、そしてstateのベクタだと言っています。よって、-init関数の戻り値は

  [[] val]

なので、親クラスのコンストラクタには引数無し、そしてstateには引数の値をそのまま設定しています。

なお、constructorsキーワードの方ですが、

:constructors {[param-types] [super-param-types], ...}

という定義のため、

   :constructors {[String] []}

という定義は、このクラスのコンストラクタはString型の引数を持ち、親クラスのコンストラクタには引数がない、ということを表しています。

なお、状態を表すstateキーワードには

   :state state

そのまま、stateという名前で参照できるようにしました。

では、コンパイルしてJavaから使ってみます。

public class Caller {
    public static void main(String[] args) {
        foo f = new foo("Hello Clojure");
        System.out.println(f.state);  // => Hello Clojure
        System.out.println(f.decorate('*'));  // => ***Hello Clojure***
    }
}

コンストラクタに、Stringの引数を与えられるようになっています。また、stateも普通に参照可能です。

続いて、Clojureから呼び出してみます。

(def f (foo. "Hello World"))
(println (.state f))  ;; => Hello World
(try
  (set! (.state f) "Hoge")
  (catch Exception e (println (.toString e)))) ;; => java.lang.IllegalAccessException: Can not set final java.lang.Object field foo.state to java.lang.String

Javaの時と同じように使用可能です。

stateですが、注意点としてはコンストラクタで初期化する以外に方法がなく、かつ1度指定した参照は変更することができません。stateはfinal修飾された形でJavaのフィールドにコンパイルされるからです。

こんな感じに。

$ javap classes/foo
Warning: Binary file classes/foo contains foo
public class foo {
  public final java.lang.Object state;
  public static {};
  public foo(java.lang.String);
  public java.lang.Object clone();
  public int hashCode();
  public java.lang.String toString();
  public boolean equals(java.lang.Object);
  public java.lang.String decorate(char);
  public static void main(java.lang.String[]);
}