up: 📂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. 🤔名前空間やファイル名は単数か複数か問題

✅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で閲覧できる.

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

WARNING: empty? already refers to: #‘clojure.core/empty? in namespace:

みたいな警告への対処.

refer-clojure :exclude をつかう.

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

✅clojure.core/useはrequireの拡張でnamespaceに他の関数を読み込む

requireに似ている関数としてcloujure.core/useという関数がある. nsの定義にしても2種類書ける.

(ns whatever
  (:use [some.namespace :only [vars you want]]))
 
(ns whatever
  (:require [some.namespace :as sn]))

useはrequireの拡張.

;; https://clojuredocs.org/clojure.core/use

Like ‘require, but also refers to each lib’s namespace using clojure.core/refer. Use :use in the ns macro in preference to calling this directly.

requireをつかって別のnamespaceの関数を呼び出すときは, hoge/xxxxみたいにnamespace付きで呼び出す必要があるが, useだと他のnamespaceの関数を自分のところに再定義して直に呼び出すことができる. もちろんこれだと名前が競合するため, :only, :rename, :excludeなどのoptionで制御する必要がある.

useは特別な場合を除けばバグの温床なのでrequireを使うほうが望ましい. たとえばREPLでのデバッグで別のnamespaceで定義して関数をuseで読み込むとか.


Emacsのclj-refactorだと強制的に (require ‘[lib.xxx :refer :all])の文法に書き換わってしまう. これはuseの変わりに:refer :allにしろということなのかな😲?


🔧potemkin/import-vars

他のnamespaceに定義されたユーティリティ的なライブラリやサブディレクトリのライブラリをメインライブラリに読み込んでさらに外部に提供したい場合に, potemkin/import-varsが便利.

https://github.com/clj-commons/potemkin#import-vars

(potemkin/import-vars
 [hogehoge.util.time ->timestamp])

これは特に, ライブラリを作成するときに機能ごとにnamespaceを分割してそれぞれ作成したものを, 単一のnamespaceに配下の関数たちをまとめて外部公開するようなときに便利. 利用する側も, 単一のnamespaceのみrequireすればいい.

ex. Part6: TODO アプリを組み上げる — Clojure の日本語ガイド

Clojure: 前方参照(declare)

Clojureではあるシンボルを参照するときは, ファイルの下から上に参照する. そのように関数を書いていく. ただし, 下で宣言した関数を上から利用するには, 明示的にdeclare関数でシンボルの定義を書く.

ただし, Clojureスタイルガイドでは, 可能な限りこの方法を避けるように書かれている.

https://totakke.github.io/clojure-style-guide/#forward-references

💡Clojureのファイル名はアンダースコアでnamespaceはハイフン

namespaceの一番のワナかもしれない. かなりハマった.

Clojureのコードで現在のnamespaceを取得するには?(ns-name)

ns-name をつかう.

(ns-name *ns*)

これでkeywordとしてnamespace名が変えるのでclojure.stringで文字列変換.

(defn get-namespace []
  (-> (ns-name *ns*)
      str
      (str/split #"\.")
      last))

Clojure: docstring

namespaceの説明, いわゆるdocstringといわれるもの.

パッケージの下の行に書く.

(ns my.name.space
  "Very cool namespace doing this and that."
  (:require other.cool.stuff))

大抵の公開されているClojureのライブラリはドキュメントが充実してないがdocstringはちゃんと書かれているのでここを読むことになる. または, ドキュメントはdocstringから自動生成される.

cider-docなどで簡単にドキュメントを参照することも可能.

see more. ✅CIDERとドキュメント連携

Clojure: Scope

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

🔖Scope


Clojure Dynamic Scope(binding)

動的スコープ(Dynamic Scope). Emacsでよくあるやつ.

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

Lispの慣例として, アスタリスク(*)でsymbolを囲む. defで定義する時, metaデータで ^:dynamicを書くとbindingと同様にコンパイラは振る舞う.

💡alter-var-rootによって更新する.


そういう意味だと, スレッドローカルといえるかも. スレッドをまたぐ方法はまた別にある? (未調査, 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.lang.Var

  • clojure.lang.Varはデータそもものを表すオブジェクト.
  • 可変(mutable).
  • varの定義は var, 確認は var?
  • varのリーダマクロは, #’
  • varの中身を確認するのは@.

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

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

ref. Clojure: MetaData

clojure.lang.Symbol

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

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

clojure.core/alter-var-root

Atomically alters the root binding of var v by applying f to its current value plus any args.

https://clojuredocs.org/clojure.core/alter-var-root

✅alter-var-rootベストプラクティス

alter-var-rootにはベストプラクティスがある.

  • defonceで定義.
  • constantlyと一緒にupdate.
(defonce string "abcd")
 
(alter-var-root #'string (constantly "wxyz"))

これをやらないと, 他のnamespaceから参照があるときのupdateでエラーがでる.

clojure - Difference between using “def” to update a var and “alter-var-root” - Stack Overflow

💡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