Clojure

データ構造

Clojureには豊富なデータ構造があります。それらは共通の特性を共有しています。

  • 不変です。

  • 読み取り可能です。

  • equalsの実装において、適切な値の等価性セマンティクスをサポートしています。

  • 良好なハッシュ値を提供します。

  • さらに、コレクションは

    • インターフェースを介して操作されます。

    • シーケンスをサポートします。

    • 永続的な操作をサポートします。

    • メタデータのサポート

    • java.lang.Iterableを実装

    • java.util.Collectionまたはjava.util.Mapのオプションではない(読み取り専用)部分を具現化

nil

nilはClojureの任意のデータ型の可能な値です。nilはJavaのnullと同じ値です。Clojureの条件式システムはnilとfalseに基づいており、nilとfalseは条件テストにおける論理的な偽の値を表します - それ以外のものは論理的な真です。さらに、nilはシーケンスプロトコルにおけるシーケンスの終端を示すセマンティクス値として使用されます。

数値

ClojureはデフォルトでJVMプリミティブ値を完全にサポートしており、数値アプリケーションのための高性能で慣習的なClojureコードを可能にします。

Clojureはまた、java.lang.Numberから派生したJavaのボックス化された数値型(BigIntegerとBigDecimalを含む)に加えて、独自のRatio型もサポートしています。いくつかの特別な処理があります。

Long型

デフォルトでは、Clojureは自然数をJavaのlongプリミティブ型インスタンスとして扱います。プリミティブ整数演算の結果がプリミティブ値に収まりきらないほど大きい場合、java.lang.ArithmeticExceptionがスローされます。Clojureは、アポストロフィが付いた一連の代替数学演算子を提供します:+'、-'、*'、inc'、dec'。これらの演算子はオーバーフロー時にBigIntに自動的に昇格しますが、通常の数学演算子よりも効率が低くなります。

Ratio型

整数の比率を表します。整数の除算で整数に簡約できない場合は、Ratio型が生成されます。つまり、22/7 = 22/7となり、浮動小数点数や切り捨てられた値にはなりません。

伝播

BigIntと浮動小数点型は、演算全体で「伝播」します。つまり、BigIntを含む任意の整数演算はBigIntを生成し、doubleまたはfloatを含む任意の演算はdoubleを生成します。

BigIntとBigDecimalのリテラル

BigIntとBigDecimalの数値リテラルは、それぞれ接尾辞NとMを使用して指定します。

式例 戻り値

(== 1 1.0 1M)

true

(/ 2 3)

2/3

(/ 2.0 3)

0.6666666666666666

