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で使いこなせるとモテるとか誰かが言ってた.

📝Clojure Tips

✅ネストした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になる.

🔗 Rerefences