Clojure

Java相互運用

クラスアクセス

クラス名
クラス名$ネストされたクラス名

クラス名を表すシンボルは、Classインスタンスに解決されます。内部クラスまたはネストされたクラスは、`$`で外部クラスから区切られます。完全修飾クラス名は常に有効です。名前空間でクラスが`import`されている場合、修飾なしで使用できます。java.langのすべてのクラスは、すべての名前空間に自動的にインポートされます。

String
-> java.lang.String
(defn date? [d] (instance? java.util.Date d))
-> #'user/date?
(.getEnclosingClass java.util.Map$Entry)
-> java.util.Map

メンバーアクセス

(.instanceMember インスタンス args*)
(.instanceMember クラス名 args*)
(.-instanceField インスタンス)
(クラス名/staticMethod args*)
クラス名/staticField

(.toUpperCase "fred")
-> "FRED"
(.getName String)
-> "java.lang.String"
(.-x (java.awt.Point. 1 2))
-> 1
(System/getProperty "java.vm.version")
-> "1.6.0_07-b06-57"
Math/PI
-> 3.141592653589793

フィールドまたはメソッドメンバーにアクセスするための推奨される慣用的な形式は上記のとおりです。インスタンスメンバー形式は、フィールドとメソッドの両方で機能します。instanceField形式はフィールドに推奨され、同じ名前のフィールドと0引数メソッドの両方が存在する場合は必須です。これらはすべて、マクロ展開時にドット演算子(下記参照)の呼び出しに展開されます。展開は次のとおりです。

(.instanceMember instance args*) ==> (. instance instanceMember args*)
(.instanceMember Classname args*) ==>
    (. (identity Classname) instanceMember args*)
(.-instanceField instance) ==> (. instance -instanceField)
(Classname/staticMethod args*) ==> (. Classname staticMethod args*)
Classname/staticField ==> (. Classname staticField)

ドット特殊形式

(*.* instance-expr member-symbol)
(*.* Classname-symbol member-symbol)
(*.* instance-expr -field-symbol)
**( *.* instance-expr (method-symbol args*))** または **(*.* instance-expr method-symbol args*)**
**( *.* Classname-symbol (method-symbol args*))** または **(*.* Classname-symbol method-symbol args*)**

特殊形式。

'.'特殊形式は、Javaへのアクセスの基礎となります。これは、メンバーアクセス演算子と見なすことができ、または「~のスコープ内」と読むことができます。

最初のオペランドがクラス名に解決されるシンボルである場合、アクセスは名前付きクラスの静的メンバーへのアクセスと見なされます。JVM仕様に従って、ネストされたクラスの名前はEnclosingClass$NestedClassであることに注意してください。そうでない場合は、インスタンスメンバーと見なされ、最初の引数はターゲットオブジェクトを生成するために評価されます。

Classインスタンスでインスタンスメンバーを呼び出す特別な場合、最初の引数はクラスインスタンスに評価される式である必要があります。上記の推奨形式では、`Classname`が`(identity Classname)`に展開されることに注意してください。

2番目のオペランドがシンボルで、引数が指定されていない場合、フィールドアクセスと見なされます。フィールドの名前はシンボルの名前であり、式の値はフィールドの値です。ただし、同じ名前の引数なしのpublicメソッドが存在する場合は、メソッドの呼び出しに解決されます。2番目のオペランドが `-` で始まるシンボルの場合、member-symbol はフィールドアクセスとしてのみ解決され(0引数メソッドとしては解決されません)、それが意図されている場合は優先する必要があります。

2番目のオペランドがリストであるか、引数が指定されている場合、メソッド呼び出しと見なされます。リストの最初の要素は単純なシンボルでなければならず、メソッドの名前はシンボルの名前です。引数がある場合は、左から右に評価され、一致するメソッドに渡され、呼び出され、その値が返されます。メソッドの戻り値の型がvoidの場合、式の値は***nil***になります。メソッド名をリストに引数と一緒に配置することは、正規の形式ではオプションですが、フォーム上に構築されたマクロで引数を収集するのに役立ちます。

ブール値の戻り値はブール値に変換され、charはCharacterになり、数値プリミティブは数値プリミティブを受け取るメソッドによってすぐに消費されない限り数値になります。

このセクションの冒頭に示したメンバーアクセス形式は、マクロ以外の場合に使用することをお勧めします。


(*..* instance-expr member+)
(*..* Classname-symbol member+)

