📝Clojure:名前空間と変数束縛(Bindings and Namespaces)

📝Clojure:名前空間と変数束縛(Bindings and Namespaces)

Clojureでは,変数の名前空間と束縛の関係は以下のようになる.

ref: 📂Clojure Core

Clojure: 名前空間(Namespaces) #

Clojure の名前空間を namespaceという. ns で宣言する. \*ns\* で参照する.

ある名前空間から別の名前空間を参照するには requireをつかう.

  • require のみ 省略なしの表記でアクセスできる(namespace/symbols)
  • require :as 省略した表記でアクセスできる(省略namesapce/symbols)
  • require :refer :all 名前空間を書かない表記でシンボルにアクセスできる(symbols)

REPL上での名前空間の移動まとめ #

現在の名前空間を確認するには \*ns\*, 既存の名前空間に移動する in-ns をつかう.

名前空間を新規作成して移動する ns はREPLというよりもソースコードで利用する.


ref. Clojure - Programming at the REPL: Navigating Namespaces

Clojure: 名前空間のスタイルガイド #

🏷Clojure Style Guide

Clojureスタイルガイド より名前空間に関わる部分を抜き出し

  • 名前空間は1ファイルに1つ.
  • 名前空間の命名規約はkebab-case(lisp-case).
  • 深い名前空間のセグメントは悪い(せいぜい5つまで).

また. library-name.coreみたいなのはLeiningen Projectの慣習.

名前空間のフォーマットについて.

  • refer, require, importの順に並べる.
  • 適切な改行.
  • requireとimportを整理して並べる.
  • Idiomatic な名前空間の名前をつかう.

これらは人間が意識するべきではないのでツールに任せたいところだ. Emacs CIDERなら cljr-clean-ns or clojure-sort-ns.

see also. 🤔名前空間やファイル名は単数か複数か問題

howto: namespaceにbindingsされたシンボルを確認するには? #

ns-xxx という関数で調べる.

  • ns-map
  • ns-publics
  • ns-interns
  • ns-refers
  • ns-imports

たとえば, (ns-map ‘my-space)でmy-spaceにbindingsされているvarをすべて返す.

ns-mapは全て. ns-publicsはpublicなvars. ns-internsはrequireやreferなどを除くそのnamespaceで定義されたもの.

しばしば, (keys (ns-interns ‘my-space’))など, keys methodと連携させる.

M-x cider-browse-nsでEmacs Bufferで閲覧できる.

tips: すでに他の名前空間に定義されている関数を読み込まない #

refer-clojure :exclude をつかう.

(ns hoge
  :refer-clojure :exclude [get set!])

Clojure: Scope #

Clojureはデフォルトで静的スコープを採用している.

🏷Scope

bindings macro | ClojureでのDynamic Scope #

動的スコープ. Emacsなら常識か.

ClojureのDynamic Scopeは bindings マクロで定義する.名前空間を超えて, 同一スレッド内なら値(var)が参照可能.

Lispの慣例として, アスタリスク(*)でsymbolを囲む.

そういう意味だと, スレッドローカルといえるかも. スレッドをまたぐ方法はまた別にある? (未調査, keyword: “binding conveyance”).

refs: Clojure - Vars and the Global Environment

Clojure: 変数束縛(Bindings) #

  • 変数の名前空間への束縛をdef
    • list formでbindingする.
    • ex.) (def developer “Alice”)
    • 無名関数はfn
  • 変数の一時的な束縛を let
    • vector formでbindingする.
    • ex.) (let [developer “Alice in wonderland”])
  • 関数の定義はdefn
    • これはdefのシンタックスシュガーでもある.
  • 無名関数はfn
    • #() でも表現可能.

🏷bindings

Clojure: defとvarとsymbol #

Clojure: clojure.lang.Var #

  • clojure.lang.Varはデータそもものを表すオブジェクト.
  • 可変(mutable).
  • varのリーダマクロは, #'

varにはMetaDataを設定できる. よく見かける代表的なのは,

  • :dynamic
    • 同一スレッド内で再定義可能.
    • (def ^:dynamic foo 1)
  • :private
    • namespaceを超えて参照不可.
    • (def ^:private bar 2)

ref. Clojure: MetaData

Clojure: clojure.lang.Symbol #

  • clojure.lang.Symbolはデータを指し示す参照のオブジェクト.
  • symbolのリーダマクロは, ' , もしくは quoteで読み出し.

