Clojure 快速突击

自己所学过的编程语言基本是 C 风格的, 给自己定下的目标是要学习下 Python, Swift 和 Clojure. 正如之前的 我的 Python 快速入门 那样的几分钟入门, 这里记录下 Clojure 的快速上手过程.

为什么是 Clojure, 因为它是 Lisp 的一个方言, 个人觉得有必要拓展一下不同的语言风格与思维方式, 就像当初接触 Objective-C 的 [person sayHello] 的方法调用有点不好理解一样, 其实把它还原为面向对象的本质是向 person 发送 sayHello 消息就简单了.

编程语方不仅仅是一种技术, 它更是一种思维习惯

希望通过 Clojure 这样的语言来感受另样的思维方式. Clojure 是运行在 JVM 之上的函数式 List 方言. Clojure 乍一看, 基本就是一个括号语言, 它的语法更能体现操作/函数为中心. Clojure 的圆括号兼具 C 风格语言的圆括号(参数列表), 分号(分隔语句), 以及大括号(限定作用域) 的功能. (1 + 2 + 3 +4) 只用写成 (+ 1 2 3 4)。

因为  Clojure 是构筑在 JVM 之上, 所以可以从 http://clojure.org/community/downloads 下载 clojure 的 jar 包, 然后

java -jar clojure-1.8.0.jar

就能进到 Clojure 的 REPL(read-eval-print-loop) 控制台了, 就可以开始体验 Clojure 的代码 user=> (+ 1 2 3) ,如果要运行一个已经写好的 Clojure 文件, 如 hello.clj, 就要用 java -jar clojure-1.8.0.jar hello.clj 来执行. 为方便可以建立一个脚本  clj, 内容为

java -jar /path/clojure.jar $1

在 Mac 下我一般首先会尝试 brew install clojure, 结果它会告诉我说 Clojure 只是一个库, 需要用 brew 来安装 leiningen, 于是就  brew install leiningen , 安装完 leiningen 后提示依赖会安装到 $HOME/.m2/repository, 用命令  lein repl 进到 Clojure 的控制台, Leiningen 是一个用 Clojure 写的像 Maven/sbt 那样的构建工具, Leininge 和 Clojure 的关系就像是 sbt 与 Scala.

现在真正开始来学习这门语言了, 主要根据在线的 Clojure 入门教程 来整理的.

Clojure 的名字包含了 C(C#), L(List) 和 J(Java). Clojure 以操作为中心(操作前置, 更能体现计算机的行为), 它实现成三种形式: 函数(function), 宏(macro) 或者 special form(非 Clojure 代码, 基本就是关键字, 像 def, catch 之类, 不是我们要考虑的).

有人觉得 Lisp 方言很简洁, 很美; 数据和代码的表达形式是一致的. 就像是 Vim 或 Emacs 中的快捷键都有对应的命令一样, Clojure 的语法糖一般也都有相对应的函数或宏, 例如:

注释用 ; _text_, 对应的宏是 (comment _text_), 如 (comment 这一行是干什么的, 不需要引号括起来的)
正则表达式 #"_pattern_",  对应的函数是 (re-pattern _pattern_)
List 是 `(_items_),  对应函数 (list _items_)
Vector [_items_],  对应函数 (vector _items_)
Set   #{_items_}, 对应函数 (hash-set _items_) 或 (sorted-set _items_)
Map {_key-value-pairs_}, 对应函数 (hash-map _key-value-pairs_) 或 (sorted-map _key-value-pairs_)
匿名函数 #(_single-expression_), 用 %1, %2 来表示参数, 对应函数 (fn [_arg-names_] _expressions_)

等等等等

怎么看起变得像 Perl 的那些约定了, Perl 的标量, 数组, 哈希分别用 $, @, % 作为变量名前缀, 以及一堆的 $_. $$, $! 这样的规定.

变量/Binding

Clojure 是函数式的, 它本质是不支持变量的, 它包括全局binding, 线程本地binding, 函数内本地binding, 以及表达式内部binding. 定义方式为

(def v 1)  或 (def ^:dynamic v 1) 在所有线程是可见的. 函数(defn foo [a b] ...) 的参数是在这个函数内的本地binding. (let [v 2]), (binding [v 3]). 宏 binding 与 let 的不同之处是在它的作用域内会暂时覆盖全局binding

不说别的, Clojure 光一个变量就能把一半想学它的人给吓跑. Clojure 还有这样的操作  (var-set #'v 6), (def ^:dynamic *shiro-response*). 语法糖的增多未必让语方简洁, 可能更晦涩难懂, 云里雾里.

集合

和 Scala 一样, Clojure 自带集合实现, 然而与 Scala 不同的是 Scala 有分可变与不可变化集合, Clojure 更彻底, 它的函数式特性决定只提供不可变的集合, 所以对集合的任何插入元素, 排序等操作都会生成一个新的集合, 不影响老的集合. 例如下面的语句, 老集合在 conj, reverse 操作后都不会变的.

Clojure 集合分为 list, vector, set 和 map. 看集合的几个基本操作, 像 Java 的 map, reduce(collect), filter 对应是 Clojure 有 map, apply 和  filter 方法

其他还有一些常用集合函数, 如 first, second, last, nth, next, butlast, drop-last, nthnext, 和相当于 java 的 && 或 or 的谓词测试函数, 如 every?, instance?, not-every?, some, not-any? 等. (every? #(instance? String %)  ["I'm string" 2 4)).

List  - 有序列表, 它更像是 Java 的堆栈, 适合操作顶端元素, 有三种方式来创建 List

像 Python 的 dir() 一样, 在 Clojure 里可以用 (doc list), (doc quote) 那样的方式来查看函数或宏的用法. 搜索 list 是线性的, 转成  set 会更高效, 如

Vector - 也是一种有序集合, 适于从后面操作, 或用索引(nth) 进行操作, 所以凡无特别需求, 尽量用  vector 而不是 list, 而且 [...] 比 (...) 更自然. vector 的声明方式和索引取值

Set - 它的概念与 Java 的 Set 是一样的, 并且也分有序与无序的. set 的声明与基本用法

Map - map 的 key 一般会用一个 keyword (内部字符串, 有点像 Scala 的  Symbol), 声明方式如下

类似于 Set, map 变量名可以作为它的 key 的函数, 方便获取值, 如果 key 是一个 keyword  的话也可作为函数用, 如

其他的 map 操作有 contains? 是否包含 key; keys 和 vals 分别返回所有 key 或  value 的集合(Vector), select-keys 选择出子 map.

Clojure 有一种与生俱来的接近于 JSON 结构的定义方法, 如下面这样的结构

其余以后实际了解 get-in, reduce, accoc-in 和  update-in 等函数.

-> 和 -?> 这两个宏很有意思, 它是一个管道操作, 把前面函数的返回值作为后一个函数的参数

还有一种特殊的 Map 是 StructMap, 用 create-stuct 函数或 defstruct  宏来创建, 不深入.

 

本文链接 https://yanbin.blog/clojure-get-started/, 来自 隔叶黄莺 Yanbin Blog

[版权声明] Creative Commons License 本文采用 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0) 进行许可。

Subscribe
Notify of
guest

0 Comments
Inline Feedbacks
View all comments