member ⇒ fieldName-symbol または (instanceMethodName-symbol args*)

マクロ。最初の引数の最初のメンバーのメンバーアクセス(.)に展開され、その結果の次のメンバーが続き、以下同様です。例えば

(.. System (getProperties) (get "os.name"))

は以下のように展開されます

(. (. System (getProperties)) (get "os.name"))

しかし、書くのも読むのも理解するのも簡単です。同様に使用できる -> マクロも参照してください。

(-> (System/getProperties) (.get "os.name"))


(*doto* instance-expr (instanceMethodName-symbol args*)*)

マクロ。instance-exprを評価し、結果のオブジェクトに対して指定された引数ですべてのメソッド/関数を順番に呼び出し、それを返します。

(doto (new java.util.HashMap) (.put "a" 1) (.put "b" 2))
-> {a=1, b=2}

(クラス名. args*)
(*new* クラス名 args*)

特殊形式。

引数がある場合は、左から右に評価され、Classnameで名前が付けられたクラスのコンストラクターに渡されます。構築されたオブジェクトが返されます。

代替マクロ構文

示されているように、正規の特殊形式newに加えて、Clojureは'.'を含むシンボルの特別なマクロ展開をサポートしています。

(new Classname args*)

は次のように書くことができます

(Classname. args*) ;末尾のドットに注意してください

後者はマクロ展開時に前者に展開されます。


(*instance?* クラス expr)

exprを評価し、それがクラスのインスタンスであるかどうかをテストします。trueまたはfalseを返します。


(*set!* (. instance-expr instanceFieldName-symbol) expr)
(*set!* (. Classname-symbol staticFieldName-symbol) expr)

代入特殊形式。

最初のオペランドがフィールドメンバーアクセス形式の場合、代入は対応するフィールドに行われます。インスタンスフィールドの場合、インスタンスexprが評価され、次にexprが評価されます。

すべての場合において、exprの値が返されます。

注 - *関数のパラメーターまたはローカルバインディングに代入することはできません。Clojureでは、Javaフィールド、Var、Ref、およびエージェントのみが変更可能です*。


(*memfn* method-name arg-names*)

マクロ。オブジェクトと引数を渡されることを期待し、引数を渡してオブジェクトで名前付きインスタンスメソッドを呼び出すfnを作成するコードに展開されます。Javaメソッドをファーストクラスfnとして扱いたい場合に使用します。

(map (memfn charAt i) ["fred" "ethel" "lucy"] [1 2 3])
-> (\r \h \y)

次のような構文で直接これを行う方がほぼ常に望ましいことに注意してください。