cf. C言語の関数と関数ポイントがvarとsymbolの関係.

💡 def macro / alter-var-rootによるvarの再定義 #

def マクロは, clojure.lang.Varオブジェクトを生成する.

user> (def x)
;; => #'user/x
user> x
;; => #object[clojure.lang.Var$Unbound 0x7d08131d "Unbound: #'user/x"]

defによる変数束縛は内部で複雑なことをしている.

user=> (def foo "hoge")
#'user/foo

defがマクロであるとは,

  1. “hoge"オブジェクトがメモリ上に確保される.(実際の値を0xhogohogehogeとしよう).
  2. Varオブジェクトがメモリ上に確保される(0xvarvarvaaaaaaaaaaa).
  3. varオブジェクトを"hoge"にbind(0xvarvarvaaaaaaaaaaa -> 0xhogohogehoge)する関係を作成.(0xbindbinddddddddddd)
  4. fooというsymbolオブジェクトを作成(0xsymmmmmmmmmm)
  5. 3のbindingをsymbolにintern(0xsymmmmmmmm -> 0xbindbinddddddd)という関係を作成してnamespaceにbind.

varはmutableでありsymbolはimmutable.

いわゆるdefによってimmutableな値を宣言するとは, 参照元がimmutableであるということを言っている.

defによってvarが再定義可能ということは, defはsymbolを新しいvarを生成してbindingしている.

一方alter-var-root関数をつかうと, mutableであるvarをそのまま変更する.

Clojure Style Guide では varの変更にalter-var-root推奨している.

https://totakke.github.io/clojure-style-guide/#alter-var

しかしそもそも論として, defやalter-var-rootによるvarの再定義は REPLやreloadのためのもの,いわば開発用のもの. 普通にプログラミングをしているときにこれが必要となったら何かがおかしい. 別の代替方法を検討したほうがいい.

Clojure: intern #

defと似た概念でintern(拘禁)というものがある.

def と Symbol と Var の話 - (-> % read write unlearn)

defでbindしたsymbol-varの対応関係をnamespaceに登録する.

Clojure: defonce #

defもdefonceも名前空間に変数束縛をするが, すでにvarが存在する場合, defは上書き, defonceはスキップする.

言い換えると, var-symbolのpairをnamespaceに登録するときに, defonceはすでにvar-symbolのpairがあったらなにもしない.

ref: What is the difference between def and defonce in Clojure? - Stack Overflow

defonceとatomとhotreload #

defonceはhotreloadの文脈でatomと合わせて登場することが多い.

再起動のときにメモリをたくさん使ったり外部通信して時間がかかったりするときに, いちいちインスタンスを再作成してるとボトルネックになるのでdefonceをつかう.

ただしreplの再起動でよく利用される tools.namespace.replのrefresh関数は, たとえdefonceで定義されていたとしても初期化するので注意.

ref: https://github.com/clojure/tools.namespace#reloading-code-preparing-your-application

たとえばM-x cider-eval-bufferをうっかりEmacsで実行してバッファまるごと読み込み直しても, defonceで宣言しているならばその変数束縛はスキップされる.

まあこのあたりのhotreloadによるDX改善は自分で考えるよりもベストプラクティスを真似るのがいいのかも.

ref: Reloaded Workflow

References #

✅ Active Recalls #

Clojure の 無名関数のシンタックスはなんですか? #

fn or #()

Clojureの名前空間の宣言方法と参照方法はなんですか? #

ns と \*ns*

Clojureで名前空間を参照するためのシンタックスはなんですか? #

require

Clojure nsの属性で :requireと:importの違いはなんですか? #

どちらもnamespaceに他のnamespaceで定義された宣言を取り込むが, requireはclojureのライブラリ, importはJavaのクラスで利用する.

Clojureでdefとdefonceの違いはなんですか? #

名前空間に変数束縛するとき,すでに変数が存在する場合の挙動が違う.

defは上書きをする. defonceはスキップする.

Clojure.lang.VarとSymbolの違いはなんですか? #

Varはデータもそのものを示す参照.

SymbolはVarを示す参照.

Clojure defとinternの違いはなんですか? #

defは値と参照の対応関係を作成してその対応関係をnamespaceに登録するまでの一連の手続きをマクロにした.

internは拘禁でありbindされた写像をnamespaceに登録する.


Tags