原帖地址:http://java.ociweb.com/mark/clojure/article.html#Namespaces
作者:R. Mark Volkmann
译者:RoySong
java通过package来对类以及其中的方法来分组,而Clojure采用命名空间来对通过符号命名的东西来分组。能够
进行分组的东西包含:Vars, Refs, Atoms, Agents, functions, macros以及命名空间本身。
符号被用来指定函数、宏和绑定的名称。而符号本身的作用域取决于它所属的命名空间。在Clojure中,总是会存在
一个默认的命名空间,最初是"user",它被保存在特殊符号*ns*中。默认命名空间可以采用两种方式来改变,
in-ns函数
仅仅改变默认命名空间,而ns宏除了改变命名空间之外还有其他功能。其中一项功能是能够使
clojure.core命名空间中
包含的符号可以在新的命名空间中生效(采用refer,接下来会讨论到
)。ns宏的其他特性将过会儿讨论。
"user"命名空间提供了对所有clojure.core命名空间中符号访问的权限。采用ns宏改变的默认命名空间同样也具备这
样的权限。
为了使用不在默认命名空间中的元素必须进行命名空间限定(namespace-qualified),在符号名前面加上命名空间
的名字和斜杠就完成了限定。举个例子, Clojure Contrib 中的str-utils库在clojure.contrib.str-utils命名空间中定
义了
str-join函数。它取出某个序列中所有元素的字符串表现,并通过指定的连接符将它们连接成一个新的字符串做为
返回值。它的命名空间限定名就是clojure.contrib.str-utils/str-join。
require函数用于加载Clojure库。它接受一个或者多个带引号的命名空间名做为参数。例子如下:
(require 'clojure.contrib.str-utils)
但这仅仅是加载了库,要使用库中的名字仍然需要采用命名空间限定。注意命名空间名和符号名采用斜杠分隔,而java
的包名和类名之间采用点来分隔。举个例子:
(clojure.contrib.str-utils/str-join "$" [1 2 3]) ; -> "1$2$3"
alias函数创建一个命名空间的引用,以免每次都要输入长长的命名空间限定。所创建引用的作用域即当前命名空间。
例子如下:
(alias 'su 'clojure.contrib.str-utils) (su/str-join "$" [1 2 3]) ; -> "1$2$3"
refer函数可以让指定命名空间的所有符号在当前命名空间可用,而不用做命名空间限定。如果被指定的命名空间已经
在当前命名空间中定义,则会抛出一个异常。例子如下:
(refer 'clojure.contrib.str-utils)
采用了refer后之前的代码就可以写成这种形式了:
(str-join "$" [1 2 3]) ; -> "1$2$3"
require
和refer通常联合使用,所以有一个快捷函数use提供来实现它们联合的功能:
(use 'clojure.contrib.str-utils)
ns宏,之前提到过,用于改变默认命名空间。它通常用在源文件的开头。它支持以下指令::require
, :use
和:import
(用于引入java类),用于替代这些指令对应的函数形式。优先采用这些指令而不是它们对应的函数形式。在下面的例子
当中,注意采用:as来创建了一个命名空间的引用,同样也要注意采用了:only来加载Clojure库的部分:
(ns com.ociweb.demo (:require [clojure.contrib.str-utils :as su]) (:use [clojure.contrib.math :only (gcd, sqrt)]) (:import (java.text NumberFormat) (javax.swing JFrame JLabel))) (println (su/str-join "$" [1 2 3])) ; -> 1$2$3 (println (gcd 27 72)) ; -> 9 (println (sqrt 5)) ; -> 2.236 (println (.format (NumberFormat/getInstance) Math/PI)) ; -> 3.142 ; See the screenshot that follows this code. (doto (JFrame. "Hello") (.add (JLabel. "Hello, World!")) (.pack) (.setDefaultCloseOperation JFrame/EXIT_ON_CLOSE) (.setVisible true))
create-ns创建了一个新的命名空间,但并没将它设置为默认命名空间。
def函数在默认命名空间中采用可选的初始值
定义了一个符号;
intern函数在指定的命名空间中采用可选的初始值定义了一个符号(前提是这个命名空间中还没有相
同的符号)。注意一点,采用
intern函数时需要在符号名前加上单引号,def则不用。因为def是个特殊form,并不会对
它所有的参数求值,而intern是个函数,这代表这它会对所有的参数进行求值,例子如下:
(def foo 1) (create-ns 'com.ociweb.demo) (intern 'com.ociweb.demo 'foo 2) (println (+ foo com.ociweb.demo/foo)) ; -> 3
ns-interns函数返回一个包含指定当前已加载命名空间中所有符号的map,这个map的key是符号名,value是Var对象
可能代表函数、宏或者绑定。例子如下:
(ns-interns 'clojure.contrib.math)
all-ns函数返回当前已加载命名空间的序列。当Clojure程序启动时,以下命名空间是默认加载的:
clojure.core
, clojure.main
, clojure.set
, clojure.xml
, clojure.zip和
user。在REPL中,除了上面的命名空间之外,
还会加载:clojure.contrib.repl-utils
, clojure.contrib.seq-utils
and clojure.contrib.str-utils。
namespace函数返回一个指定符号或者关键字对应的命名空间。
其他命名空间相关的函数在这儿就不进行讨论了,比如:ns-aliases
, ns-imports
, ns-map
, ns-name
, ns-publics
, ns-refers
, ns-unalias
, ns-unmap和
remove-ns。
Symbol对象拥有一个
String
名字和一个
String
命名空间(
调用ns),事实上它采用
字符串命名空间名代替了一个命名空间(Namespace
)对象引用,
这就允许了它可能采用的是一个实际不存在的命名空间。
Var对象拥有对一个
Symbol对象(
调用sym),一个
Namespace对象(
调用ns)和一个做为其“根值”
("root value",调用root
)的Object对象
的引用
。
Namespace对象拥有一个map的引用,这个map保存了
Symbol对象和
Var对象的联系(称作
mappings)。它们同样拥有一个包含
Symbol别名和
Namespace对象联系
(称作namespaces
)的map。看看下面的类图,展示了在Clojure实现中,java类和接口的联系的一个
小子集。在Clojure中,术语"interning"通常指添加一个Symbol
-to-Var映射到
Namespace。