Clojureのデストラクチャリングは、シーケンシャルデストラクチャリングとアソシアティブデストラクチャリングの2つのカテゴリに分けられます。シーケンシャルデストラクチャリングは、シーケンシャルなデータ構造をletバインディング内のClojureベクトルとして表します。
このタイプのデストラクチャリングは、リスト、ベクトル、seqs、文字列、配列、そしてnth
をサポートするあらゆる種類の線形時間でトラバースできるデータ構造に使用できます。
(def my-vector [1 2 3])
(def my-list '(1 2 3))
(def my-string "abc")
;= It should come as no surprise that this will print out 1 2 3
(let [[x y z] my-vector]
(println x y z))
;= 1 2 3
;= We can also use a similar technique to destructure a list
(let [[x y z] my-list]
(println x y z))
;= 1 2 3
;= For strings, the elements are destructured by character.
(let [[x y z] my-string]
(println x y z)
(map type [x y z]))
;= a b c
;= (java.lang.Character java.lang.Character java.lang.Character)
シーケンシャルデストラクチャリングの鍵は、値をベクトル内のシンボルに1対1でバインドすることです。たとえば、ベクトル[x y z]
は、リスト'(1 2 3)
と各要素を1対1で照合します。
場合によっては、デストラクチャリングするコレクションのサイズが、デストラクチャリングバインディングのサイズと完全に一致しないことがあります。ベクトルのサイズが小さすぎる場合、余分なシンボルはnilにバインドされます。
(def small-list '(1 2 3))
(let [[a b c d e f g] small-list]
(println a b c d e f g))
;= 1 2 3 nil nil nil nil
一方、コレクションが大きすぎる場合、余分な値は単に無視されます。
(def large-list '(1 2 3 4 5 6 7 8 9 10))
(let [[a b c] large-list]
(println a b c))
;= 1 2 3
デストラクチャリングにより、バインドする(またはしない)要素と、それらをバインドする方法を完全に制御できます。
多くの場合、コレクションのすべての要素ではなく、特定の要素のみへのアクセスが必要です。
(def names ["Michael" "Amber" "Aaron" "Nick" "Earl" "Joe"])
最初の要素を1行に、残りを別の行に印刷したいとします。
(let [[item1 item2 item3 item4 item5 item6] names]
(println item1)
(println item2 item3 item4 item5 item6))
;= Michael
;= Amber Aaron Nick Earl Joe
このバインディングは機能しますが、デストラクチャリングを使用してもかなりぎこちないです。代わりに、&
を使用して、末尾の要素をシーケンスに結合できます。
(let [[item1 & remaining] names]
(println item1)
(apply println remaining))
;= Michael
;= Amber Aaron Nick Earl Joe
使用しないバインディングは、任意のシンボルにバインドすることで無視できます。
(let [[item1 _ item3 _ item5 _] names]
(println "Odd names:" item1 item3 item5))
;= Odd names: Michael Aaron Earl
このための慣例は、上記のようにアンダースコアを使用することです。
:as all
を使用して、ベクトル全体をシンボルall
にバインドできます。
(let [[item1 :as all] names]
(println "The first name from" all "is" item1))
;= The first name from [Michael Amber Aaron Nick Earl Joe] is Michael
少し休憩して、:as
と&
のタイプをもう少し詳しく見てみましょう。
(def numbers [1 2 3 4 5])
(let [[x & remaining :as all] numbers]
(apply prn [remaining all]))
;= (2 3 4 5) [1 2 3 4 5]
ここで、remaining
はnumbers
ベクトルの残りの要素を含むシーケンスにバインドされ、all
は元のvector
にバインドされています。文字列をデストラクチャリングするとどうなるでしょうか?
(def word "Clojure")
(let [[x & remaining :as all] word]
(apply prn [x remaining all]))
;= \C (\l \o \j \u \r \e) "Clojure"
ここでは、all
は元の構造(文字列、ベクトル、リストなど)にバインドされ、x
は文字\C
にバインドされ、remaining
は残りの文字のリストです。
これらのテクニックを任意に組み合わせて使用できます。
(def fruits ["apple" "orange" "strawberry" "peach" "pear" "lemon"])
(let [[item1 _ item3 & remaining :as all-fruits] fruits]
(println "The first and third fruits are" item1 "and" item3)
(println "These were taken from" all-fruits)
(println "The fruits after them are" remaining))
;= The first and third fruits are apple and strawberry
;= These were taken from [apple orange strawberry peach pear lemon]
;= The fruits after them are (peach pear lemon)
デストラクチャリングはネストすることもでき、任意のレベルのシーケンシャルな構造にアクセスできます。最初のベクトルmy-line
に戻りましょう。
(def my-line [[5 10] [10 20]])
このベクトルはネストされたベクトルで構成されており、直接アクセスできます。
(let [[[x1 y1][x2 y2]] my-line]
(println "Line from (" x1 "," y1 ") to (" x2 ", " y2 ")"))
;= "Line from ( 5 , 10 ) to ( 10 , 20 )"
ネストされたベクトルがある場合、任意のレベルで:as
または&
を使用することもできます。
(let [[[a b :as group1] [c d :as group2]] my-line]
(println a b group1)
(println c d group2))
;= 5 10 [5 10]
;= 10 20 [10 20]