Clojureの destructuring (分配束縛)の書き方。
setなどのマイナーな記法もできるだけ網羅。
サンプルコードはletだが、関数の引数など分配束縛が有効なフォームではどこでも同じ。
用語
- 
destructuringの訳としてここでは分配束縛を使います。 - 
sequential: Clojureのシーケンス抽象可能な構造全般に対してこう呼びます。 - 
associative: mapのような構造全般に対してこう呼びます。一般的にはassociative arrayを連想配列と訳すようです。 
参考
- Clojure - Destructuring in Clojure
 - GPソフト Wiki - ClojureのDestructuring(分配束縛)
 - Clojure Destructuring Tutorial and Cheat Sheet
 - The complete guide to Clojure destructuring.
 
1. 基本
(1) Sequential Destructuring シーケンシャルなデータ構造に対する分配束縛
vectorやlistなどのsequentialな構造に対しては、vectorを使って分配を表現します。
; vector
(let [[a b c d] [10 20 30 40]]
  (println a b c d))
;=> 10 20 30 40
; list
(let [[a b c d] '(10 20 30 40)]
  (println a b c d))
;=> 10 20 30 40
文字列にも使用可能。
; string
(let [[a b c d] "ABCD"]
  (prn a b c d)         ;=> \A \B \C \D
  (println (class a)))  ;=> java.lang.Character
変数名が重複すると後勝ち
(let [[a b c c] [10 20 30 40]]
  (println c))
;=> 40
余った要素は単に無視される
(let [[a b] [10 20 30 40]]
  (println a b))
;=> 10 20
該当する要素がないとnil
(let [[a b c d] [10 20 30]]
  (println d))
;=> nil
ネストした構造も可
(let [[a [b1 b2] c] [10 [21 22] 30]]
  (println b1 b2))
;=> 21 22
(let [[a b c] [10 [21 22] 30]]
  (println b))
;=> [21 22]
setはsequentialではない(順序がない)のでエラー 1
(let [[a b c d] #{10 20 30 40}]
  (println a b c))
;=> UnsupportedOperationException nth not supported on this type: PersistentHashSet  clojure.lang.RT.nthFrom (RT.java:947)
不要な要素をスキップする_
1番目と3番目の要素は要らなくて、2番めと4番目の要素だけ欲しい
(let [[_ b _ d] [10 20 30 40]]
  (println b d))
;=> 20 40
ちなみに、_に何か特別な機能があるわけではないです。_も単なる変数です。2
使用しない変数名に_を使う慣例があるだけで、値は_にきちんと束縛されています。
(let [[_ b _ d] [10 20 30 40]]
  (println _))
;=> 30
元の構造全てを束縛する:as
(let [[a b c d :as all] [10 20 30 40]]
  (println all))
;=> [10 20 30 40]
残り全てを束縛する&
(let [[a & rest] [10 20 30 40]]
  (println rest))
;=> (20 30 40)
&は、残りが1つしかなくてももちろんシーケンスで返る
(let [[a b c & rest] [10 20 30 40]]
  (println rest))
;=> (40)
&は、残りが1つもないとnilが返る。(空のシーケンス () ではない。)
(let [[a b c d & rest] [10 20 30 40]]
  (println rest))
;=> nil
ちなみに、:asは元のデータ型そのままで束縛されるのに対し、
&は元のシーケンスの型と同じになるとは限りません。
上記のコードで言えば、
:asで束縛されるのは、元のvectorと同じPersistentVectorで、
&で束縛されるのはPersistentVector$ChunkedSeqです。
(let [[& rest :as all] [10 20 30 40]]
  (println rest (class rest))  ;=> (10 20 30 40) clojure.lang.PersistentVector$ChunkedSeq
  (println all  (class all)))  ;=> [10 20 30 40] clojure.lang.PersistentVector
文字列だと
(let [[& rest :as all] "ABCDEF"]
  (println rest (class rest))  ;=> (A B C D E F) clojure.lang.StringSeq
  (println all  (class all)))  ;=> ABCDEF java.lang.String
全部使ってみる
:asや&は、ネストした構造のどのレベルでも使用可能
(let [[a [b1 & b_rest :as b_all] _ _ & rest :as all] [10 '(21 22 23) 30 40 50]]
  (println a)
  (println b1)
  (println b_rest)
  (println b_all)
  (println rest)
  (println all))
;=> 10
;=> 21
;=> (22 23)
;=> (21 22 23)
;=> (50)
;=> [10 (21 22 23) 30 40 50]
(2) Associative destructuring アソシエーティブなデータ構造に対する分配束縛
associativeな構造に対しては、mapを使って分配を表現します。
; Keyword key
(let [{a :a b :b} {:a 10 :b 20}]
  (println a b))
;=> 10 20
; String key
(let [{a "a" b "b"} {"a" 10 "b" 20}]
  (println a b))
;=> 10 20
; Symbol key
(let [{a 'a b 'b} {'a 10 'b 20}]
  (println a b))
;=> 10 20
キーと同名の変数に値を束縛する:keys, :strs, :symsを使えば、もっと簡潔に書くことができます。
- 
:keys: キーが、キーワードのとき(Keywords) - 
:strs: キーが、文字列のとき(Strings) - 
:syms: キーが、シンボルのとき(Symbols) 
これらを使って、上記のコードを書き直すと次のようになります。 3
; Keyword key
(let [{:keys [a b]} {:a 10 :b 20}]
  (println a b))
;=> 10 20
; String key
(let [{:strs [a b]} {"a" 10 "b" 20}]
  (println a b))
;=> 10 20
; Symbol key
(let [{:syms [a b]} {'a 10 'b 20}]
  (println a b))
;=> 10 20
ネストした構造も可
(let [{a :a {b1 :b1 b2 :b2} :b c :c} {:a 10 :b {:b1 21 :b2 22} :c 30}]
  (println a b1 b2 c))
;=> 10 21 22 30
該当のキーが存在しないとnil
(let [{a :a b :b c :c} {:a 10 :b 20}]
  (println a b c))
;=> 10 20 nil
(let [{:keys [a b c]} {:a 10 :b 20}]
  (println a b c))
;=> 10 20 nil
元の構造全てを束縛する:as
(let [{:keys [a b c] :as all} {:a 10 :b 20}]
  (println all))
;=> {:a 10, :b 20}
該当のキーが存在しない場合のデフォルト値を指定する:or
(let [{:keys [a b c] :or {a 100 b 200 c 300}} {:a 10 :b 20}]
  (println a b c))
;=> 10 20 300
自分は、:orに渡すmapのキーをキーワードで書いてしまう間違いをしてしまったことが何度かあります。
こんな↓バグです。
(let [{:keys [a b c] :or {:a 100 :b 200 :c 300}} {:a 10 :b 20}]
  (println a b c))
;=> 10 20 nil
- alephの作者の人もミスってたので、結構みんなよくやるミスなのかもしれないです。
Fix :or map - keys are bound symbols, not lookup keys · ztellman/aleph@7d6f2f5 
2. 応用
(1) setの分配束縛
setの分配束縛は、キーとバリューが同一なmapのように扱います。
; Keyword
(let [{:keys [spring summer autumn winter] :or {winter :none} :as all} #{:spring :summer :fall}]
  (prn spring summer autumn winter)  ;=> :spring :summer nil :none
  (prn all))                         ;=> #{:fall :spring :summer}
; String
(let [{:strs [spring summer autumn winter] :or {winter :none} :as all} #{"spring" "summer" "fall"}]
  (prn spring summer autumn winter)  ;=> "spring" "summer" nil :none
  (prn all))                         ;=> #{"summer" "fall" "spring"}
; Symbol
(let [{:syms [spring summer autumn winter] :or {winter :none} :as all} #{'spring 'summer 'fall}]
  (prn spring summer autumn winter)  ;=> spring summer nil :none
  (prn all))                         ;=> #{summer fall spring}
イメージ(※あくまで挙動のイメージで、実装を表しているわけではありません)。
#{:spring :summer :fall}
 ; ↓
{:spring :spring, :summer :summer, :fall :fall}
この記法は、関数の引数で一連のフラグを受け取るような際に便利だと、こちらには書かれています。
(2) mapを使ってシーケンスを分配束縛
実はvectorではなくmapで、シーケンスを分配束縛することもできます。
(a) シーケンスのインデックスをキーとしてmapで束縛
シーケンスのインデックス(0,1,2,3,...)がassociativeな構造のキーとして扱われます。
(let [{a 0 d 3 e 4} [10 20 30 40]]
  (println a d e))
;=> 10 40 nil
(let [{c 2 :as all} "ABCDE"]
  (prn c)     ;=> \C
  (prn all))  ;=> "ABCDE"
イメージ(※あくまで挙動のイメージで、実装を表しているわけではありません)。
"ABCDE"
 ; ↓
(\A \B \C \D \E)
 ; ↓
{0 \A, 1 \B, 2 \C, 3 \D, 4 \E}
 ;           ^^^^^
 ;           c に束縛
この記法を使っているのは、あまり見たことがありません。
(b) associativeな構造のシーケンスを&を使ってmapで束縛
associativeな構造のシーケンスは、&を使えば、それを活かして分配束縛できます。
associativeな構造のシーケンスとは、要素数が偶数で奇数番目の要素がキー、偶数番目の要素がバリューにそれぞれ相当するような構造のシーケンスのことです。
; associative
(let [[& {a :a b :b c :c}] [:a 10 :b 20]]
  (println a b c))
;=> 10 20 nil
:keys, :strs, :symsを使うことも可能。
(let [[& {:keys [a b c]}] [:a 10 :b 20]]
  (println a b c))
そもそも、対象のデータ型全体がassociativeなlistなら&なんか使用しなくてもいけたりします。
vectorは駄目です。なぜだろう・・・
; associativeに分配束縛できる
(let [{:keys [a b]} '(:a 10 :b 20)]
  (println a b))
;=> 10 20
; listではなくvectorなので、associativeに分配束縛はできない
(let [{:keys [a b]} [:a 10 :b 20]]
  (println a b))
;=> nil nil
もちろん&は、「残り全て」を扱います。
(let [[m _ & {a :a b :b c :c}] [{:foo "bar"} "baz" :a 10 :b 20]]
  (println m)       ;=> {:foo bar}
  (println a b c))  ;=> 10 20 nil
(let [[m _ & {:keys [a b c]}] [{:foo "bar"} "baz" :a 10 :b 20]]
  (println m)       ;=> {:foo bar}
  (println a b c))  ;=> 10 20 nil
ちなみに、対象のシーケンスが奇数だとエラーになります。
(let [[& {:keys [a b c]}] [:a 10 :b]])
;=> IllegalArgumentException No value supplied for key: :b  clojure.lang.PersistentHashMap.create (PersistentHashMap.java:77)
この記法は、関数の引数でオプションをmapで受け取るような時に結構使うんじゃないかと思います。
(defn write-wonderfully [name & {:keys [new append encoding quietly]}]
  (prn new append encoding quietly)
  (when verbose (comment ....))
  ; ....
  )
(write-wonderfully "foo.txt" :encoding "utf-8" :new true)
Rubyでも似たようなシグネチャはよく見かけます。
def write_wonderfully(name, *opts)
  p opts
  p Hash[*opts]
end
write_wonderfully("foo.txt", :encoding, "utf-8", :new, true)
(3) 複雑な構造
ネストした複雑な構造も分配束縛が可能です。
(def members
  [{:name     "john"
    :birthday (-> "yyyy-MM-dd" java.text.SimpleDateFormat. (.parse "1999-10-01"))
    :hobbies  [:running :reading :travel]}
   {:name     "pete"
    :birthday (-> "yyyy-MM-dd" java.text.SimpleDateFormat. (.parse "1997-04-03"))
    :hobbies  [:reading]}
   {:name     "mark"
    :birthday (-> "yyyy-MM-dd" java.text.SimpleDateFormat. (.parse "2001-12-30"))
    :hobbies  [:shopping :programming :travel :swimming]}])
(let [[{[initial :as whole_name] :name hobbies :hobbies} _ {:keys [name birthday]}] members
      fmt (java.text.SimpleDateFormat. "yyyy年MM月dd日(EE)")]
  (println "1人目のメンバーのイニシャルは" initial "です。")
  (println "1人目のメンバーの名前は" whole_name "です。")
  (println "1人目のメンバーの名前は" (clojure.string/join " と " hobbies) "です。")
  (println "3人目のメンバーの名前は" name "で、誕生日は" (.format fmt birthday) "です。"))
;=> 1人目のメンバーのイニシャルは j です。
;=> 1人目のメンバーの名前は john です。
;=> 1人目のメンバーの名前は :running と :reading と :travel です。
;=> 3人目のメンバーの名前は mark で、誕生日は 2001年12月30日(日) です。
もっと良い例をください。