(map #(.charAt %1 %2) ["fred" "ethel" "lucy"] [1 2 3])
-> (\r \h \y)

(*bean* obj)

Javaオブジェクトを受け取り、そのJavaBeanプロパティに基づいてマップ抽象化の読み取り専用実装を返します。

(bean java.awt.Color/black)
-> {:RGB -16777216, :alpha 255, :blue 0, :class java.awt.Color,
    :colorSpace #object[java.awt.color.ICC_ColorSpace 0x5cb42b "java.awt.color.ICC_ColorSpace@5cb42b"],
    :green 0, :red 0, :transparency 1}

Clojureライブラリ関数におけるJavaのサポート

Clojureライブラリ関数の多くは、Java型のオブジェクトに対して定義されたセマンティクスを持っています。contains?とgetはJavaマップ、配列、文字列で機能し、後者の2つは整数キーを使用します。countはJava文字列、コレクション、配列で機能します。nthはJava文字列、リスト、配列で機能します。seqはJava参照配列、Iterable、および文字列で機能します。ライブラリの残りの大部分はこれらの関数に基づいて構築されているため、ClojureアルゴリズムでJavaオブジェクトを使用するための優れたサポートがあります。

インターフェースの実装とクラスの拡張

Clojureは、proxyマクロを使用して、1つ以上のインターフェースを実装する、および/またはクラスを拡張するオブジェクトの動的作成をサポートしています。結果のオブジェクトは匿名クラスです。gen-classを使用して、静的に名前が付けられたクラスと.classファイルを生成することもできます。Clojure 1.2以降、インターフェースを実装するためにreifyも使用できます。

Javaアノテーションは、`gen-class` および Clojure 型コンストラクトの メタデータ を介してクラス、コンストラクタ、およびメソッドに添付できます。例については、データ型リファレンス を参照してください。


(*proxy* [class-and-interfaces] [args] fs+)

class-and-interfaces - クラス名のベクター
args - スーパークラスコンストラクターへの引数の(おそらく空の)ベクター。
f ⇒ (name [params*] body) または (name ([params*] body) ([params+] body) …​)

マクロ

指定された関数呼び出しによって、名前付きクラス/インターフェースを実装するプロキシクラスのインスタンスを作成するコードに展開されます。単一のクラスが提供される場合、最初に指定する必要があります。提供されない場合、デフォルトはObjectです。インターフェース名は有効なインターフェース型である必要があります。クラスメソッドにメソッド関数が提供されない場合、スーパークラスメソッドが呼び出されます。インターフェースメソッドにメソッド関数が提供されない場合、呼び出されるとUnsupportedOperationExceptionがスローされます。メソッド関数はクロージャであり、プロキシが呼び出される環境をキャプチャできます。各メソッド関数は、thisにバインドされている暗黙の最初の引数を追加で取ります。メソッド関数はprotectedメソッドをオーバーライドするために提供できますが、protectedメンバーにもsuperにもアクセスできません。これらの機能はプロキシできません。

配列

Clojureは、Java配列の作成、読み取り、および変更をサポートしています。配列の使用は、引数として配列を必要とするJavaライブラリとの相互運用、または戻り値としての使用に限定することをお勧めします。

多くの他の Clojure 関数は、seq ライブラリなどを介して配列を操作します。ここにリストされている関数は、配列の初期作成、または配列に対する変更や高性能操作をサポートするために存在します。

可変長引数メソッド

Java の可変長引数メソッドは、末尾の可変長引数パラメータを配列として扱います。 Clojure からは、可変長引数の代わりに明示的な配列を渡すことで呼び出すことができます。

可変長引数の型に応じて、プリミティブ型には型固有の配列コンストラクタを使用し、特定の型の配列を作成するには into-array を使用します。例については、FAQ を参照してください。

既存のコレクションから配列を作成する: aclone amap to-array to-array-2d into-array
多次元配列のサポート: aget aset to-array-2d make-array
型固有の配列コンストラクタ: boolean-array byte-array char-array double-array float-array int-array long-array object-array short-array
プリミティブ配列のキャスト: booleans bytes chars doubles floats ints longs shorts
配列を変更する: aset
既存の配列を処理する: aget alength amap areduce

型ヒント

Clojure は、コンパイラがパフォーマンスクリティカルなコード領域でのリフレクションを回避するのを支援するために、型ヒントの使用をサポートしています。通常、既知のパフォーマンスボトルネックが発生するまで、型ヒントの使用は避けるべきです。型ヒントは、コンパイラによって使用されるシンボルまたは式に配置されるメタデータタグです。関数パラメータ、let でバインドされた名前、var 名(定義されている場合)、および式に配置できます。

(defn len [x]
  (.length x))

(defn len2 [^String x]
  (.length x))

user=> (time (reduce + (map len (repeat 1000000 "asdf"))))
"Elapsed time: 3007.198 msecs"
4000000
user=> (time (reduce + (map len2 (repeat 1000000 "asdf"))))
"Elapsed time: 308.045 msecs"
4000000

識別子または式に型ヒントが配置されると、コンパイラはコンパイル時にそのメソッドの呼び出しを解決しようとします。さらに、コンパイラは戻り値の使用を追跡し、それらの使用の型などを推測するため、完全にコンパイル時に解決される一連の呼び出しを取得するために必要なヒントはごくわずかです。コンパイラは常にその型情報を持っているため、静的フィールドまたは静的メソッドの戻り値には型ヒントは必要ありません。

*warn-on-reflection* フラグ(デフォルトは false)があり、コンパイラが直接呼び出しに解決できない場合に警告が表示されます。

(set! *warn-on-reflection* true)
-> true

(defn foo [s] (.charAt s 1))
-> Reflection warning, line: 2 - call to charAt can't be resolved.
-> #user/foo

(defn foo [^String s] (.charAt s 1))
-> #user/foo

関数の戻り値の場合、型ヒントはパラメータベクトルの前に配置できます。

(defn hinted-single ^String [])

-> #user/hinted-single

(defn hinted
  (^String [])
  (^Integer [a])
  (^java.util.List [a & args]))

-> #user/hinted

エイリアス

Clojure は、Java クラス名としての一般的な表現を持たないプリミティブな Java 型と配列のエイリアスを提供します。型は、Java フィールド記述子の仕様に従って表されます。たとえば、バイト配列(byte-array [])の型は "[B" です。

  • int - プリミティブ int

  • ints - int 配列

  • long - プリミティブ long

  • longs - long 配列

  • float - プリミティブ float

  • floats - float 配列

  • double - プリミティブ double

  • doubles - double 配列

  • void - void 戻り値

  • short - プリミティブ short

  • shorts - short 配列

  • boolean - プリミティブ boolean

  • booleans - boolean 配列

  • byte - プリミティブ byte

  • bytes - byte 配列

  • char - プリミティブ文字

  • chars - 文字配列

  • objects - オブジェクト配列

Java プリミティブのサポート

Clojure は、ローカルコンテキストでの Java プリミティブ型と、それらを含む算術演算の高性能操作をサポートしています。すべての Java プリミティブ型がサポートされています:int、float、long、double、boolean、char、short、および byte。

  • let/loop でバインドされたローカル変数はプリミティブ型にすることができ、初期化フォームから推測される、場合によってはプリミティブ型になります。

  • プリミティブなローカル変数を再バインドする recur 形式は、ボクシングなしで再バインドを行い、同じプリミティブ型の型チェックを行います。

  • 算術演算(+、-、*、/、inc、dec、<、<=、>、>= など)は、セマンティクスが同じであればプリミティブ型に対してオーバーロードされます。

  • aget / aset はプリミティブの配列に対してオーバーロードされます

  • プリミティブの配列用の aclonealength 関数

  • プリミティブ配列のコンストラクタ関数: float-arrayint-array など

  • プリミティブ配列の型ヒント - ^ints、^floats など

  • 型変換操作 intfloat などは、コンシューマがプリミティブを受け取ることができる場合にプリミティブを生成します

  • num 型変換関数は、ジェネリック算術を強制するためにプリミティブをボックス化します

  • int[]、long[] などを生成する配列キャスト関数 ints longs など

  • 最大限のパフォーマンスを発揮するが、潜在的に安全でない整数(int / long)操作のための「チェックされていない」操作のセット: unchecked-multiply unchecked-dec unchecked-inc unchecked-negate unchecked-add unchecked-subtract unchecked-remainder unchecked-divide

  • 安全な操作を未チェックの操作に自動的にスワップするための動的変数: *unchecked-math*

  • それぞれ新しい配列または集計値を生成するために、1つ以上の配列を関数的に(つまり、非破壊的に)処理するための amap および areduce マクロ。

この Java を書くのではなく

static public float asum(float[] xs){
  float ret = 0;
  for(int i = 0; i < xs.length; i++)
    ret += xs[i];
  return ret;
}

この Clojure を書くことができます

(defn asum [^floats xs]
  (areduce xs i ret (float 0)
    (+ ret (aget xs i))))

そして、結果のコードはまったく同じ速度です(java -server で実行した場合)。

これの最も優れた点は、初期コーディングで特別なことをする必要がないことです。多くの場合、これらの最適化は不要です。コードの一部がボトルネックになる場合は、わずかな装飾で高速化できます

(defn foo [n]
  (loop [i 0]
    (if (< i n)
      (recur (inc i))
      i)))

(time (foo 100000))
"Elapsed time: 0.391 msecs"
100000

(defn foo2 [n]
  (let [n (int n)]
    (loop [i (int 0)]
      (if (< i n)
        (recur (inc i))
        i))))

(time (foo2 100000))
"Elapsed time: 0.084 msecs"
100000

関数は、プリミティブな引数と戻り値の型を限定的にサポートしています。longdouble(これらのみ)の型ヒントは、プリミティブ型のオーバーロードを生成します。この機能は、アリティが 4 以下の関数に制限されていることに注意してください。

このように定義された関数

(defn foo ^long [^long n])

は、プリミティブ型 long の値を受け取り、返します(ボックス化された引数と実際にはオブジェクトの結果は、キャストとプリミティブ型のオーバーロードへの委任になります)。

型変換

特定のプリミティブ型の値が必要になる場合があります。これらの型変換関数は、そのような型変換が可能であれば、指定された型の値を生成します: bigdec bigint boolean byte char double float int long num short

最適化のヒント

  • すべての引数はオブジェクトとして Clojure 関数に渡されるため、関数引数に任意のプリミティブ型ヒントを付ける意味はありません(プリミティブ配列型ヒント、および long と double は例外です)。代わりに、示されている let 手法を使用して、本文でプリミティブ算術に参加する必要がある場合に、引数をプリミティブローカルに配置します。

  • (let [foo (int bar)] …​) は、プリミティブローカルを取得する正しい方法です。 ^Integer などを使用しないでください。

  • 切り捨て操作が必要でない限り、チェックされていない数学に急がないでください。 HotSpot はオーバーフローチェックの最適化に優れており、サイレント切り捨ての代わりに例外が発生します。典型的な例では、速度に約 5% の違いがあります。それは価値があります。また、コードを読んでいる人は、切り捨てのためにチェックされていないものを使用しているのか、パフォーマンスのために使用しているのかわかりません。前者のために予約し、後者の場合はコメントするのが最善です。

  • 通常、外側のループを最適化しようとする意味はありません。実際、内部呼び出しの引数になるために再ボックス化する必要があるプリミティブとして表現されるため、有害になる可能性があります。唯一の例外はリフレクションの警告です。頻繁に呼び出されるコードでは、それらを削除する必要があります。

  • 誰かがヒントで最適化しようとしているものを提示するたびに、高速バージョンはオリジナルよりもはるかに少ないヒントを持っています。ヒントが最終的に物事を改善しない場合は、それを削除してください。

  • 多くの人は、チェックされていない操作のみがプリミティブ算術を実行すると推定しているようです。そうではありません。引数がプリミティブローカルである場合、通常の + や * などはオーバーフローチェックでプリミティブ数学を実行します。高速*かつ*安全です。

  • したがって、高速数学への最も簡単なルートは、演算子をそのままにして、ソースリテラルとローカルがプリミティブであることを確認することです。プリミティブの算術演算はプリミティブを生成します。ループがある場合(最適化する必要がある場合はおそらくそうです)、最初にループローカルがプリミティブであることを確認してください。そうすれば、誤ってボックス化された中間結果を生成した場合、recur でエラーが発生します。そのエラーを中間結果を強制することによって解決しないでください。代わりに、どの引数またはローカルがプリミティブでないかを把握してください。

シンプルな XML サポート

配布物には、src/clj/clojure/xml.clj ファイルにあるシンプルな XML サポートが含まれています。このファイルのすべての名前は clojure.xml 名前空間に属します。


parse ソース)

