From 69d55b7a2498f40daa5b13a2090352db5bd3f7e2 Mon Sep 17 00:00:00 2001 From: eltrac Date: Sat, 3 Oct 2026 18:52:05 +0800 Subject: [publish] /article/clj5 --- content/article/clj5.md | 27 ++++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-) (limited to 'content/article') diff --git a/content/article/clj5.md b/content/article/clj5.md index 37d1abe5..9d02c736 100644 --- a/content/article/clj5.md +++ b/content/article/clj5.md @@ -1,12 +1,13 @@ +++ title = 'Coffee Break Clojure, Vol.5' subtitle = '管理 Clojure 项目' -slug = '' -tags = [] +slug = 'clj5' +tags = ["Lisp","编程"] categories = '茶歇蔻芥' -date = 2026-09-30T15:06:43+08:00 -draft = true +date = 2026-10-03 18:50:02 +draft = false toc = true +fedi = 'https://akk.eltr.ac/objects/81d108fb-6962-44d9-8127-0f1c2698cddb' +++ 在[第一篇](/article/clj1/)我们安装了 Leiningen,当时只是用它启动了 REPL 和 nREPL 服务器,现在我们要用它来干点重活儿了。顺带一提,Leiningen 的名字取自短篇故事《[莱宁根大战蚂蚁](https://en.wikipedia.org/wiki/Leiningen_Versus_the_Ants)》,故事情节…… 就是一个叫莱宁根的男人大战蚂蚁。 @@ -82,7 +83,7 @@ cd antfighter 余下的是可变长度的参数,要求是偶数个且以键值对的形式出现,就是映射的结构。除去 `:description`、`:url` 和 `:license` 这些顾名思义的选项,上述选项的意思分别是: - `:dependencies`:依赖项,应该是包含所有依赖声明的向量。向量的元素通常是长度为 `2` 的向量,第一个元素是依赖名,第二个元素是版本号。默认都应该加载 `org.clojure/clojure` 依赖项。 -- `:main`:项目的入口,即运行项目应该在哪个**命名空间**里找 `-main` 函数。 +- `:main`:项目的入口,即运行项目时应该在哪个**命名空间**里找 `-main` 函数。 - `:target-path`:和 Java 一样,`target` 就是编译产物存放的目录。 - `:profiles`:可以为不同的运行环境和命令指定选项、参数和依赖项。 - `:uberjar`:Uberjar 也叫 Fat Jar。Clojure 和 Java 一样,最终编译为 `.jar` 文件,而 Uberjar 就是把所有依赖项都打包在内,可以独立运行的 `.jar`。这个 Profile 是执行 `lein uberjar` 编译命令时会用到的参数。 @@ -179,7 +180,7 @@ Leiningen 的写法干净不少。Leiningen 和 `deps.edn` 都默认用 [Maven C ) ``` -保存之后执行 `lein deps` 可以下载依赖。用 `lein repl` 启动 REPL,或者用 `lein run` 运行程序的时候也会自动下载依赖。 +保存之后执行 `lein deps` 可以下载依赖。用 `lein repl` 启动 REPL,或者用 `lein run` 运行程序的时候也会自动下载依赖。如果添加了新的依赖,就要重启 REPL 或者重启应用,否则不会生效。 写个文件测试一下,把 Clojure 数据转换为 JSON 字符串,再把字符串转换为 Clojure 数据试试。 @@ -194,7 +195,7 @@ Leiningen 的写法干净不少。Leiningen 和 `deps.edn` 都默认用 [Maven C ;; => {"foo" "bar", "key" "val"} ``` -好了,现在你已经会导包了,可以说已经跨进 Clojure 生态的大门了。 +好了,现在你已经会导包了,可以说已经跨进 Clojure 生态的大门了。如果想知道某个外部命名空间里有哪些符号,可以阅读对应的文档,或者翻阅源代码。 ## 偷渡 Java 代码 @@ -227,7 +228,7 @@ Clojure 的 `import` 跟 Java 的差不了太多,`import` 的参数是 Java `import` 和 `require` 类似,也可以接收多个向量。每个向量的第一个元素是包名,后面的是可变数量的元素,是 Java 类名。上述代码从 `org.apache.commons.validator.routines` 这个包[^3]里引用了 `UrlValidator` 类。 -顺带一提,Clojure 做的一大改变就是把类和命名空间解耦了。在 Java 中,类既是数据类型,又是命名空间。在 Clojure 创造者 Rich Hickey 的演讲《[Simple Made Easy](https://redirect.invidious.io/watch?v=SxdOUGdseq4)》就提到了这一点。命名空间就只是命名空间而已。 +顺带一提,Clojure 做的一大改变就是把类和命名空间解耦了。在 Java 中,类既是数据类型,又是命名空间。在 Clojure 创造者 Rich Hickey 的演讲《[Simple Made Easy](https://redirect.invidious.io/watch?v=SxdOUGdseq4)》就提到了这一点。Clojure 的命名空间就只是命名空间而已。 除了在 `project.clj` 里声明的外部 Java 库,也可以直接用 Java 标准库里的类。 @@ -282,7 +283,7 @@ date.getMonth() 好了,现在你已经会偷 Java 代码用了。 -在 Clojure 中写 Java,本质上同时使用 Lisp 的语法和 Java 的语义(semantics),类的概念都来自 Java,和 Clojure 格格不入。所以,除非必要不建议大量使用 Java 库,如果要用,也建议把 Java 代码封装在 Clojure 函数里。其实不少 Clojure 库就是通过封装 Java 库开发的,有些 Clojure 库还会专门用 Java 写,这样性能会更好。[^4] +在 Clojure 中写 Java,本质上是同时使用 Lisp 的语法和 Java 的语义(semantics),类的概念都来自 Java,和 Clojure 格格不入。所以,除非必要不建议大量使用 Java 库,如果要用,也建议把 Java 代码封装在 Clojure 函数里。其实不少 Clojure 库就是通过封装 Java 库开发的,有些 Clojure 库还会专门用 Java 写,这样性能会更好。[^4] ## 开发依赖和 Leiningen 插件 @@ -311,6 +312,14 @@ date.getMonth() - [lein-cloverage](https://github.com/cloverage/cloverage):用 `lein cloverage` 运行测试并统计测试覆盖率,支持生成多种格式的报告,可以查看各个命名空间的形式覆盖率(forms coverage)和代码行覆盖率。还可以在生成的 HTML 页面里查看具体哪些语句没有被完全覆盖。 - [lein-cljfmt](https://github.com/weavejester/cljfmt):用 `lein cljfmt` 格式化代码。 +## 最后 + +除了命名空间的概念,Clojure 项目的结构和管理方法与其他软件项目并无太多差异。不过我这里说的「管理」并不是针对人力和时间分配的项目管理,而是指对软件的结构和依赖项的管理。 + +Clojure 项目比较特殊的是,尽管其他非 Lisp 编程语言可能也有热重载能力,但他们不像 Lisp 的 REPL 那样灵活。REPL 不只能做到热重载,还允许程序员直接与程序对话,快速上手调试。在项目根目录使用 `lein repl` 启动 REPL 和 nREPL,项目里所有的命名空间都会被加载,程序员可以在 REPL 里用 `ns` 在命名空间之间移动,探索软件的内部结构。 + +既然我们已经掌握了不少 Clojure 的基础知识和项目管理工具,那么是时候来点实操了。下一期我们将会接触到 Ring 及相关的工具,开发简单的 Web 后端。 + [^1]: `ns` 实际上只是把全局变量 `*ns*` 设置成了指定的命名空间。 [^2]: Cheshire 指《爱丽丝梦游仙境》里的「柴郡猫」。`cheshire` 库用的是 Java 库 Jackson。 -- cgit v1.2.3