Clojureの分配束縛まとめ


Clojureの **destructuring** （分配束縛）の書き方。

`set`などのマイナーな記法もできるだけ網羅。
サンプルコードは`let`だが、関数の引数など分配束縛が有効なフォームではどこでも同じ。

# 用語

* `destructuring`の訳としてここでは**分配束縛**を使います。
* `sequential`: Clojureのシーケンス抽象可能な構造全般に対してこう呼びます。
* `associative`: mapのような構造全般に対してこう呼びます。一般的には`associative array`を連想配列と訳すようです。

# 参考

* [Clojure - Destructuring in Clojure](http://clojure.org/guides/destructuring)
* [GPソフト Wiki - ClojureのDestructuring(分配束縛)](http://gpsoft.dip.jp/hiki/?Clojure%E3%81%AEDestructuring(%E5%88%86%E9%85%8D%E6%9D%9F%E7%B8%9B))
* [Clojure Destructuring Tutorial and Cheat Sheet](https://gist.github.com/john2x/e1dca953548bfdfb9844)
* [The complete guide to Clojure destructuring.](http://blog.brunobonacci.com/2014/11/16/clojure-complete-guide-to-destructuring/)

# 1. 基本

## (1) Sequential Destructuring シーケンシャルなデータ構造に対する分配束縛

vectorやlistなどのsequentialな構造に対しては、**vector**を使って分配を表現します。  


```clojure
; 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
```

文字列にも使用可能。

```clojure
; string
(let [[a b c d] "ABCD"]
  (prn a b c d)         ;=> \A \B \C \D
  (println (class a)))  ;=> java.lang.Character
```

変数名が重複すると後勝ち

```clojure
(let [[a b c c] [10 20 30 40]]
  (println c))
;=> 40
```

余った要素は単に無視される

```clojure
(let [[a b] [10 20 30 40]]
  (println a b))
;=> 10 20
```

該当する要素がないと`nil`

```clojure
(let [[a b c d] [10 20 30]]
  (println d))
;=> nil
```

ネストした構造も可

```clojure
(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]

[^1]: mapで分配束縛する方法はあります。後述。

```clojure
(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番目と３番目の要素は要らなくて、2番めと4番目の要素だけ欲しい

```clojure
(let [[_ b _ d] [10 20 30 40]]
  (println b d))
;=> 20 40
```

ちなみに、`_`に何か特別な機能があるわけではないです。`_`も単なる変数です。[^2]
使用しない変数名に`_`を使う慣例があるだけで、値は`_`にきちんと束縛されています。

[^2]: IDEやeditorによっては、`_`や`_`から始まる変数名を、使用する意図のない変数として解釈してくれるものがあるようです。例えば、IntelliJ IDEA (Cursive) では、`_`の変数が未使用でも警告が表示されません。

```clojure
(let [[_ b _ d] [10 20 30 40]]
  (println _))
;=> 30
```

### 元の構造全てを束縛する`:as`

```clojure
(let [[a b c d :as all] [10 20 30 40]]
  (println all))
;=> [10 20 30 40]
```

### 残り全てを束縛する`&`

```clojure
(let [[a & rest] [10 20 30 40]]
  (println rest))
;=> (20 30 40)
```

`&`は、残りが1つしかなくてももちろんシーケンスで返る

```clojure
(let [[a b c & rest] [10 20 30 40]]
  (println rest))
;=> (40)
```

`&`は、残りが1つもないと`nil`が返る。（空のシーケンス `()` ではない。）

```clojure
(let [[a b c d & rest] [10 20 30 40]]
  (println rest))
;=> nil
```

ちなみに、`:as`は元のデータ型そのままで束縛されるのに対し、
`&`は元のシーケンスの型と同じになるとは限りません。


上記のコードで言えば、  
`:as`で束縛されるのは、元のvectorと同じ`PersistentVector`で、  
`&`で束縛されるのは`PersistentVector$ChunkedSeq`です。

```clojure
(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
```

文字列だと

```clojure
(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`や`&`は、ネストした構造のどのレベルでも使用可能

```clojure
(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**を使って分配を表現します。  

```clojure
; 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`を使えば、もっと簡潔に書くことができます。

1. `:keys`: キーが、キーワードのとき（`Keywords`）
2. `:strs`: キーが、文字列のとき（`Strings`）
3. `:syms`: キーが、シンボルのとき（`Symbols`）

これらを使って、上記のコードを書き直すと次のようになります。 [^3]

[^3]: 簡潔になった感があまりないですが、束縛する変数が多くなったり、構造がネストしていたりして複雑になると恩恵が大きくなります。

```clojure
; 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
```

ネストした構造も可

```clojure
(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`

```clojure
(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`

```clojure
(let [{:keys [a b c] :as all} {:a 10 :b 20}]
  (println all))
;=> {:a 10, :b 20}
```

### 該当のキーが存在しない場合のデフォルト値を指定する`:or`

```clojure
(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のキーをキーワードで書いてしまう間違いをしてしまったことが何度かあります。
こんな↓バグです。

```clojure
(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](https://github.com/ztellman/aleph/commit/7d6f2f5bf743301c5a4ab0705725bf9b50b91896)



# 2. 応用


### (1) setの分配束縛

**set**の分配束縛は、キーとバリューが同一なmapのように扱います。

```clojure
; 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}
```

イメージ（※あくまで挙動のイメージで、実装を表しているわけではありません）。

```clojure
#{:spring :summer :fall}
 ; ↓
{:spring :spring, :summer :summer, :fall :fall}
```

この記法は、関数の引数で一連のフラグを受け取るような際に便利だと、[こちら](http://blog.brunobonacci.com/2014/11/16/clojure-complete-guide-to-destructuring/#sets-destructuring)には書かれています。

## (2) mapを使ってシーケンスを分配束縛

実は**vector**ではなく**map**で、シーケンスを分配束縛することもできます。

### (a) シーケンスのインデックスをキーとしてmapで束縛

シーケンスのインデックス（0,1,2,3,...）がassociativeな構造のキーとして扱われます。

```clojure
(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"
```

イメージ（※あくまで挙動のイメージで、実装を表しているわけではありません）。

```clojure
"ABCDE"
 ; ↓
(\A \B \C \D \E)
 ; ↓
{0 \A, 1 \B, 2 \C, 3 \D, 4 \E}
 ;           ^^^^^
 ;           c に束縛
```

この記法を使っているのは、あまり見たことがありません。

### (b) associativeな構造のシーケンスを`&`を使ってmapで束縛

associativeな構造のシーケンスは、`&`を使えば、それを活かして分配束縛できます。
associativeな構造のシーケンスとは、要素数が偶数で奇数番目の要素がキー、偶数番目の要素がバリューにそれぞれ相当するような構造のシーケンスのことです。

```clojure
; associative
(let [[& {a :a b :b c :c}] [:a 10 :b 20]]
  (println a b c))
;=> 10 20 nil
```

`:keys`, `:strs`, `:syms`を使うことも可能。

```clojure
(let [[& {:keys [a b c]}] [:a 10 :b 20]]
  (println a b c))
```

そもそも、対象のデータ型全体がassociativeな**list**なら`&`なんか使用しなくてもいけたりします。
vectorは駄目です。なぜだろう・・・

```clojure
; 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
```

もちろん`&`は、「残り全て」を扱います。

```clojure
(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
```

ちなみに、対象のシーケンスが奇数だとエラーになります。

```clojure
(let [[& {:keys [a b c]}] [:a 10 :b]])
;=> IllegalArgumentException No value supplied for key: :b  clojure.lang.PersistentHashMap.create (PersistentHashMap.java:77)
```

この記法は、関数の引数でオプションをmapで受け取るような時に結構使うんじゃないかと思います。

```clojure
(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でも似たようなシグネチャはよく見かけます。

```ruby
def write_wonderfully(name, *opts)
  p opts
  p Hash[*opts]
end

write_wonderfully("foo.txt", :encoding, "utf-8", :new, true)
```

## (3) 複雑な構造

ネストした複雑な構造も分配束縛が可能です。

```clojure
(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 "１人目のメンバーのイニシャルは" initial "です。")
  (println "１人目のメンバーの名前は" whole_name "です。")
  (println "１人目のメンバーの名前は" (clojure.string/join " と " hobbies) "です。")
  (println "３人目のメンバーの名前は" name "で、誕生日は" (.format fmt birthday) "です。"))

;=> １人目のメンバーのイニシャルは j です。
;=> １人目のメンバーの名前は john です。
;=> １人目のメンバーの名前は :running と :reading と :travel です。
;=> ３人目のメンバーの名前は mark で、誕生日は 2001年12月30日(日) です。
```

もっと良い例をください。