Clojure-JVM上的函数式编程语言(7) 命名空间 作者: R. Mark Volkmann

 原帖地址:http://java.ociweb.com/mark/clojure/article.html#Namespaces

 作者:R. Mark Volkmann

 译者:RoySong

 

命名空间(Namespaces)

    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。

 

一些良好的输出(Some Fine Print)

    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。

Clojure-JVM上的函数式编程语言(7) 命名空间 作者: R. Mark Volkmann

你可能感兴趣的:(java,jvm,clojure,lisp)