Clojure

學習 Clojure - 名稱空間

名稱空間和名稱

名稱空間提供一種方法來組織我們的程式碼和程式碼中使用的名稱。具體來說,它們讓我們可以為函式或其他值賦予新的明確名稱。這些完整名稱自然很長,因為它們包含了上下文。因此,名稱空間也提供了一種方法來明確參照其他函式和值的名稱,但使用較短且易於輸入的名稱。

名稱空間既是名稱上下文,也是變數的容器。名稱空間名稱是符號,其中使用句點來分隔名稱空間部分,例如 clojure.string。根據慣例,名稱空間名稱通常是小寫,並使用 - 來分隔字詞,儘管這不是必需的。

變數

變數是名稱(符號)和值之間的關聯。名稱空間中的變數具有完全限定的名稱,它是名稱空間名稱和變數名稱的組合。例如,clojure.string/join 是完全限定的變數名稱,其中 clojure.string 參照名稱空間,而 join 參照名稱空間內的變數。所有變數都可以透過其完全限定的名稱在全域存取。根據慣例,變數具有小寫名稱,並使用 - 分隔字詞,儘管這也不是必需的。變數名稱可以包含大多數非空白字元。

變數是使用 def 和其他以 def 開頭的特殊形式或巨集(例如 defn)建立的。變數是在「目前」的名稱空間中建立的。Clojure 執行時間會在變數 clojure.core/*ns* 中追蹤目前的名稱空間。可以使用 in-ns 函式變更目前的名稱空間。

載入

除了提供命名內容外,命名空間名稱也提供一個慣例,說明載入命名空間的程式碼應在哪裡找到。路徑是根據命名空間名稱建立的

  • 句點會變成目錄分隔符號

  • 連字號會變成底線

  • 加入檔案副檔名 .clj

因此命名空間名稱 com.some-example.my-app 會變成載入路徑 com/some_example/my_app.clj。載入路徑會使用 JVM 類別路徑進行搜尋。類別路徑是一系列目錄位置或 JAR 檔案(JAR 基本上只是 zip 檔案)。

當需要資源時,JVM 會依序搜尋每個類別路徑位置,以尋找載入路徑相對位置的檔案。因此,如果類別路徑為 src:test,載入路徑會在 src/com/some_example/my_app.clj 然後 test/com/some_example/my_app.clj 處進行檢查。

有許多方法可以在 Clojure 中載入程式碼,但最常見的載入方式是透過 require

由於這個載入慣例,大多數 Clojure 都以 1 對 1 的方式將命名空間對應到檔案,並以階層方式儲存,對應到命名空間結構。

宣告命名空間

大多數 Clojure 檔案都代表一個單一命名空間,並在檔案最上方使用 ns 巨集宣告該命名空間的相依性,通常如下所示

(ns com.some-example.my-app
  "My app example"
  (:require
    [clojure.set :as set]
    [clojure.string :as str]))

ns 巨集會指定命名空間名稱(這應與檔案路徑位置相符,使用上述慣例)、一個選用的文件字串,然後是一或多個宣告命名空間事項的子句。

參照

預設情況下,我們可以在不指定命名空間的情況下參照或呼叫目前命名空間中的變數(目前命名空間為「預設」)。

此外,你可能已經注意到,我們通常也可以在不完全限定的情況下參照 clojure.core 函式庫函式。這是因為 clojure.core 函式庫變數都已 參照 到目前命名空間。refer 會在目前命名空間的符號表中建立一個項目,參照另一個命名空間中的變數。

clojure.core 參照是由 ns 巨集執行的。(如果你想在核心程式碼中重複使用名稱而不產生警告,有方法可以部分抑制這個動作。)

require

:require 子句對應到 require 函式,指定要載入一個或多個命名空間,而這個命名空間是此命名空間的相依性。對於每個命名空間,require 可以執行多項動作

  • 載入(或重新載入)命名空間

  • 選擇性指定一個別名,用來在這個名稱空間的範圍內,僅參考已載入名稱空間的變數

  • 選擇性參考已載入名稱空間的變數,讓這個名稱空間可以使用未限定的名稱

最後兩個部分都是關於讓名稱更容易使用。儘管變數總是可以用它們的完全限定名稱來參考,但在我們的程式碼中,我們很少想要輸入完全限定的名稱。別名讓我們可以使用較短的版本,來取代較長的完全限定別名。參考讓我們可以使用完全沒有名稱空間限定的名稱。

require 中,名稱空間最常採用四種形式之一

  • clojure.set - 僅載入 clojure.set 名稱空間(如果尚未載入)

  • [clojure.set :as set] - 載入並為名稱空間 clojure.set 建立一個別名 set

    • 這允許你使用 set 中的變數,例如 set/union,而不是 clojure.set/union

  • [clojure.set :refer [union intersection]] - 載入並將特定變數參考到這個名稱空間

    • 這允許你僅使用 union,而不是 clojure.set/union

  • [company.application.component.user :as-alias user] - 為名稱空間 company.application.component.user 建立一個別名 user,而不載入名稱空間

    • 通常,在使用 :as-alias 時,名稱空間被用作限定詞,但不是可載入的名稱空間

    • 這允許你使用名稱空間限定詞的簡寫,例如:在建立映射時:{::user/id 1},註冊規格:(s/def ::user/id int?) 或解構:(defn find-by-id [{::user/keys [id]}] ,,,)

Java 類別和匯入

除了變數之外,Clojure 還提供對 Java 互操作和存取 Java 類別(存在於套件中)的支援。Java 類別總是可以用它們的完全限定類別名稱來參考,例如 java.util.Date

ns 巨集也會匯入 java.lang 套件中的類別,以便它們可以用類別名稱來使用,而不是完全限定的類別名稱。例如,僅使用 String,而不是 java.lang.String

類似於 :referns 巨集有一個 :import 子句(由 import 巨集支援),它允許你匯入其他類別,以便它們可以使用未限定的名稱

(ns com.some-example.my-app2
  (:import
    [java.util Date UUID]
	[java.io File]))

此範例從 java.util 套件匯入 DateUUID 類別,以及從 java.io 套件匯入 File 類別。