Clojure分配束縛 Overview
Clojureでは分配束縛をサポートしている.
- シーケンシャルとアソシエーティブなデータ構造の2つの種類がある.
- Sequencial Destructuring: Vector構造を分解
- Associative Destructuring: Map構造を分解
- vectorの中にvectorやmapを書いて表現する.
- [[][]]
- [{}{}]
- 分配束縛したくない変数はvectorなら:as, mapなら:or で表現する.
- :keysをつかうと,mapをうけとったらその値をシンボルにバインドできる.
Clojure Basic Techniques
一時変数(let)を分解するために分配束縛をつかう
通常は let の中で使われる.
(def my-vector [:a :b :c :d])
(let [[a b c d] my-vector]
(println a b c d))
;; => :a :b :c :d
関数の引数で分配束縛をつかう
通常は [[][]]や[{}{}]のように書いて右から左にparseするが, 関数の引数として[[][]]を省略して[[]]とかける.
通常のbinding.
(defn foo [a b]
(println a b))
(defn foo [a b & {:keys [x y]}]
(println a b x y))
(foo "A" "B" :x "X" :y "Y") ;; => A B X Y
余分なものはひとつにまとめるbinding.
(defn foo [a b & args]
(println a b args))
(foo :a :b :x :y :z) ;; => :a :b (:x :y :z)
(defn foo [& {:as m}]
(println m))
(foo :x "X" :y "Y") ;; => {:y Y, :x X}
keysで必要なものだけ取りつつ残りも取るよくばりパターン.
(defn foo [a b & {:keys [x y] :as m}]
(println a b x y m))
(foo "A" "B" :x "X" :y "Y")
;; => A B X Y {:y Y, :x X}
この記法(keyword引数にmapを指定)はClojure 1.11からのサポートなのかな?
Clojure - Keyword argument functions now also accept maps
この書き方はつかえそう.
(defn some-handler [{:keys [db,,,,] :as req}]
,,)
奥が深い…
see also: 関数の引数にデフォルト値を指定するには?
Mapのkeyword以外のバラし方(:strs/:syms)
:keysがあまりにも登場機会がおおい気がするが, 分配束縛はkeyword以外にも文字列やシンボルで利用可能.
文字列は :strs, シンボルは :syms でそれぞれ分解する.
✅Clojure Destructureing Tips
分配束縛のテクニックをClojureで使いこなせるとモテるとか誰かが言ってた.
✅ネストしたMapを分配束縛でバラせるか?
殺れます!
(def my-nested-hashmap {:a "A" :b "B" :c "C" :d "D"
:q {:x "X" :y "Y" :z "Z"}})
(let [{:keys [a b] {:keys [x y]} :q} my-nested-hashmap]
(println a b x y))
しかし若干の読みにくさがあるので無理ないほうがいいかも.
✅関数の引数にデフォルト値を指定するには?
いわゆるデフォルト引数というものだが, 位置引数に値を設定する方法は見当たらない(見つけられてないだけかも).
代わりにオプション引数と キーワード引数 の文法の組み合わせでできる.
分配束縛(Destructuring) の or を活用する.
(defn myfunc
[arg & {:keys [opt1 opt2] :or {opt1 "default1" opt2 "default2"}}]
(format "arg=[%s] opt1=[%s] opt2=[%s]" arg opt1 opt2))
(myfunc "argument" {:opt1 "option1"})
;; => "arg=[argument] opt1=[option1] opt2=[default2]"
def inside let: 分配束縛でバラした値をbind
面白いletとdefの組み合わせなのでメモ.
ref. https://github.com/ptaoussanis/sente
(let [{:keys [chsk ch-recv send-fn state]}
(sente/make-channel-socket-client!
"/chsk" ; Note the same path as before
?csrf-token
{:type :auto ; e/o #{:auto :ajax :ws}
})]
(def chsk chsk)
(def ch-chsk ch-recv) ; ChannelSocket's receive channel
(def chsk-send! send-fn) ; ChannelSocket's send API fn
(def chsk-state state) ; Watchable, read-only atom
)
ここでやっていることは, make-channel-socket-client!の戻り値のMapを分配束縛でバラした上で, defを使ってnamespaceにbindしている. letはレキシカルスコープだが, defのスコープはnamespaceになる.