URI を示す File、InputStream、または String である source を解析してロードします。:tag、:attrs、:content をキーとし、tag、attrs、content をアクセサ関数とする clojure.xml/element struct-map のツリーを返します。

(clojure.xml/parse "/Users/rich/dev/clojure/build.xml")
-> {:tag :project, :attrs {:name "clojure", :default "jar"}, :content [{:tag :description, ...

Java から Clojure を呼び出す

clojure.java.api パッケージは、他の JVM 言語から Clojure へのアクセスをブートストラップするための最小限のインターフェースを提供します。これは以下を提供することで実現されます。

  1. Clojure の名前空間を使用して任意の var を探し、var の clojure.lang.IFn インターフェースを返す機能。

  2. Clojure の edn reader を使用してデータを読み取るための便利なメソッド read

IFns は Clojure の API への完全なアクセスを提供します。ソースまたはコンパイルされた形式をクラスパスに追加した後、Clojure で記述された他のライブラリにもアクセスできます。

Clojure の公開 Java API は、以下のクラスとインターフェースで構成されています。

他のすべての Java クラスは実装の詳細として扱うべきであり、アプリケーションはそれらに依存することを避けるべきです。

Clojure 関数をルックアップして呼び出すには

IFn plus = Clojure.var("clojure.core", "+");
plus.invoke(1, 2);

clojure.core の関数は自動的にロードされます。他の名前空間は require を介してロードできます。

IFn require = Clojure.var("clojure.core", "require");
require.invoke(Clojure.read("clojure.set"));

IFns は高階関数に渡すことができます。たとえば、以下の例では incmap に渡しています。

IFn map = Clojure.var("clojure.core", "map");
IFn inc = Clojure.var("clojure.core", "inc");
map.invoke(inc, Clojure.read("[1 2 3]"));

Clojure のほとんどの IFns は関数を参照します。ただし、関数以外のデータ値を参照するものもあります。これらにアクセスするには、関数を呼び出す代わりに deref を使用します。

IFn printLength = Clojure.var("clojure.core", "*print-length*");
IFn deref = Clojure.var("clojure.core", "deref");
deref.invoke(printLength);