Clojure命名空间

版本

本文翻译自Clojure Namespaces and Vars 本文涵盖如下内容:
+ Clojure命名空间和var概述 + 如何定义命名空间 + 如何使用其它命名空间里的函数 + require,refer和use + 常见错误和典型错误,以及导致这些错误的原因 + 命名空间和代码管理

版权:

This work is licensed under a Creative Commons Attribution 3.0 Unported License (including images & stylesheets). The source is available on Github.

涵盖Clojure版本:Clojure 1.5

概述

Clojure的函数通过命名空间来组织.Clojure命名空间和Java的包或者Python的模块很类似.命名空间实际上就是个map,将名字映射到了var上.在大部分情况下,这些var持有这些函数.

定义一个命名空间

一般情况下使用clojure.core/ns宏来定义命名空间.最基本的形式下,它将名字作为符号.


          (ns superlib.core)

命名空间可以由点号切割的好多段组成


          (ns megacorp.service.core)

需要注意的是,请尽量避免使用单段的命名空间,以免与其它开发人员的命名空间相冲突.如果库或者应用属于某个组织,那么建议以如下形式作为命名空间.[组织名称].[包名|应用名].[函数组名] 例如


          (ns clojurewerkz.welle.kv)

          (ns megacorp.search.indexer.core)

另外,ns宏可以包含如下形式: + (:require ...) + (:import ...) + (:use ...) + (:refer-clojure ...) + (:gen-class ...)

这些其实就是clojure.core/import,clojure.core/require等等这些的简写形式而已

:require