(map #(Math/abs %) (range -3 3))

(3 2 1 0 1 2)

(class 36786883868216818816N)

clojure.lang.BigInt

(class 3.14159265358M)

java.math.BigDecimal

計算:+ - * / inc dec quot rem min max
自動昇格計算:+' -' *' inc' dec'
比較:== < <= > >= zero? pos? neg?
ビット演算:bit-and bit-or bit-xor bit-not bit-shift-right bit-shift-left
Ratio型:numerator denominator
型変換:int bigdec bigint double float long num short

文字列

Clojureの文字列はJavaのStringです。出力も参照してください。

user=> (map (fn [x] (.toUpperCase x)) (.split "Dasher Dancer Prancer" " "))
("DASHER" "DANCER" "PRANCER")

文字

Clojureの文字はJavaのCharacterです。

キーワード

キーワードは、それ自身に評価されるシンボリック識別子です。非常に高速な等価性テストを提供します。シンボルと同様に、名前とオプションの名前空間を持ち、どちらも文字列です。先頭の':'は名前空間または名前の一部ではありません。

キーワードは、1つの引数(マップ)とオプションの2番目の引数(デフォルト値)のinvoke()に対してIFnを実装しています。たとえば、(:mykey my-hash-map :none)(get my-hash-map :mykey :none)と同じ意味です。getを参照してください。

シンボル

シンボルは、通常は何か他のものを参照するために使用される識別子です。プログラム形式で使用して、関数パラメータ、letバインディング、クラス名、グローバル変数を参照できます。名前とオプションの名前空間を持ち、どちらも文字列です。シンボルにはメタデータを含めることができます(with-metaを参照)。

キーワードと同様に、シンボルは1つの引数(マップ)とオプションの2番目の引数(デフォルト値)のinvoke()に対してIFnを実装しています。たとえば、('mysym my-hash-map :none)(get my-hash-map 'mysym :none)と同じ意味です。getを参照してください。

symbol symbol? gensym(#-サフィックス付きリーダーマクロも参照)

コレクション

Clojureのコレクションはすべて不変であり、永続的です。特に、Clojureのコレクションは、構造共有を利用することで、「変更された」バージョンの効率的な作成をサポートし、永続的な使用に関するすべての性能に関する保証を行います。コレクションは効率的で、本質的にスレッドセーフです。コレクションは抽象化によって表され、1つ以上の具体的な実現方法がある場合があります。「変更」操作は新しいコレクションを生成するため、新しいコレクションはソースコレクションと同じ具体的な型ではない可能性がありますが、同じ論理的な(インターフェース)型になります。

全ての集合は、集合のサイズを取得するためのcount、集合に「追加」するためのconj、そして集合全体を走査できるシーケンスを取得するためのseqをサポートしています。ただし、それらの具体的な動作は、集合の種類によってわずかに異なります。

集合はseq関数をサポートしているため、全てのシーケンス関数を任意の集合で使用できます。

Java集合のハッシュ

Javaの集合インターフェースは、リストセットマップにおけるhashCode()値の計算アルゴリズムを指定しています。全てのClojure集合は、そのhashCode()実装においてこれらの仕様に準拠しています。

Clojure集合のハッシュ

Clojureは、集合(およびその他の型)に対してより優れたハッシュ特性を提供する独自のハッシュ計算を提供しており、hasheq値として知られています。

IHashEqインターフェースは、hasheq値を取得するためのhasheq()関数を提供する集合をマークします。Clojureでは、hash関数を用いてhasheq値を計算できます。

順序付き集合(ベクター、リスト、シーケンスなど)は、hasheqの計算に以下のアルゴリズムを使用する必要があります(hashはhasheqを計算します)。整数オーバーフロー計算を取得するために、unchecked-add-intとunchecked-multiply-intが使用されていることに注意してください。

(defn hash-ordered [collection]
  (-> (reduce (fn [acc e] (unchecked-add-int
                            (unchecked-multiply-int 31 acc)
                            (hash e)))
              1
              collection)
      (mix-collection-hash (count collection))))

順序付けられていない集合(マップ、セット)は、hasheqの計算に以下のアルゴリズムを使用する必要があります。マップのエントリは、キーと値の順序付き集合として扱われます。整数オーバーフロー計算を取得するために、unchecked-add-intが使用されていることに注意してください。

(defn hash-unordered [collection]
  (-> (reduce unchecked-add-int 0 (map hash collection))
      (mix-collection-hash (count collection))))

mix-collection-hashアルゴリズムは、変更される可能性のある実装の詳細です。

リスト (IPersistentList)

リストは集合です。それらはISeqインターフェースを直接実装します。(空のリストもISeqを実装することに注意してください。ただし、seq関数は空のシーケンスに対しては常にnilを返します。)countはO(1)です。conjはアイテムをリストの先頭に配置します。

リストを作成する:list list*
リストをスタックのように扱う:peek pop
リストを調べる:list?

ベクター (IPersistentVector)

ベクターは、連続した整数でインデックス付けされた値の集合です。ベクターは、log32N回のジャンプでインデックスによるアイテムへのアクセスをサポートしています。countはO(1)です。conjはアイテムをベクターの最後に配置します。ベクターはrseqもサポートしており、これはアイテムを逆順で返します。ベクターはIFnを実装しており、1つの引数(インデックスと想定)のinvoke()をサポートし、nthのように自身で検索します。つまり、ベクターはそのインデックスの関数です。ベクターは、最初に長さで比較され、次に各要素が順に比較されます。

ベクターを作成する:vector vec vector-of
ベクターを調べる:get nth peek rseq vector?
ベクターを「変更する」:assoc pop subvec replace

こちらも参照:ジッパー

マップ (IPersistentMap)

マップは、キーを値にマッピングする集合です。ハッシュマップとソート済みマップの2種類のマップが提供されています。ハッシュマップは、hashCodeとequalsを正しくサポートするキーを必要とします。ソート済みマップは、Comparableを実装するキー、またはComparatorのインスタンスを必要とします。ハッシュマップは、(logN回のジャンプ)と比較して、より高速なアクセス(log32N回のジャンプ)を提供しますが、ソート済みマップは、ソートされています。countはO(1)です。conjは、別の(おそらく単一のエントリの)マップをアイテムとして期待し、古いマップと新しいマップのエントリを合わせた新しいマップを返します。これは古いマップのエントリを上書きする可能性があります。conjは、MapEntryまたは2つのアイテム(キーと値)のベクターも受け入れます。seqは、キー/値ペアであるマップエントリのシーケンスを返します。ソート済みマップはrseqもサポートしており、これはエントリを逆順で返します。マップはIFnを実装しており、1つの引数(キー)とオプションの2番目の引数(デフォルト値)のinvoke()をサポートします。つまり、マップはそのキーの関数です。nilキーと値は問題ありません。

新しいマップを作成する:hash-map sorted-map sorted-map-by
マップを「変更する」:assoc dissoc select-keys merge merge-with zipmap
マップを調べる:get contains? find keys vals map?
マップのエントリを調べる:key val

StructMaps

StructMapsのほとんどの用途は、現在ではレコードの方が適しています。

多くのマップインスタンスは、同じ基本キーセットを持つことがよくあります。たとえば、マップが他の言語での構造体やオブジェクトとして使用される場合などです。StructMapsは、キー情報を効率的に共有しながら、それらのキーへのオプションの強化されたパフォーマンスアクセッサを提供することにより、このユースケースをサポートします。StructMapsはあらゆる点でマップであり、同じ関数セットをサポートし、他のすべてのマップと相互運用可能であり、永続的に拡張可能です(つまり、StructMapsは基本キーに限定されません)。唯一の制限は、StructMapの基本キーの1つをdissociateできないことです。StructMapは、基本キーを順序どおりに保持します。

StructMapsは、最初にcreate-structまたはdefstructを使用して構造体の基礎オブジェクトを作成し、次にstruct-mapまたはstructを使用してインスタンスを作成することによって作成されます。

(defstruct desilu :fred :ricky)
(def x (map (fn [n]
              (struct-map desilu
                :fred n
                :ricky 2
                :lucy 3
                :ethel 4))
             (range 100000)))
(def fred (accessor desilu :fred))
(reduce (fn [n y] (+ n (:fred y))) 0 x)
 -> 4999950000
(reduce (fn [n y] (+ n (fred y))) 0 x)
 -> 4999950000

StructMapの設定:create-struct defstruct accessor
個々の構造体を作成する:struct-map struct

ArrayMaps

コード形式操作を行う場合、キーの順序を維持するマップを持つことが望ましい場合があります。ArrayMapはそのようなマップであり、単にキー値キー値…の配列として実装されています。そのため、線形ルックアップパフォーマンスを持ち、非常に小さなマップにのみ適しています。これは完全なマップインターフェースを実装しています。array-map関数を使用して新しいArrayMapsを作成できます。ArrayMapは、「変更」されていない場合にのみソート順序を維持することに注意してください。後続のassocは最終的にハッシュマップになる原因となります。

セット

セットは、一意の値の集合です。

ハッシュセットのリテラルサポートがあります。

#{:a :b :c :d}
-> #{:d :a :b :c}

hash-set関数とsorted-set関数を使用してセットを作成できます。

(hash-set :a :b :c :d)
-> #{:d :a :b :c}

(sorted-set :a :b :c :d)
-> #{:a :b :c :d}

set関数を使用して、集合内の値のセットを取得することもできます。

(set [1 2 3 2 1 2 3])
-> #{1 2 3}

セットは集合です。

(def s #{:a :b :c :d})
(conj s :e)
-> #{:d :a :b :e :c}

(count s)
-> 4

(seq s)
-> (:d :a :b :c)

(= (conj s :e) #{:a :b :c :d :e})
-> true

セットは、disjによる「削除」をサポートし、contains?getもサポートします。後者は、キーと等しく比較されるセットに保持されているオブジェクトを(見つかった場合)返します。

(disj s :d)
-> #{:a :b :c}

(contains? s :b)
-> true

(get s :a)
-> :a

セットは、getを使用して、そのメンバーの関数です。

(s :b)
-> :b

(s :k)
-> nil

Clojureは、union / difference / intersectionなどの基本的な集合演算と、単なるマップの集合である「関係」に対する擬似関係代数サポート(select / index / rename / join)を提供します。