Clojure

学习 Clojure - 命名空间

命名空间和名称

命名空间提供了一种组织我们的代码和我们在代码中使用的名称的方法。具体来说,它们允许我们为函数或其他值赋予新的、明确的名称。这些完整名称自然很长,因为它们包含上下文。因此,命名空间也提供了一种方法,可以使用更短、更容易键入的名称来明确引用其他函数和值的名称。

命名空间既是名称上下文,也是 var 的容器。命名空间名称是符号,其中使用句点分隔命名空间部分,例如 clojure.string。按照约定,命名空间名称通常为小写,并使用 - 分隔单词,尽管这不是必需的。

Var

Var 是名称(符号)和值之间的关联。命名空间中的 Var 具有一个完全限定的名称,它是命名空间名称和 var 名称的组合。例如,clojure.string/join 是一个完全限定的 var 名称,其中 clojure.string 指的是命名空间,join 指的是命名空间内的 var。所有 var 都可以通过其完全限定的名称全局访问。按照约定,var 使用小写名称,并使用 - 分隔单词,尽管这也不是必需的。Var 名称可以包含大多数非空格字符。

Var 是使用 def 和其他以 def 开头的特殊形式或宏(如 defn)创建的。Var 在“当前”命名空间中创建。Clojure 运行时在 var 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 都以命名空间与文件的一对一映射结构化,并以与命名空间结构相映射的分层方式存储。

声明命名空间

大多数 Clojure 文件表示单个命名空间,并在文件顶部使用 ns 宏声明该命名空间的依赖项,这通常如下所示

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

ns 宏指定命名空间名称(这应该与使用上述约定的文件路径位置匹配),一个可选的文档字符串,然后是一个或多个声明有关命名空间信息的子句。

Refer

默认情况下,我们可以引用或调用当前命名空间中的 var,而无需指定命名空间(当前命名空间是“默认”)。

此外,您可能已经注意到,我们通常也可以在不完全限定它们的情况下引用 clojure.core 库函数。原因是所有 clojure.core 库 var 都已 referred 到当前命名空间。refer 在当前命名空间的符号表中创建一个条目,该条目引用另一个命名空间中的 var。

clojure.core 引用由 ns 宏完成。(如果您想在不出现警告的情况下重新使用核心中的名称,则可以通过多种方法部分抑制此操作。)

Require

:require 子句对应于 require 函数,该函数指定此命名空间依赖的要加载的一个或多个命名空间。对于每个命名空间,require 可以执行以下操作

  • 加载(或重新加载)命名空间

  • 可选地分配一个别名,该别名仅可在此命名空间的范围内用于引用来自已加载命名空间的 var

  • 可选地引用来自已加载命名空间的 var,以便在此命名空间中使用非限定名称

最后两部分都是关于使名称更容易使用。虽然 var 始终可以通过其完全限定的名称来引用,但我们很少希望在代码中键入完全限定的名称。别名允许我们使用更长完全限定别名的较短版本。Refer 允许我们根本不使用命名空间限定符来使用名称。

require 中,命名空间最常采用四种形式之一

  • clojure.set - 只加载 clojure.set 命名空间(如果尚未加载)

  • [clojure.set :as set] - 加载并为命名空间 clojure.set 创建一个别名 set

    • 这允许您例如使用 set/union 而不是 clojure.set/union 来引用 set 中的 var

  • [clojure.set :refer [union intersection]] - 加载并将特定 var 引用到此命名空间

    • 这允许您仅使用 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 类和导入

除了 var 之外,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 类。