背景
プログラミング言語clojureで「namespaceで定義した変数」と「関数でキーワードで受け取る引数」の競合が発生してnamespace側の変数の呼び方が分からなくてしばらく困りました。いくつかの解決方法が分かったので、関連情報と共に備忘録として記事に残します。
動作確認した環境
clojure 1.11.1shadow-cljs 2.28.20
問題が発生する記述
下記の記述だとファイル冒頭で定義したsome-valが関数内で呼べません。(ns some.file)
(def some-val 5)
(defn soem-fn [{:keys [some-val]}]
(println (if (nil? some-val) some-val some-val)))
解決方法1: @#'を使う
@#'を使うことで、他のプログラミング言語c++ の :: のように関数内の変数ではなくnamespaceで定義した変数を呼べます。(def some-val 5)
(defn soem-fn [{:keys [some-val]}]
(println (if (nil? some-val) some-val @#'some-val)))
@はderef、#'はvarの略記で、 @#'some-val は (deref (var some-val) とも書けます。
varでsome-valという名前のポインタを作り、derefでその名前で参照できる値を取得しているようです。
athosさんに教えていただきました。
一応、 @#'name-table のように書くとローカル束縛のシャドーイングの影響を受けずにトップレベルのvarを参照できますが、ちょっとトリッキーですね
— athos)))))))) (@athos0220) March 8, 2025
あんまり分かりやすい解説ページはないですかねー。
— athos)))))))) (@athos0220) March 9, 2025
@#'foo というのは (deref (var foo)) の略記で、 #'foo または (var foo) というのは誤解を恐れずにいうとfooという名前自体へのポインタをとるような操作です。derefによってそのポインタを辿ってfooという名前で参照される値を取得できます。
@#'で呼び出す変数が無ければclojure-cliもshadow-cljsも静的解析ツールのclj-kondo教えてくれたので、@#'を使うにあたって特別な注意は必要無さそうでした。
参考
「clojure @#'」で検索して見つかったサイトです。
1行ずつ説明があります。
Clojureスタイルガイド
Contrib How-To
解決方法2: ファイルのnamespace名を付けて変数を呼ぶ
他のファイルで呼び出す場合と同様にnamespaceと/を組み合わせて呼べば関数内変数と名称が被らず呼び出せます。(ns some.file)
(def some-val 5)
(defn soem-fn [{:keys [some-val]}]
(println (if (nil? some-val) some-val some.file/some-val)))
解決方法3: 名前が被らないprivate変数を定義して呼ぶ
別の名前で定義した変数を通せば所望の値を呼べます。(ns some.file)
(def some-val 5)
(def ^:private this-some-val some-val)
(defn soem-fn [{:keys [some-val]}]
(println (if (nil? some-val) some-val this-some-val)))
おわり
@#'を使えばプログラミング言語c++の :: のようにnamespace内の要素を呼び出せると分かりました。自力でその記述に辿り着くのは困難だったので、情報共有がありがたかったです。
参考
情報共有があったツイートです。問題提起
https://x.com/asukiaaa/status/1898341742900092976
情報共有
https://x.com/athos0220/status/1898385131708228093
解説
https://x.com/athos0220/status/1898622930688393475
@#'の解説が1行だけあるサイトです。
Clojureスタイルガイド
Contrib How-To
@#'の別表記です。
deref
var
0 件のコメント :
コメントを投稿