:require形式可以使你的代码能访问其它命名空间的Clojure代码.例如


          (ns megacorp.profitd.scheduling
            (:require clojure.set))

          ;; Now it is possible to do:
          ;; (clojure.set/difference #{1 2 3} #{3 4 5})

此代码将保证clojure.set命名空间被加载,编译并且可以通过clojure.set名来调用.当然可以给加载的命名空间取个别名:


          (ns megacorp.profitd.scheduling
            (:require [clojure.set :as cs]))

          ;; Now it is possible to do:
          ;; (cs/difference #{1 2 3} #{3 4 5})

一次导入两个命名空间的例子;


          (ns megacorp.profitd.scheduling
            (:require [clojure.set  :as cs]
                      [clojure.walk :as walk]))

:refer选项

如果想在当前命名空间里通过简写名称来引用clojure.set空间里的函数,可以通过refer来实现:


          (ns megacorp.profitd.scheduling
            (:require [clojure.set :refer [difference intersection]]))

          ;; Now it is possible to do:
          ;; (difference #{1 2 3} #{3 4 5})

:require形式中的:refer特性为Clojure1.4新增特性.

可能有时需要引入某个命名空间下所有的函数:


          (ns megacorp.profitd.scheduling
            (:require [clojure.set :refer :all]))

          ;; Now it is possible to do:
          ;; (difference #{1 2 3} #{3 4 5})

:import

:import的作用是在当前命名空间引入Java类:


          (ns megacorp.profitd.scheduling
            (:import java.util.concurrent.Executors))

执行上面的代码后,java.util.concurrent.Executors类将会被引入,请可以直接通过名字Executors来使用.可以同时引入多个类.


          (ns megacorp.profitd.scheduling
            (:import java.util.concurrent.Executors
                     java.util.concurrent.TimeUnit
                     java.util.Date))

如果引入的多个类在同一个包下面,就像上面那样,可以使用如下的简介方式:


          (ns megacorp.profitd.scheduling
            (:import [java.util.concurrent Executors TimeUnit]
                     java.util.Date))

虽然导入的list被叫做list,实际上可以使用任意的Clojure的集合(一般使用vector)

当前命名空间

Clojure将通过*ns*来持有当前的命名空间.使用def形式定义的var被添加到了当前命名空间中.

:refer-clojure

我们在使用像clojure.core/get这样的函数和clojure.core/defn这样的宏的时候我们不需要使用它的全限定名.这是因为Clojure默认将clojure.core下的内容全部加载进了当前命名空间里了.所以如果你定义了一个函数名和clojure.core里的重复了(比如find),你将会得到一个警告.

WARNING: find already refers to: #‘clojure.core/find in namespace:    megacorp.profitd.scheduling, being replaced by: #‘megacorp.profitd.scheduling/find

这个警告的意思是在megacorp.profitd.scheduling这个命名空间里,已经有一个clojure.core/find了,但是现在它被你定义的函数覆盖了.请记住,Clojure是很动态的语言,命名空间就是map而已.

解决这个问题的办法有:你可以重命名你的函数或者不引入clojure.core里的这个函数


          (ns megacorp.profitd.scheduling
            (:refer-clojure :exclude [find]))

          (defn find
            "Finds a needle in the haystack."
            [^String haystack]
            (comment ...))

在这里,如果你想使用clojure.core/find的话,你需要通过全限定名来使用:


          (ns megacorp.profitd.scheduling
            (:refer-clojure :exclude [find]))

          (defn find
            "Finds a needle in the haystack."
            [^String haystack]
            (clojure.core/find haystack :needle))

:use

Clojure在1.4之前,:require是不支持:refer的,只能使用:use


          (ns megacorp.profitd.scheduling-test
            (:use clojure.test))

在上面的例子中,clojure.test里的所有内容都被引入到了当前命名空间中.但是一般不会这样使用,建议是只引入需要的函数:


          (ns megacorp.profitd.scheduling-test
            (:use clojure.test :only [deftest testing is]))

1.4以前的做法


          (ns megacorp.profitd.scheduling-test
            (:require clojure.test :refer [deftest testing is]))

而现在鼓励的做法是使用:require,通过:refer来进行限制.

文档与元数据

命名空间可以包含说明文档.你可以在ns宏里添加:


          (ns superlib.core
            "Core functionality of Superlib.

             Other parts of Superlib depend on functions and macros in this namespace."
            (:require [clojure.set :refer [union difference]]))

或者元数据


          (ns ^{:doc "Core functionality of Superlib.
                      Other parts of Superlib depend on functions and macros in this namespace."
                :author "Joe Smith"}
             superlib.core
            (:require [clojure.set :refer [union difference]]))

元数据可以包含任意的键,例如:author,很多工具可以使用(像Codox,Cadastre或者lein-clojuredocs)

如何在REPL里使用其它命名空间的函数

ns宏是你经常需要使用的,它引入其它命名空间的函数.但是它在REPL里不太方便.这里可以直接使用require:


          ;; Will be available as clojure.set, e.g. clojure.set/difference.
          (require ‘clojure.set)

          ;; Will be available as io, e.g. io/resource.
          (require ‘[clojure.java.io :as io])
          It takes a quoted libspec. The libspec is either a namespace name or a collection (typically a vector) of [name :as alias] or [name :refer [fns]]:

          (require ‘[clojure.set :refer [difference]])

          (difference #{1 2 3} #{3 4 5 6})  ; ? #{1 2}

:as和:refer可以一起使用


          (require ‘[clojure.set :as cs :refer [difference]])

          (difference #{1 2 3} #{3 4 5 6})  ; ? #{1 2}
          (cs/union #{1 2 3} #{3 4 5 6})    ; ? #{1 2 3 4 5 6}

clojure.core/use可以做和clojure.core/require一样的事情,但是不推荐使用了.

命名空间和编译

Clojure是一个需要编译的语言:代码在被加载的时候进行编译.

命名空间可以包含var或者去继承协议,添加多重方法实现或载入其它库.所以为了完成编译,你需要引入需要的命名空间.

私有Vars

Vars(包括defn宏定义的函数)可以设为私有的.有两种方法可以来做这件事情:使用元数据或者defn-宏


          (ns megacorp.superlib)

          ;;
          ;; Implementation
          ;;

          (def ^{:private true}
            source-name "supersource")

          (defn- data-stream
            [source]
            (comment ...))

常量Vars

Vars可以设为常量,通过:const元数据来设置.这将会促使Clojure编译器将其编译为常量:


          (ns megacorp.epicgame)

          ;;
          ;; Implementation
          ;;

          (def ^{:const true}
            default-score 100)

如何通过名称来查找和执行函数

可以通过clojure.core/resolve在制定的命名空间里通过名字查找函数.名字需要使用引号修饰.返回值可以直接当做函数使用,比如,当做参数传递给高阶函数:


          (resolve ‘clojure.set ‘difference)  ; ? #‘clojure.set/difference

          (let [f (resolve ‘clojure.set ‘difference)]
             (f #{1 2 3} #{3 4 5 6}))  ; ? #{1 2}

编译异常

本节讨论一些常见的编译错误.

ClassNotFoundException

这个异常的意思是JVM无法加载类.可能是因为拼写错误,或者在classpath上没有这个类.可能是你的项目没有很好的处理依赖关系.

user=> (import java.uyil.concurrent.TimeUnit)
          ClassNotFoundException java.uyil.concurrent.TimeUnit  java.net.URLClassLoader$1.run (URLClassLoader.java:366)

在上面的例子中,java.uyil.concurrent.TimeUnit拼写错误,应该是java.util.concurrent.TimeUnit

CompilerException java.lang.RuntimeException: No such var

这个错误的意思是,使用了一个不存在的var.这可能是拼写错误,或者不正确的宏展开等类似问题.

user=> (clojure.java.io/resouce "thought_leaders_quotes.csv")
          CompilerException java.lang.RuntimeException: No such var: clojure.java.io/resouce, compiling:(NO_SOURCE_PATH:1)

在上面的例子中,clojure.java.io/resouce应该写成clojure.java.io/resource.NO_SOURCE_PATH的意思是编译是在repl里触发的,而不是一个Clojure源文件.

时间: 2024-10-03 07:49:19

Clojure命名空间的相关文章

storm源码之storm代码结构【译】【转】

[原]storm源码之storm代码结构[译] 说明:本文翻译自Storm在GitHub上的官方Wiki中提供的Storm代码结构描述一节Structure of the codebase,希望对正在基于Storm进行源码级学习和研究的朋友有所帮助. Storm的源码共分为三个不同的层次. 首先,Storm在设计之初就考虑到了兼容多语言开发.Nimbus是一个thrift服务,topologies被定义为Thrift结构体.Thrift的运用使得Storm可以被任意开发语言使用. 其次,Stor

storm源码之storm代码结构【译】

说明:本文翻译自Storm在GitHub上的官方Wiki中提供的Storm代码结构描述一节Structure of the codebase,希望对正在基于Storm进行源码级学习和研究的朋友有所帮助. Storm的源码共分为三个不同的层次. 首先,Storm在设计之初就考虑到了兼容多语言开发.Nimbus是一个thrift服务,topologies被定义为Thrift结构体. Thrift优势 : 使得Storm可以被任意开发语言使用. 其次,Storm的所有接口都是Java语言来定义的.因此

Clojure学习笔记(一)——介绍、安装和语法

什么是Clojure Clojure是一种动态的.强类型的.寄居在JVM上的语言. Clojure的特性: 函数式编程基础,包括一套性能可以和典型可变数据结构媲美的持久性数据结构 由JVM提供的成熟的.高效的运行时环境:所以Clojure可以使用Java类库,反之Clojure库也可以被Java使用 跟JVM/Java的互操作能力使得很多架构.运维方面的需求可以得到满足:Clojure代码可以像Java代码一样被打包,然后部署到任何Java应用可以部署的地方 一套提供并发.并行语义的机制:Clo

Clojure基础

最近看了一段clojure,下面是从书上摘下来的一下语言基础的精华部分 ;函数的基本形式 (defn average [numbers] (/ (apply + numbers) (count numbers))) (average [60 80 100 400]) (read-string "42") (read-string "(+ 1 2)") (pr-str [1 2 3]) (read-string "[1 2 3]") "h

schema for clojure

Schema for Clojure Data Shape Declaration and Validation 1.何为schema schema是描写叙述数据形式的一种clojure数据结构,可用于文件.校验函数和数据. 以下举个样例让大家对schema有个整体认识. 例: (ns schema-examples (:require [schema.core :as s])) (def  s-type s/Str) (s/validate  s-type  "123")  ;; S

Clojure进阶:使用Clojure编写文字冒险游戏

1. 准备 2. 语法和语义 3. 为我们的游戏世界定义数据 4. 环顾我们的游戏世界 5. 函数式编码风格 6. 环游我们的游戏世界 7. 构建SPELs 8. 创建特殊操作 9. 附录 10. 为什么没有使用"宏"这个词 11. 译者感想 本文翻译自:Casting SPELs in Clojure 1 准备 任何学过Lisp的人都会说List和其它语言有很大的不同.它有很多不可思议的地 方.本文将告诉你它有哪些独特之处! 本文适用于Clojure,它是一个运行在JVM上的Lisp

clojure GUI编程-2

*/--> pre.src {background-color: #292b2e; color: #b2b2b2;} pre.src {background-color: #292b2e; color: #b2b2b2;} pre.src {background-color: #292b2e; color: #b2b2b2;} pre.src {background-color: #292b2e; color: #b2b2b2;} clojure GUI编程-2 目录 1. 简介 2. 实现过程

命名空间

1.命名空间,即将代码划分成不同空间,不同空间的类名相互独立,互不冲突.一个php文件中可以存在多个命名空间,第一个命名空间前不能有任何代码.内容空间声明后的代码便属于这个命名空间,例如: <?php echo 111; //由于namespace前有代码而报错 namespace Teacher; class Person{ function __construct(){ echo 'Please study!'; } } 2.调用不同空间内类或方法需写明命名空间.例如: <?php nam

PHP命名空间

命名空间 namespace命名空间 1. 什么是命名空间 在php程序语言里边,语法规则要求同名称的函数.类名.常量在一个请求里边不允许出现多次.如果有的应用程序(例如tp框架中有smarty.视频方法插件,他们有同名称的多个元素)不得已必须出现多个同名的 函数.类名.常量,那么我们就可以把它们放到不同的空间里边做请求.这个不同的空间就称作“命名空间”. 2. 使用命名空间 通过namespace关键字声明命名空间. namespace  空间名称; (空间名称 按照php正确的命名方式定义即