読者です 読者をやめる 読者になる 読者になる

日曜プログラミング

休日趣味でやってるプログラミング関連記事をダラダラと

トップレベルでの let,letfn を使った private 化

Clojure でコード書いてる時、^:private な変数や関数で 行数が増えてくるとこれってどこで使ってたっけ?と思う事が増えてきた。

原始的な検索や、タグジャンプでまあ分からん事もないんだけど 行数が増えてくるとそれすらも億劫になってくる。

良く書くパターンとしては以下のようなもの。

(def ^:private local-map {:a 1 :b 2})

(defn a-val [] (:a local-map))
(defn b-val [] (:b local-map))

(defn- local-fn [arg] (some-proc arg))

(defn a-fn [arg] (-> (local-fn arg) some-a-proc))
(defn b-fn [arg] (-> (local-fn arg) some-b-proc))

^:private な下に書くようにすればまあそれなりに分かるんだけど、 パッと見の時は同一インデントの為すぐには区別つかなかったりする。

ある時、ふと Clojure の関数ってクロージャだから環境抱え込めるんだよな、ってのを 思い出した。て事は let や letfn の中に Public Var な関数、変数定義すりゃ少しは 整理付くんじゃなかろーかと思い立った。

先ほど例示した擬似コードを書き直すとこんな感じ。

(let [local-map {:a 1 :b 2}]
  (defn a-val [] (:a local-map))
  (defn b-val [] (:b local-map)))

(letfn [(local-fn [arg] (some-proc arg))]
  (defn a-fn [arg] (-> (local-fn arg) some-a-proc))
  (defn b-fn [arg] (-> (local-fn arg) some-b-proc)))

どうだろう。どちらの例も local-maplocal-fn^:private な事は 変わりないが、letletfn で包まれる事で最初の例よりも関連付けが明確に なってるんじゃなかろうか。

ns やファイルを分ける程の規模ではないが、共通で使われるのがせいぜい 2,3 箇所くらいな ローカルを定義したいと言う状況では有効だと思う。もちろん ns 全体であちこちで local として使いたい場合は素直に ^:private 付与したシンボルでいいと思う。

ただ、この手法は擬似コード例で言う local-map, local-fn に docstring を 入れられなくなるという明確なデメリットもあるので結局お好みで選ぶことになるのかなやっぱり。

ちなみにこれはオリジナルな話でも何でもなく、xyzzyスクリプトを書いていた頃 こんな書き方そういやあったなと言う記憶が蘇ったもの。