├── .nojekyll ├── 02~语法使用 ├── 02~核心特性 │ ├── README.md │ ├── 不可变性 │ │ └── README.md │ ├── 序列抽象 │ │ ├── README.md │ │ └── 2024.11~序列抽象.md │ ├── 延迟求值 │ │ └── README.md │ └── 持久化数据结构 │ │ └── README.md ├── 03~函数式编程 │ ├── README.md │ └── 线程宏 │ │ └── 2024.11~thread-first 与 thread-last 对比.md ├── 01~语法基础 │ ├── 01~数据类型 │ │ └── README.md │ ├── 02~函数基础 │ │ ├── README.md │ │ ├── 2024.11~Clojure 函数定义.md │ │ └── 2024.11~Clojure 函数参数与解构.md │ ├── 03~控制流 │ │ └── README.md │ ├── 05~集合类型 │ │ └── README.md │ └── 04~绑定与作用域 │ │ ├── README.md │ │ └── let 解构.md └── 04~Java 互操作 │ └── README.md ├── 04~工程实践 ├── 类型校验 │ └── malli │ │ └── README.md ├── Web 开发 │ └── ring 开发 │ │ └── README.md └── 数据库操作 │ └── honeysql │ └── README.md ├── 88~Clojure Refs ├── README.md └── Simple Values │ └── Numbers │ └── +.md ├── INTRODUCTION.md ├── 03~并发编程 └── Missionary │ └── README.md ├── 99~参考资料 ├── 2023~《从零开始的 Clojure》笔记 │ └── README.md ├── 2015~《Brave Clojure》 │ ├── README.md │ └── 04~深入探讨核心功能.md ├── 2012~《Clojure - Functional Programming for the JVM》~中文版 │ ├── README.md │ ├── 15~谓词.md │ ├── 02~函数式编程.md │ ├── 06~REPL.md │ ├── 09~StructMap.md │ ├── 14~递归.md │ ├── 20~元数据.md │ ├── 13~迭代.md │ ├── 18~解构.md │ ├── 04~快速开始.md │ ├── 22~并发.md │ ├── 07~变量.md │ ├── 24~编译.md │ ├── 17~输入输出.md │ ├── 19~命名空间.md │ ├── 12~条件处理.md │ ├── 21~宏.md │ ├── 16~序列.md │ ├── 03~Clojure 概述.md │ ├── 11~Java 互操作.md │ ├── 10~函数定义.md │ ├── 05~Clojure 语法.md │ └── 08~集合.md ├── 《Clojure 官方教程》 │ ├── README.md │ ├── 03~Sequential Collections.md │ ├── 06~Namespaces.md │ ├── 05~Flow Control.md │ ├── 04~Hashed Collections.md │ ├── 02~Functions.md │ └── 01~Syntax.md └── 《Clojure 趣学指南》 │ └── 01~Clojure 简介.md ├── 01~快速开始 ├── 99~参考资料 │ ├── Baeldung~Introduction to Clojure.md │ └── 2019~《Clojure 学习笔记》 │ │ ├── 01~环境搭建.md │ │ ├── 03~小火汁,没想到你也是序列.md │ │ └── 02~快速上手.md └── Babashka │ └── 99~参考资料 │ └── 2023~Babashka Babooka: 使用 Clojure 开发命令行工具.md ├── .gitattributes ├── README.md ├── .gitignore ├── index.html └── header.svg /.nojekyll: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /02~语法使用/02~核心特性/README.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /02~语法使用/03~函数式编程/README.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /04~工程实践/类型校验/malli/README.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /88~Clojure Refs/README.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /INTRODUCTION.md: -------------------------------------------------------------------------------- 1 | # 本篇导读 2 | -------------------------------------------------------------------------------- /02~语法使用/01~语法基础/01~数据类型/README.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /02~语法使用/01~语法基础/02~函数基础/README.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /02~语法使用/01~语法基础/03~控制流/README.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /02~语法使用/01~语法基础/05~集合类型/README.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /02~语法使用/02~核心特性/不可变性/README.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /02~语法使用/02~核心特性/序列抽象/README.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /02~语法使用/02~核心特性/延迟求值/README.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /02~语法使用/02~核心特性/持久化数据结构/README.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /02~语法使用/04~Java 互操作/README.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /04~工程实践/Web 开发/ring 开发/README.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /04~工程实践/数据库操作/honeysql/README.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /02~语法使用/01~语法基础/04~绑定与作用域/README.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /03~并发编程/Missionary/README.md: -------------------------------------------------------------------------------- 1 | # Missionary 2 | -------------------------------------------------------------------------------- /88~Clojure Refs/Simple Values/Numbers/+.md: -------------------------------------------------------------------------------- 1 | # + 2 | -------------------------------------------------------------------------------- /99~参考资料/2023~《从零开始的 Clojure》笔记/README.md: -------------------------------------------------------------------------------- 1 | > [原文地址](https://ithelp.ithome.com.tw/articles/10318803) 2 | -------------------------------------------------------------------------------- /01~快速开始/99~参考资料/Baeldung~Introduction to Clojure.md: -------------------------------------------------------------------------------- 1 | # Links 2 | 3 | - https://www.baeldung.com/clojure 4 | -------------------------------------------------------------------------------- /99~参考资料/2015~《Brave Clojure》/README.md: -------------------------------------------------------------------------------- 1 | > [原文地址](https://www.braveclojure.com/getting-started/) 2 | 3 | # Clojure for the Brave and True 4 | -------------------------------------------------------------------------------- /99~参考资料/2012~《Clojure - Functional Programming for the JVM》~中文版/README.md: -------------------------------------------------------------------------------- 1 | > [原文地址](https://wizardforcel.gitbooks.io/clojure-fpftj/content/26.html) 2 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.xmind filter=lfs diff=lfs merge=lfs -text 2 | *.zip filter=lfs diff=lfs merge=lfs -text 3 | *.pdf filter=lfs diff=lfs merge=lfs -text 4 | -------------------------------------------------------------------------------- /01~快速开始/Babashka/99~参考资料/2023~Babashka Babooka: 使用 Clojure 开发命令行工具.md: -------------------------------------------------------------------------------- 1 | > [原文地址](https://blog.3vyd.com/blog/posts-output/2023-11-20-Babashka-Babooka/) 2 | 3 | # Babashka Babooka: 使用 Clojure 开发命令行工具 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Clojure 2 | 3 | # Links 4 | 5 | - https://github.com/adityaathalye/clojure-by-example 6 | - https://www.ituring.com.cn/book/tupubarticle/1793 7 | - https://www.w3cschool.cn/clojure/clojure-j5w81wf2.html 8 | -------------------------------------------------------------------------------- /99~参考资料/《Clojure 官方教程》/README.md: -------------------------------------------------------------------------------- 1 | # Welcome to Clojure! 2 | 3 | This guide is a gentle introduction to the basics of Clojure and links out to other references and resources if you want more detail. These pages are intended to be read in order and may build upon material from prior pages. Each page has a few exercises provided at the bottom of the page. 4 | -------------------------------------------------------------------------------- /99~参考资料/《Clojure 趣学指南》/01~Clojure 简介.md: -------------------------------------------------------------------------------- 1 | > [原文地址](https://devinkin.github.io/post/clojureprogramming/chapter1/) 2 | 3 | # Clojure 简介 4 | 5 | # 关键字 6 | 7 | 关键字使用一个冒号标识。如果关键字里面包含 / ,表示这个关键字是命名空间限定的。如果一个关键字以两个冒号 :: 开头,表示是当前命名空间的关键字。 8 | 9 | ```clj 10 | (def pizza {:name "Ramunto's" 11 | :location "Claremont, NH" 12 | ::location "43.3734, -72.3365"}) 13 | ``` 14 | -------------------------------------------------------------------------------- /99~参考资料/2012~《Clojure - Functional Programming for the JVM》~中文版/15~谓词.md: -------------------------------------------------------------------------------- 1 | # 谓词 2 | 3 | Clojure 提供了很多函数来充当谓词的功能 -- 测试条件是否成立。它们的返回值是 true 或者 false。在 Clojure 里面`false` 以及`nil` 被解释成 false. `true` 以及任何其他值都被解释成 true,包括 0。谓词函数的名字一般以问号结尾。 4 | 5 | 反射是一种获取一个对象的特性,而不是它的值的过程。比如说对象的类型。有很多谓词函数进行反射。测试一个对象的类型的谓词包括 `class?`,`coll?`,`decimal?`,`delay?`,`float?`,`fn?`,`instance?`,`integer?`,`isa?`,`keyword?`,`list?`,`macro?`,`map?`,`number?`,`seq?`,`set?`,`string?` 以及 `vector?`。一些非谓词函数也进行反射操作,包括:`ancestors`,`bases`,`class`,`ns-publics` 以及`parents`。 6 | 7 | 测试两个值之间关系的谓词有:`<`,`<=`,`=`,`not=`,`==`,`>`,`>=`,`compare`,`distinct?` 以及 `identical?`. 8 | 9 | 测试逻辑关系的谓词有:`and`, `or`, `not`, `true?`, `false?` 和 `nil?` 10 | 11 | 测试集合的一些谓词在前面已经讨论过了,包括:`empty?`,`not-empty`,`every?`,`not-every?`,`some?` 以及`not-any?`. 12 | 13 | 测试数字的谓词有 `even?`,`neg?`,`odd?`,`pos?` 以及 `zero?`. 14 | -------------------------------------------------------------------------------- /99~参考资料/2012~《Clojure - Functional Programming for the JVM》~中文版/02~函数式编程.md: -------------------------------------------------------------------------------- 1 | # 函数式编程 2 | 3 | 函数式编程 是一种强调函数必须被当成第一等公民对待,并且这些函数是“纯”的编程方式。这是受 lambda 表达式 启发的。纯函数的意思是同一个函数对于同样同样的参数,它的返回值始终是一样的 — 而不会因为前一次调用修改了某个全局变量而使得后面的调用和前面调用的结果不一样。这使得这种程序十分容易理解、调试、测试。它们没有副作用 — 修改某些全局变量,进行一些 IO 操作(文件 IO 和数据库)。状态被维护在方法的参数上面,而这些参数被存放在栈(stack)上面(通常通过递归调用),而不是被维护在全局的堆(heap)上面。这使得方面可以被执行多次而不用担心它会更改什么全局的状态(这是非常重要的特征,等我们讨论事务的时候你就会意识到了)。这也使得高级编译器为了提高代码性能而对代码进行重排(reording)和并行化(parallelizing)成为可能。(并行化代码现在还很少见) 4 | 5 | 在实际生活中,我们的程序是需要一定的副作用的。Haskel 的主力开发 Simon Peyton-Jones 曾经曰过: 6 | 7 | “到最后,任何程序都需要修改状态,一个没有副作用的程序对我们来说只是一个黑盒,你唯一可以感觉到的是:这个黑盒在变热。。” 8 | 9 | 问题的关键是我们要控制副作用的范围,清晰地定位它们,避免这种副作用在代码里面到处都是。 10 | 11 | 把函数当作“第一公民”的语言可以把函数赋值给一个变量,作为参数来调用别的函数,同时一个函数也可以返回一个函数。可以把函数作为返回值的能力使得我们选择之后程序的行为。接受函数作为参数的函数我们称为“高阶函数”。从某个方面来说,高阶函数的行为是由传进来的函数来配置的,这个函数可以被执行任意次,也可以从不执行。 12 | 13 | 函数式语言里面的数据是不可修改的。这使得多个线程可以在不用锁的情况下并发地访问这个数据。因为数据不会改变,所以根本不需要上锁。随着多核处理器的越发流行,函数式语言对并发语言的简化可能是它最大的优点。如果所有这些听起来对你来说很有吸引力而且你准备来学学函数式语言,那么你要有点心理准备。许多人觉得函数式语言并不比面向对象的语言难,它们只是风格不同罢了。而花些时间学了函数式语言之后可以得到上面说到的那些好处,我想还是值得的。比较流行的函数式语言有: Clojure , Common Lisp , Erlang , F# , Haskell , ML) , OCaml , Scheme) , Scala . Clojure 和 Scala 是 Java Virtual Machine (JVM)上的语言. 还有一些其它基于 JVM 的语言: Armed Bear Common Lisp (ABCL) , OCaml-Java and Kawa (Scheme). 14 | -------------------------------------------------------------------------------- /99~参考资料/2012~《Clojure - Functional Programming for the JVM》~中文版/06~REPL.md: -------------------------------------------------------------------------------- 1 | # REPL 2 | 3 | REPL 是 read-eval-print loop 的缩写. 这是 Lisp 的方言提供给用户的一个标准交互方式,如果用过 python 的人应该用过这个,你输入一个表达式,它立马再给你输出结果,你再输入。。。如此循环。这是一个非常有用的学习语言,测试一些特性的工具。 4 | 5 | 为了启动 REPL,运行我们上面写好的 clj 脚本。成功的话会显示一个” `user=>` “. “ `=>` ” 前面的字符串表示当前的默认名字空间。“=>”后面的则是你输入的 form 以及它的输出结果。下面是个简单的例子: 6 | 7 | ``` 8 | user=> (def n 2) 9 | #'user/n 10 | user=> (* n 3) 11 | 6 12 | ``` 13 | 14 | `def` 是一个 special form,它相当于 java 里面的定义加赋值语句. 它的输出表示一个名字叫 “ `n` ” 的 symbol 被定义在当前的名字空间 “ `user` ” 里面。 15 | 16 | 要查看一个函数,宏或者名字空间的文档输入 `(doc _name_)。看下面的例子:` 17 | 18 | ``` 19 | (require 'clojure.contrib.str-utils) 20 | (doc clojure.contrib.str-utils/str-join) ; -> 21 | ; ------------------------- 22 | ; clojure.contrib.str-utils/str-join 23 | ; ([separator sequence]) 24 | ; Returns a string of all elements in 'sequence', separated by 25 | ; 'separator'. Like Perl's 'join'. 26 | ``` 27 | 28 | 如果要找所有包含某个字符串的所有的函数的,宏的文档,那么输入这个命令 `(find-doc "_text_")` . 29 | 30 | 如果要查看一个函数,宏的源代码 `(source _name_)` . `source` 是一个定义在 `clojure.contrib.repl-utils` 名字空间里面的宏,REPL 会自动加载这个宏的。 31 | 32 | 如果要加载并且执行文件里面的 clojure 代码那么使用这个命令 `(load-file "_file-path_")` . Clojure 源文件一般以.clj 作为后缀。 33 | 34 | 如果要退出 REPL,在 Windows 下面输出 ctrl-z 然后回车,或者直接 ctrl-c; 在其它平台下 (包括 UNIX, Linux 和 Mac OS X), 输入 ctrl-d. 35 | -------------------------------------------------------------------------------- /99~参考资料/2012~《Clojure - Functional Programming for the JVM》~中文版/09~StructMap.md: -------------------------------------------------------------------------------- 1 | # StructMap 2 | 3 | StructMap 和普通的 map 类似,它的作用其实是用来模拟 java 里面的 javabean,所以它比普通的 map 的优点就是,它把一些常用的字段抽象到一个 map 里面去,这样你就不用一遍一遍的重复了。并且和 java 类似,他会帮你生成合适的 `equals` 和 `hashCode` 方法。并且它还提供方式让你可以创建比普通 map 里面的 hash 查找要快的字段访问方法(javabean 里面的 getXXX 方法)。 4 | 5 | `create-struct` 函数 和 `defstruct` 宏都可以用来定义 StructMap, defstruct 内部调用的也是 `create-struct` 。map 的 key 通常都是用 keyword 来指定的。看例子: 6 | 7 | ``` 8 | (def vehicle-struct (create-struct :make :model :year :color)) ; long way 9 | (defstruct vehicle-struct :make :model :year :color) ; short way 10 | ``` 11 | 12 | `struct` 实例化 StructMap 的一个对象,相当于 java 里面的 new 关键字. 你提供给 struct 的参数的顺序必须和你定义的时候提供的 keyword 的顺序一致,后面的参数可以忽略,如果忽略,那么对应 key 的值就是 nil。看例子: 13 | 14 | ``` 15 | (def vehicle (struct vehicle-struct "Toyota" "Prius" 2009)) 16 | ``` 17 | 18 | `accessor` 函数可以创建一个类似 java 里面的 getXXX 的方法,它的好处是可以避免 hash 查找,它比普通的 hash 查找要快。看例子: 19 | 20 | ``` 21 | ; Note the use of def instead of defn because accessor returns 22 | ; a function that is then bound to "make". 23 | (def make (accessor vehicle-struct :make)) 24 | (make vehicle) ; -> "Toyota" 25 | (vehicle :make) ; same but slower 26 | (:make vehicle) ; same but slower 27 | ``` 28 | 29 | 在创建一个 StructMap 之后,你还可以给它添加在定义 struct 的时候没有指定的 key。但是你不能删除定义时候已经指定的 key。 30 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore all 2 | * 3 | 4 | # Unignore all with extensions 5 | !*.* 6 | 7 | # Unignore all dirs 8 | !*/ 9 | 10 | .DS_Store 11 | 12 | # Logs 13 | logs 14 | *.log 15 | npm-debug.log* 16 | yarn-debug.log* 17 | yarn-error.log* 18 | 19 | # Runtime data 20 | pids 21 | *.pid 22 | *.seed 23 | *.pid.lock 24 | 25 | # Directory for instrumented libs generated by jscoverage/JSCover 26 | lib-cov 27 | 28 | # Coverage directory used by tools like istanbul 29 | coverage 30 | 31 | # nyc test coverage 32 | .nyc_output 33 | 34 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 35 | .grunt 36 | 37 | # Bower dependency directory (https://bower.io/) 38 | bower_components 39 | 40 | # node-waf configuration 41 | .lock-wscript 42 | 43 | # Compiled binary addons (https://nodejs.org/api/addons.html) 44 | build/Release 45 | 46 | # Dependency directories 47 | node_modules/ 48 | jspm_packages/ 49 | 50 | # TypeScript v1 declaration files 51 | typings/ 52 | 53 | # Optional npm cache directory 54 | .npm 55 | 56 | # Optional eslint cache 57 | .eslintcache 58 | 59 | # Optional REPL history 60 | .node_repl_history 61 | 62 | # Output of 'npm pack' 63 | *.tgz 64 | 65 | # Yarn Integrity file 66 | .yarn-integrity 67 | 68 | # dotenv environment variables file 69 | .env 70 | 71 | # next.js build output 72 | .next 73 | -------------------------------------------------------------------------------- /99~参考资料/2012~《Clojure - Functional Programming for the JVM》~中文版/14~递归.md: -------------------------------------------------------------------------------- 1 | # 递归 2 | 3 | 递归发生在一个函数直接或者间接调用自己的时候。一般来说递归的退出条件有检查一个集合是否为空,或者一个状态变量是否变成了某个特定的值(比如 0)。这一种情况一般利用连续调用集合里面的 `next` 函数来实现。后一种情况一般是利用`dec` 函数来递减某一个变量来实现。 4 | 5 | 如果递归的层次太深的话,那么可能会产生内存不足的情况。所以一些编程语言利用 "[tail call optimization](http://en.wikipedia.org/wiki/Tail_call)" (TCO)的技术来解决这个问题。但是目前 Java 和 Clojure 都不支持这个技术。在 Clojure 里面避免这个问题的一个办法是使用 special form:`loop` 和`recur`。另一个方法是使用[trampoline](http://clojure.github.com/clojure/clojure.core-api.html#clojure.core/trampoline) 函数。 6 | 7 | `loop`/`recur` 组合把一个看似递归的调用变成一个迭代 -- 迭代不需要占用栈空间。`loop` special form 跟`let` special form 类似的地方是它们都会建立一个本地 binding,但是同时它也建立一个递归点,而这个递归点就是 recur 的参数里面的那个函数。`loop`给这些 binding 一个初始值。对`recur` 的调用使得程序的控制权返回给`loop` 并且给那些本地 binding 赋了新的值。给 recur 传递的参数一定要和 loop 所创建的 binding 的个数一样。同样 recur 只能出现在 loop 这个 special form 的最后一行。 8 | 9 | ```clj 10 | (defn factorial-1 [number] 11 | "computes the factorial of a positive integer 12 | in a way that doesn't consume stack space" 13 | (loop [n number factorial 1] 14 | (if (zero? n) 15 | factorial 16 | (recur (dec n) (* factorial n))))) 17 | 18 | (println (time (factorial-1 5))) ; -> "Elapsed time: 0.071 msecs"\n120 19 | ``` 20 | 21 | `defn` 宏跟`loop` special form 一样也会建立一个递归点。`recur` special form 也可以被用在一个函数的最后一句用来把控制权返回到函数的第一句并以新的参数重新执行。 22 | 23 | 另外一种实现 factorial 函数的方法是使用`reduce` 函数。这个我们在 "[集合](#collections)" 那一节就已经介绍过了。它支持一种更加“函数”的方式来做这个事情。不过不幸的是,在这种情况下,它的效率要低一点。注意一下`range` 函数返回一个数字的范围,这个范围包括它的左边界,但是不包括它的右边界。 24 | 25 | ```clj 26 | (defn factorial-2 [number] (reduce * (range 2 (inc number)))) 27 | 28 | (println (time (factorial-2 5))) ; -> "Elapsed time: 0.335 msecs"\n120 29 | ``` 30 | 31 | 你可以把上面的`reduce` 换成`apply`, 可以得到同样的结果,但是 apply 要更慢一点。这也说明了我们要熟悉每个方法的特点的重要性,以在各个场合使用合适的函数。 32 | 33 | `recur` 不支持那种一个函数调用另外一个函数,然后那个函数再回调这个函数的这种递归。但是我们没有提到的`trampoline`函数是支持的。 34 | -------------------------------------------------------------------------------- /99~参考资料/2012~《Clojure - Functional Programming for the JVM》~中文版/20~元数据.md: -------------------------------------------------------------------------------- 1 | # 元数据 2 | 3 | Clojure 里面的元数据是附加到一个符号或者集合的一些数据,它们和符号或者集合的逻辑数据没有直接的关系。两个逻辑上一样的方法可以有不同的元数据。下面是一个有关扑克牌的例子 4 | 5 | ``` 6 | (defstruct card-struct :rank :suit) 7 | 8 | (def card1 (struct card-struct :king :club)) 9 | (def card2 (struct card-struct :king :club)) 10 | 11 | (println (== card1 card2)) ; same identity? -> false 12 | (println (= card1 card2)) ; same value? -> true 13 | 14 | (def card2 #^{:bent true} card2) ; adds metadata at read-time 15 | (def card2 (with-meta card2 {:bent true})) ; adds metadata at run-time 16 | (println (meta card1)) ; -> nil 17 | (println (meta card2)) ; -> {:bent true} 18 | (println (= card1 card2)) ; still same value despite metadata diff. -> true 19 | ``` 20 | 21 | 一些元数据是 Clojure 内部定义的。比如`:private` 它表示一个 Var 是否能被包外的函数访问。`:doc` 是一个 Var 的文档字符串。`:test` 元数据是一个 Boolean 值表示这个函数是否是一个测试函数。 22 | 23 | `:tag` 是一个字符串类型的类名或者一个`Class` 对象,表示一个 Var 在 Java 里面对应的类型,或者一个函数的返回值。这些被称为“类型提示” 。提供这些可以提高代码性能。如果你想查看你的 clojure 代码里面哪里使用反射来决定类型信息 -- 也就是说这里可能会有性能的问题,那么你可以设置全局变量`*warn-on-reflection*` 为`true`。 24 | 25 | 一些元数据会由 Clojure 的编译器自动地绑定到 Var 对象。`:file` 是定义这个 Var 的文件的名字。`:line` 是定义这个 Var 的行数。`:name` 是一个 Var 的名字的`Symbol` 对象。`:ns` 是一个`Namespace` 对象描述这个 Var 所在的名字空间。`:macro` 是一个标识符标识这个符号是不是一个宏。`:arglist` 是一个装有一堆 vector 的一个 list,表示一个函数所接受的所有的参数列表(前面在介绍函数的时候说过一个函数可以接受多个参数列表)。 26 | 27 | 函数以及宏,都是有一个`Var` 对象来表示的, 它们都有关联的元数据。比如输入这个在 REPL 里面:`(meta (var reverse))` 或者`^#'reverse`。输出结果应该下面这些类似(为了好看我加了换行缩进) 28 | 29 | ``` 30 | { 31 | :ns #, 32 | :name reverse, 33 | :file "core.clj", 34 | :line 630, 35 | :arglists ([coll]), 36 | :doc "Returns a seq of the items in coll in reverse order. Not lazy." 37 | } 38 | ``` 39 | 40 | clojure.repl 包里面的`source` 函数,利用元数据来获取一个指定函数的源代码,比如: 41 | 42 | ``` 43 | (source reverse) 44 | ``` 45 | 46 | 上面代码的输出应该是: 47 | 48 | ``` 49 | (defn reverse 50 | "Returns a seq of the items in coll in reverse order. Not lazy." 51 | [coll] 52 | (reduce conj nil coll)) 53 | ``` 54 | -------------------------------------------------------------------------------- /99~参考资料/2012~《Clojure - Functional Programming for the JVM》~中文版/13~迭代.md: -------------------------------------------------------------------------------- 1 | # 迭代 2 | 3 | 有很多方法可以遍历一个集合。 4 | 5 | 宏`dotimes` 会执行给定的表达式一定次数, 一个本地 binding 会被给定值:从 0 到一个给定的数值. 如果这个本地 binding 是不需要的 (下面例子里面的`card-number` ), 可以用下划线来代替,看例子: 6 | 7 | ```clj 8 | (dotimes [card-number 3] 9 | (println "deal card number" (inc card-number))) ; adds one to card-number 10 | ``` 11 | 12 | 注意下上面例子里面的`inc` 函数是为了让输出变成 1, 2, 3 而不是 0, 1, 2。上面代码的输出是这样的: 13 | 14 | ```clj 15 | deal card number 1 16 | deal card number 2 17 | deal card number 3 18 | ``` 19 | 20 | 宏`while` 会一直执行一个表达式只要指定的条件为 true. 下面例子里面的`while` 会一直执行,只要这个线程没有停: 21 | 22 | ```clj 23 | (defn my-fn [ms] 24 | (println "entered my-fn") 25 | (Thread/sleep ms) 26 | (println "leaving my-fn")) 27 | 28 | (let [thread (Thread. #(my-fn 1))] 29 | (.start thread) 30 | (println "started thread") 31 | (while (.isAlive thread) 32 | (print ".") 33 | (flush)) 34 | (println "thread stopped")) 35 | ``` 36 | 37 | 上面代码的输出是这样的: 38 | 39 | ``` 40 | started thread 41 | .....entered my-fn. 42 | .............leaving my-fn. 43 | thread stopped 44 | ``` 45 | 46 | ### List Comprehension 47 | 48 | 宏`for` 和`doseq` 可以用来做 list comprehension. 它们支持遍历多个集合 (最右边的最快) ,同时还可以做一些过滤用`:when 和` `:while。` 宏`for` 只接受一个表达式 , 它返回一个懒惰集合作为结果. 宏`doseq` 接受任意数量的表达式, 以有副作用的方式执行它们, 并且返回`nil`. 49 | 50 | 下面的例子会打印一个矩阵里面 所有的元素出来。它们会跳过 "B" 列 并且只输出小于 3 的那些行。我们会在“[序列](#sequences)”那一节介绍`dorun` , 它会强制提取 for 所返回的懒惰集合. 51 | 52 | ```clj 53 | (def cols "ABCD") 54 | (def rows (range 1 4)) ; purposely larger than needed to demonstrate :while 55 | 56 | (println "for demo") 57 | (dorun 58 | (for [col cols :when (not= col \B) 59 | row rows :while (< row 3)] 60 | (println (str col row)))) 61 | 62 | (println "\ndoseq demo") 63 | (doseq [col cols :when (not= col \B) 64 | row rows :while (< row 3)] 65 | (println (str col row))) 66 | ``` 67 | 68 | 上面的代码的输出是这样的: 69 | 70 | ``` 71 | for demo 72 | A1 73 | A2 74 | C1 75 | C2 76 | D1 77 | D2 78 | 79 | doseq demo 80 | A1 81 | A2 82 | C1 83 | C2 84 | D1 85 | D2 86 | ``` 87 | 88 | 宏`loop` 是一个 special form, 从它的名字你就可以猜出来它是用来遍历的. 它以及和它类似的`recur` 会在下一节介绍. 89 | -------------------------------------------------------------------------------- /99~参考资料/2012~《Clojure - Functional Programming for the JVM》~中文版/18~解构.md: -------------------------------------------------------------------------------- 1 | # 解构 2 | 3 | 解构可以用在一个函数或者宏的参数里面来把一个集合里面的一个或者几个元素抽取到一些本地 binding 里面去。它可以用在由`let` special form 或者`binding` 宏所创建的 binding 里面。 4 | 5 | 比如,如果我们有一个 vector 或者一个 list,我们想要获取这个集合里面的第一个元素和第三个元素的和。那么可以用下面两种办法,第二种解构的方法看起来要简单一点。 6 | 7 | ```clj 8 | (defn approach1 [numbers] 9 | (let [n1 (first numbers) 10 | n3 (nth numbers 2)] 11 | (+ n1 n3))) 12 | 13 | ; Note the underscore used to represent the 14 | ; second item in the collection which isn't used. 15 | (defn approach2 [[n1 _ n3]] (+ n1 n3)) 16 | 17 | (approach1 [4 5 6 7]) ; -> 10 18 | (approach2 [4 5 6 7]) ; -> 10 19 | ``` 20 | 21 | & 符合可以在解构里面用来获取集合里面剩下的元素。比如: 22 | 23 | ```clj 24 | (defn name-summary [[name1 name2 & others]] 25 | (println (str name1 ", " name2) "and" (count others) "others")) 26 | 27 | (name-summary ["Moe" "Larry" "Curly" "Shemp"]) ; -> Moe, Larry and 2 others 28 | ``` 29 | 30 | `:as` 关键字可以用来获取对于整个被解构的集合的访问。如果我们想要一个函数接受一个集合作为参数,然后要计算它的第一个元素与第三个元素的和占总和的比例,看下面的代码: 31 | 32 | ```clj 33 | (defn first-and-third-percentage [[n1 _ n3 :as coll]] 34 | (/ (+ n1 n3) (apply + coll))) 35 | 36 | (first-and-third-percentage [4 5 6 7]) ; ratio reduced from 10/22 -> 5/11 37 | ``` 38 | 39 | 解构也可以用来从 map 里面获取元素。假设我们有一个 map 这个 map 的 key 是月份,value 对应的是这个月的销售额。那么我们可以写一个函数来计算夏季的总销售额占全年销售额的比例: 40 | 41 | ```clj 42 | (defn summer-sales-percentage 43 | ; The keywords below indicate the keys whose values 44 | ; should be extracted by destructuring. 45 | ; The non-keywords are the local bindings 46 | ; into which the values are placed. 47 | [{june :june july :july august :august :as all}] 48 | (let [summer-sales (+ june july august) 49 | all-sales (apply + (vals all))] 50 | (/ summer-sales all-sales))) 51 | 52 | (def sales { 53 | :january 100 :february 200 :march 0 :april 300 54 | :may 200 :june 100 :july 400 :august 500 55 | :september 200 :october 300 :november 400 :december 600}) 56 | 57 | (summer-sales-percentage sales) ; ratio reduced from 1000/3300 -> 10/33 58 | ``` 59 | 60 | 我们一般使用和 map 里面 key 的名字一样的本地变量来对 map 进行解构,比如上面例子里面我们使用的`{june :june july :july august :august :as all}`. 这个可以使用`:keys`来简化。比如,`{:keys [june july august] :as all}`. 61 | -------------------------------------------------------------------------------- /99~参考资料/2012~《Clojure - Functional Programming for the JVM》~中文版/04~快速开始.md: -------------------------------------------------------------------------------- 1 | # 快速开始 2 | 3 | Clojure 是一个相对来说很新的语言。在经过一些年的努力之后,Clojure 的第一版是在 2007 年 10 月 16 日发布的。Clojure 的主要部分被称为 “Clojure proper” 或者 “core”。你可以从这里下载: http://clojure.org/downloads . 你也可以使用 [Leiningen](http://github.com/technomancy/leiningen/) 。最新的源代码可以从它的 Git 库下载. 4 | 5 | “Clojure Contrib“是一个大家共享的类库列表。其中有些类库是成熟的,被广泛使用的并且最终可能会被加入 Clojure Proper 的。但是也有些库不是很成熟,没有被广泛使用,所以也就不会被包含在 Conjure Proper 里面。所以 Clojure Proper 里面是鱼龙混杂,使用的时候要自己斟酌,文档在这里: http://richhickey.github.com/clojure-contrib/index.html 6 | 7 | 对于一个 Clojure Contrib,有三种方法可以得到对应的 jar 包. 首先你可以下载一个打包好的 jar 包。其次你可以用 maven 来自己打个 jar 包. Maven 可以从这里下载 http://maven.apache.org/ . 打包命令是 “ `mvn package` “. 再其次你可以用 ant. ant 可以从这里下载 http://ant.apache.org/ 。命令是: “ `ant -Dclojure.jar={path}` “. 8 | 9 | 要从最小的源代码来编译 clojure, 我们假设你已经安装了 [Git](http://git-scm.com/) 和 [Ant](http://ant.apache.org/) , 运行下面的命令来下载并且编译打包 Clojure Proper 和 Clojure Contrib: 10 | 11 | ``` 12 | git clone git://github.com/richhickey/clojure.git 13 | cd clojure 14 | ant clean jar 15 | cd .. 16 | git clone git://github.com/richhickey/clojure-contrib.git 17 | cd clojure-contrib 18 | ant -Dclojure.jar=../clojure/clojure.jar clean jar 19 | ``` 20 | 21 | 下一步,写一个脚本来运行 Read/Eval/Print Loop (REPL) 以及运行 Clojure 程序. 这个脚本通常被命名为”clj”. 怎么使用 REPL 我们等会再介绍. Windows 下面,最简单的 clj 脚本是这样的(UNIX, Linux 以及 Mac OS X 下面把 %1 改成 $1): 22 | 23 | ``` 24 | java -jar /path/clojure.jar %1 25 | ``` 26 | 27 | 这个脚本假定 `java` 在你的 `PATH` 环境变量里面. 为了让这个脚本更加有用: 28 | 29 | - 把经常使用的 JAR 包比如 “Clojure Contrib” 以及数据库 driver 添加到 classpath 里面去( `-cp` ). 30 | - 使 clj 更好用:用 [rlwrap](http://utopia.knoware.nl/~hlub/uck/rlwrap/) (利用 keystrokes 来支持的) 或者 [JLine](http://jline.sourceforge.net/) 来得到命令提示以及命令历史提示。 31 | - 添加一个启动脚本来设置一些特殊变量(比如 `*print-length*和` `*print-level*` ), 加载一些常用的、不再 `java.lang 里面的包` 加载一些常用的不再 `clojure.core` 里面的函数并且定义一些常用自定义的函数. 32 | 33 | 使用这个脚本来启动 REPL 我们会等会介绍. 用下面这个命令来运行一个 clojure 脚本(通常以 clj 为后缀名): 34 | 35 | ``` 36 | clj source-file-path 37 | ``` 38 | 39 | 更多细节看这里 http://clojure.org/getting_started 以及这里: http://clojure.org/repl_and_main 。同时 Stephen Gilardi 还提供了一个脚本: http://github.com/richhickey/clojure-contrib/raw/master/launchers/bash/clj-env-dir 。 40 | 41 | 为了更充分的利用机器的多核,你应该这样来调用: “ `java -server ...` “. 42 | 43 | 提供给 Clojure 的命令行参数被封装在预定义的变量 `*command-line-args*里面。` 44 | -------------------------------------------------------------------------------- /99~参考资料/2012~《Clojure - Functional Programming for the JVM》~中文版/22~并发.md: -------------------------------------------------------------------------------- 1 | # 并发 2 | 3 | Wikipedia 上面对于并发有个很精准的定义: 4 | 5 | > "Concurrency is a property of systems in which several computations are executing and overlapping in time, and potentially interacting with each other. The overlapping computations may be executing on multiple cores in the same chip, preemptively time-shared threads on the same processor, or executed on physically separated processors." 6 | 7 | 并发编程的主要挑战就在于管理对于共享的、可修改的状态的修改。 8 | 9 | 通过锁来对并发进行管理是非常难的。我们需要决定哪些对象需要加锁以及什么时候加锁。这还不算完,每次你修改代码或者添加新的代码的时候你都要重新审视下你的这些决定。如果一个开发人员忘记了去锁一个应该加锁的对象,或者锁的时机不对,一些非常糟糕的事情就会发生了。这些糟糕的事情包括[死锁](http://en.wikipedia.org/wiki/Deadlock)和[竞争条件](http://en.wikipedia.org/wiki/Race_condition);另一个方面如果你锁了一个不需要锁的对象,那么你的系统的性能则会下降。 10 | 11 | 为了更好地进行并发编程是很多开发人员选择 Clojure 的原因。Clojure 的所有的数据都是只读的,除非你显示的用[Var](#Vars),[Ref](http://java.ociweb.com/mark/clojure/article.html#Refs),[Atom](#Atoms) 和[Agent](#Agents)来标明它们是可以修改的。这些提供了安全的方法去管理共享状态,我们会在下一节:“[引用类型](#reference-typs)”里面更加详细地介绍。 12 | 13 | 用一个新线程来运行一个 Clojure 函数是非常简单的,不管它是内置的,还是自定义的,不管它是有名的还是匿名的。关于这个更详细的可以看上面有关[和 Java 的互操作的讨论](#java-interoperability)。 14 | 15 | 因为 Clojure 代码可以使用 java 里面的所有的类和接口,所以它可以使用 Java 的并发能力。Java 领域一个很棒的有关 java 并发编程的书: "[Java Concurrency In Practice](http://jcip.net/)". 这本书里面讲到了很多 java 里面如果做好并发编程的一些建议。但是要做到这些建议并不是一件很轻松的事情。在大多数情况下,使用 java 的引用类型比使用 java 里面并发要更简单。 16 | 17 | 除了引用类型,Clojure 还提供了其它一些函数来使你的并发编程更简单。 18 | 19 | `future` 宏把它的 body 里面的表达式在另外一个线程里面执行(这个线程来自于`CachedThreadPool`,[Agents](#Agents)(后面会介绍)用的也是这个). 这个对于那种运行时间比较长,而且一下子也不需要运行结果的程序来说比较有用。你可以通过 dereferencing 从`future`. 放回的对象来得到返回值。如果计算已经结束了,那么立马返回那个值;如果计算还没有结束,那么当前线程会 block 住,直到计算结束返回。因为这里使用了一个来自 Agent 线程池的线程, 所以我们要在一个适当的时机调用`shutdown-agents` 关闭这些线程,然后程序才能退出。 20 | 21 | 为了演示`future`的用法,我们加了一些 println 的方法调用,它能帮助我们观察方法执行的状态,注意输出的消息的顺序。 22 | 23 | ``` 24 | (println "creating future") 25 | (def my-future (future (f-prime 2))) ; f-prime is called in another thread 26 | (println "created future") 27 | (println "result is" @my-future) 28 | (shutdown-agents) 29 | ``` 30 | 31 | 如果`f-prime` 是一个比较耗时的方法的话, 那么输出应该是这样的: 32 | 33 | ``` 34 | creating future 35 | created future 36 | derivative entered 37 | result is 9.0 38 | ``` 39 | 40 | `pmap` 函数把一个函数作用到一个集合里面的所有的元素,和 map 不一样的是这个过程是完全并行的,所以如果你要调用的这个函数是非常耗时间的话,那么使用 pmap 将比使用 41 | 42 | `clojure.parallel` 名字空间里买你有好多方法可以帮助你并行化你的代码, 他们包括:`par`,`pdistinct`,`pfilter-dupes`,`pfilter-nils`,`pmax`,`pmin`,`preduce`,`psort`,`psummary` 和`pvec`. 43 | -------------------------------------------------------------------------------- /99~参考资料/2012~《Clojure - Functional Programming for the JVM》~中文版/07~变量.md: -------------------------------------------------------------------------------- 1 | # 变量 2 | 3 | Clojure 里面是不支持变量的。它跟变量有点像,但是在被赋值之前是不允许改的,包括:全局 binding, 线程本地(thread local)binding,以及函数内的本地 binding,以及一个表达式内部的 binding。 4 | 5 | `def` 这个 special form 定义一个全局的 binding,并且你还可以给它一个”root value” ,这个 root value 在所有的线程里面都是可见的,除非你给它赋了一个线程本地的值. `def` 也可以用来改变一个已经存在的 binding 的 root value —— 但是这是不被鼓励的,因为这会牺牲不可变数据所带来的好处。 6 | 7 | 函数的参数是只在这个函数内可见的本地 binding。 8 | 9 | `let` 这个 special form 创建局限于一个 当前 form 的 bindings. 它的第一个参数是一个 vector, 里面包含名字-表达式的对子。表达式的值会被解析然后赋给左边的名字。这些 binding 可以在这个 vector 后面的表达式里面使用。这些 binding 还可以被多次赋值以改变它们的值,let 命令剩下的参数是一些利用这个 binding 来进行计算的一些表达式。注意:如果这些表达式里面有调用别的函数,那么这个函数是无法利用 let 创建的这个 binding 的。 10 | 11 | 宏 `binding` 跟 `let 类似` , 但是它创建的本地 binding 会暂时地覆盖已经存在的全局 binding. 这个 binding 可以在创建这个 binding 的 form 以及这个 form 里面调用的函数里面都能看到。但是一旦跳出了这个 `binding` 那么被覆盖的全局 binding 的值会回复到之前的状态。 12 | 13 | 从 Clojure 1.3 开始, binding 只能用在 动态变量(dynamic var)上面了. 下面的例子演示了怎么定一个 dynamic var。另一个区别是 `let` 是串行的赋值的, 所以后面的 binding 可以用前面 binding 的值, 而 `binding` 是不行的. 14 | 15 | 要被用来定义成新的、本地线程的、用 binding 来定义的 binding 有它们自己的命名方式:她们以星号开始,以星号结束。在这篇文章里面你会看到: `*command-line-args*` , `*agent*` , `*err*` , `*flush-on-newline*` , `*in*` , `*load-tests*` , `*ns*` , `*out*` , `*print-length*` , `*print-level*` and `*stack-trace-depth*` .要使用这些 binding 的函数会被这些 binding 的值影响的。比如给*out*一个新的 binding 会改变 println 函数的输出终端。 16 | 17 | 下面的例子介绍了 `def` , `let` 和 `binding` 的用法。 18 | 19 | ``` 20 | (def ^:dynamic v 1) ; v is a global binding 21 | 22 | (defn f1 [] 23 | (println "f1: v =" v)) ; global binding 24 | 25 | (defn f2 [] 26 | (println "f2: before let v =" v) ; global binding 27 | (let [v 2] ; creates local binding v that shadows global one 28 | (println "f2: in let, v =" v) ; local binding 29 | (f1)) 30 | (println "f2: after let v =" v)) ; global binding 31 | 32 | (defn f3 [] 33 | (println "f3: before binding v =" v) ; global binding 34 | (binding [v 3] ; same global binding with new, temporary value 35 | (println "f3: in binding, v =" v) ; global binding 36 | (f1)) 37 | (println "f3: after binding v =" v)) ; global binding 38 | 39 | (defn f4 [] 40 | (def v 4)) ; changes the value of the global binding 41 | 42 | (f2) 43 | (f3) 44 | (f4) 45 | (println "after calling f4, v =" v) 46 | ``` 47 | 48 | 上面代码的输出是这样的: 49 | 50 | ``` 51 | f2: before let v = 1 52 | f2: in let, v = 2 53 | f1: v = 1 (let DID NOT change value of global binding) 54 | f2: after let v = 1 55 | f3: before binding v = 1 56 | f3: in binding, v = 3 57 | f1: v = 3 (binding DID change value of global binding) 58 | f3: after binding v = 1 (value of global binding reverted back) 59 | after calling f4, v = 4 60 | ``` 61 | -------------------------------------------------------------------------------- /99~参考资料/2012~《Clojure - Functional Programming for the JVM》~中文版/24~编译.md: -------------------------------------------------------------------------------- 1 | # 编译 2 | 3 | 当 clojure 的源代码文件被当作脚本文件执行的时候,它们是在运行时被编译成 java 的 bytecode 的。同时我们也可以提前编译(AOT ahead-of-time)它们成 java bytecode。这会缩短 clojure 程序的启动时间,并且产生的.class 文件还可以给 java 程序使用。我们推荐按照下面的步骤来做: 4 | 5 | 1. 为你要编译的文件选择一个名字空间,比如:`com.ociweb.talk`。 6 | 2. 在父目录里面创建两个目录: "`src`" 和 "`classes`" 。 7 | 3. 使你的其中一个文件的文件名和包名的最后一段相同,比如:`talk.clj`。 8 | 4. 把你的源文件放在 "`src`" 目录下面,并且创建和名字空间一样的目录层级,比如:`src/com/ociweb/talk.clj`。 9 | 5. 在你的源代码的最上面给你的文件指定名字空间,并且包含:gen-class 标记:`(ns com.ociweb.talk (:gen-class))` 10 | 6. 在你的主源文件里面,使用`load` 函数来加载同一个目录下面的其它源文件,比如,如果`more.clj` 在目录`src/com/ociweb` 的子目录 "`talk`"下面那么用这个语句来加载`(load "talk/more")`. 11 | 7. 在其它的源文件里面, 使用`in-ns` 函数来设置他们的名字空间. 比如, 在 more.clj 文件上面指定名字空间:`(in-ns 'com.ociweb.talk)。` 12 | 8. 把 "`src`" 和 "`classes`" 目录添加到 REPL 的 classpath 里面去。如果你使用了一个脚本来运行 REPL, 那么修改那个脚本。 13 | 9. 启动一个 REPL。 14 | 10. 使用`compile `函数来编译一个给定名字空间的 clojure 文件:`(compile '*namespace*)。比如`:`(compile 'com.ociweb.talk)`. 15 | 16 | 这些步骤会为每个函数创建一个单独的.class 文件。他们会被写到 "`classes`" 文件夹下对应的子文件夹下面去。 17 | 18 | 如果这个被编译的名字空间有一个叫做-`main`的函数,那么你可以把它当作 java 的主类的运行。命令行参数会被当作参数传递给这个函数。比如,如果`talk.clj` 包含一个叫`-main` 的函数,你可以用下面的命令来运行: 19 | 20 | ``` 21 | java -classpath path/classes:path/clojure.jar com.ociweb.talk args 22 | ``` 23 | 24 | ### 在 Java 里面调用 Clojure 25 | 26 | 提前编译的 Clojure 函数如果是静态的函数的话,那么它们可以被 java 程序调用。可以通过把函数的元数据项:`:static` 设置为`true` 来达到这个目的。语法是这样的: 27 | 28 | ``` 29 | (ns namespace 30 | (:gen-class 31 | :methods [#^{:static true} [function-name [param-types] return-type]])) 32 | ``` 33 | 34 | 让我们看一个例子:下面是一个名字叫做 Demo.clj 的文件,它的路径是`src/com/ociweb/clj。` 35 | 36 | ``` 37 | (ns com.ociweb.clj.Demo 38 | (:gen-class 39 | :methods [#^{:static true} [getMessage [String] String]])) 40 | 41 | # Note the hyphen at the beginning of the function name! 42 | (defn -getMessage [name] 43 | (str "Hello, " name "!")) 44 | ``` 45 | 46 | 下面是一个叫做`Main.java` 的 java 文件,它和`src` 以及`classes` 在同一个目录。 47 | 48 | ``` 49 | import com.ociweb.clj.Demo; // class created by compiling Clojure source file 50 | 51 | public class Main { 52 | 53 | public static void main(String[] args) { 54 | String message = Demo.getMessage("Mark"); 55 | System.out.println(message); 56 | } 57 | } 58 | ``` 59 | 60 | 下面是编译并且运行它的步骤: 61 | 62 | 1. cd 到包含`src` 和`classes` 的目录。 63 | 2. 通过 "`clj`"命令来打开一个 REPL。 64 | 3. 运行 "`(compile 'com.ociweb.clj.Demo)`". 65 | 4. 退出 REPL (ctrl-d 或者 ctrl-c). 66 | 5. 运行 "`javap -classpath classes com.ociweb.clj.Demo`" 来查看生成的 class 文件里面的方法。 67 | 6. 运行 "`javac -cp classes Main.java`". 68 | 7. 运行 "`java -cp .:classes:*path*/clojure.jar Main.java`". 注意 Windows 下面的路径分隔符是分号而不是冒号。 69 | 8. 输出应该是 "`Hello, Mark!`". 70 | 71 | Clojure 还有一些更加高级的编译特性。更多细节可以参考宏`gen-class` 的文档以及[http://clojure.org/compilation/](http://clojure.org/compilation)。 72 | -------------------------------------------------------------------------------- /02~语法使用/02~核心特性/序列抽象/2024.11~序列抽象.md: -------------------------------------------------------------------------------- 1 | # Clojure 序列抽象 2 | 3 | ## 1. 什么是序列抽象 4 | 5 | 序列(Sequence)是 Clojure 中最重要的抽象之一,它提供了一个统一的方式来处理各种类型的集合和数据源。序列抽象通过 `ISeq` 接口实现,这个接口定义了访问任何序列所需的最小操作集。 6 | 7 | ### 1.1 核心概念 8 | 9 | 序列抽象基于以下几个简单的操作: 10 | 11 | - `first`: 获取序列的第一个元素 12 | - `rest`: 获取除第一个元素外的其余元素 13 | - `cons`: 在序列的开头添加一个元素 14 | 15 | 所有的序列操作都建立在这些基本功能之上。 16 | 17 | ### 1.2 为什么需要序列抽象? 18 | 19 | 序列抽象的优势: 20 | 21 | 1. **统一性**: 相同的函数可以处理不同的数据结构 22 | 2. **组合性**: 序列操作可以轻松组合 23 | 3. **惰性**: 支持无限序列和内存效率处理 24 | 4. **表达力**: 简洁地表达复杂的数据转换 25 | 26 | ## 2. 创建序列 27 | 28 | ### 2.1 基本创建方法 29 | 30 | ```clojure 31 | ;; 从现有集合创建 32 | (seq [1 2 3]) ; => (1 2 3) 33 | (seq '(1 2 3)) ; => (1 2 3) 34 | (seq #{1 2 3}) ; => (1 2 3) 35 | (seq {:a 1 :b 2}) ; => ([:a 1] [:b 2]) 36 | 37 | ;; 生成序列 38 | (range 5) ; => (0 1 2 3 4) 39 | (range 1 5) ; => (1 2 3 4) 40 | (range 1 10 2) ; => (1 3 5 7 9) 41 | ``` 42 | 43 | ### 2.2 无限序列生成器 44 | 45 | ```clojure 46 | ;; repeat: 重复单个值 47 | (take 5 (repeat 1)) ; => (1 1 1 1 1) 48 | 49 | ;; repeatedly: 重复执行函数 50 | (take 5 (repeatedly #(rand-int 10))) ; => (3 7 2 9 4) 51 | 52 | ;; iterate: 重复应用函数 53 | (take 5 (iterate inc 0)) ; => (0 1 2 3 4) 54 | (take 5 (iterate #(* % 2) 1)) ; => (1 2 4 8 16) 55 | 56 | ;; cycle: 循环一个序列 57 | (take 7 (cycle [1 2 3])) ; => (1 2 3 1 2 3 1) 58 | ``` 59 | 60 | ## 3. 序列操作函数 61 | 62 | ### 3.1 基本操作 63 | 64 | ```clojure 65 | ;; first/second/last 66 | (first [1 2 3]) ; => 1 67 | (second [1 2 3]) ; => 2 68 | (last [1 2 3]) ; => 3 69 | 70 | ;; rest vs next 71 | (rest [1]) ; => () 72 | (next [1]) ; => nil 73 | (rest []) ; => () 74 | (next []) ; => nil 75 | 76 | ;; cons vs conj 77 | (cons 0 [1 2 3]) ; => (0 1 2 3) 78 | (conj [1 2 3] 4) ; => [1 2 3 4] 79 | ``` 80 | 81 | ### 3.2 转换操作 82 | 83 | ```clojure 84 | ;; map: 转换每个元素 85 | (map inc [1 2 3]) ; => (2 3 4) 86 | (map + [1 2 3] [4 5 6]) ; => (5 7 9) 87 | 88 | ;; mapcat: map + concat 89 | (mapcat reverse [[1 2] [3 4]]) ; => (2 1 4 3) 90 | 91 | ;; 使用匿名函数 92 | (map #(* % %) [1 2 3]) ; => (1 4 9) 93 | ``` 94 | 95 | ### 3.3 过滤操作 96 | 97 | ```clojure 98 | ;; filter: 保留满足条件的元素 99 | (filter even? (range 10)) ; => (0 2 4 6 8) 100 | 101 | ;; remove: 删除满足条件的元素 102 | (remove even? (range 10)) ; => (1 3 5 7 9) 103 | 104 | ;; take/drop 105 | (take 3 (range 10)) ; => (0 1 2) 106 | (drop 3 (range 10)) ; => (3 4 5 6 7 8 9) 107 | 108 | ;; take-while/drop-while 109 | (take-while #(< % 5) (range 10)) ; => (0 1 2 3 4) 110 | (drop-while #(< % 5) (range 10)) ; => (5 6 7 8 9) 111 | ``` 112 | 113 | ### 3.4 规约操作 114 | 115 | ```clojure 116 | ;; reduce: 将序列规约为单个值 117 | (reduce + [1 2 3 4 5]) ; => 15 118 | (reduce (fn [acc x] (if (even? x) 119 | (conj acc x) 120 | acc)) 121 | [] 122 | (range 10)) ; => [0 2 4 6 8] 123 | 124 | ;; reductions: 显示规约的中间结果 125 | (reductions + [1 2 3 4 5]) ; => (1 3 6 10 15) 126 | ``` 127 | -------------------------------------------------------------------------------- /99~参考资料/2012~《Clojure - Functional Programming for the JVM》~中文版/17~输入输出.md: -------------------------------------------------------------------------------- 1 | # 输入输出 2 | 3 | Clojure 提供了很少的方法来进行输入/输出的操作。因为我们在 Clojure 代码里面可以很轻松的使用 java 里面的 I/O 操作方法。但是`clojure.java.io`库使得使用 java 的 I/O 方法更加简单。 4 | 5 | 这些预定义的 special symbols`*in*`,`*out*` 以及`*err*` 默认被设定成 stdin, stdout 以及 stderr 。如果要 flush`*out*`,里面的输出,使用 `(flush)`方法,效果和`(.flush *out*)`一样。当然这些 symbol 的 binding 是可以改变的。比如你可以把输出重定向到 "`my.log`"文件里面去。看下面的例子: 6 | 7 | ``` 8 | (binding [*out* (java.io.FileWriter. "my.log")] 9 | ... 10 | (println "This goes to the file my.log.") 11 | ... 12 | (flush)) 13 | ``` 14 | 15 | `print` 可以打印任何对象的字符串表示到*out*,并且在两个对象之间加一个空格。 16 | 17 | `println` 函数和`print`类似,但是它会在最后加一个 newline 符号。默认的话它还会有一个 flush 的动作。这个默认动作可以通过把 special symbol`*flush-on-newline*` 设成`false`来取消掉。 18 | 19 | `newline` 函数写一个 newline 符号`*out*` 流里面去。在调用`print` 函数后面手动调用`newline` 和直接调用`println`的效果是一样的。 20 | 21 | `pr` 与`prn` 是和`print` 与`println` 想对应的一对函数, 但是他们输出的形式可以被 Clojure reader 去读取。它们对于把 Clojure 的对象进行序列化的时候比较有用。默认情况下它们不会打印数据的元数据。可以通过把 special symbol`*print-meta*` 设置成`true`来调整这个行为。 22 | 23 | 下面的例子演示了我们提到的四个打印方法。注意使用 print 和 pr 输出的字符串的不同之处。 24 | 25 | ``` 26 | (let [obj1 "foo" 27 | obj2 {:letter \a :number (Math/PI)}] ; a map 28 | (println "Output from print:") 29 | (print obj1 obj2) 30 | 31 | (println "Output from println:") 32 | (println obj1 obj2) 33 | 34 | (println "Output from pr:") 35 | (pr obj1 obj2) 36 | 37 | (println "Output from prn:") 38 | (prn obj1 obj2)) 39 | ``` 40 | 41 | 上面代码的输出是这样的: 42 | 43 | ``` 44 | Output from print: 45 | foo {:letter a, :number 3.141592653589793}Output from println: 46 | foo {:letter a, :number 3.141592653589793} 47 | Output from pr: 48 | "foo" {:letter \a, :number 3.141592653589793}Output from prn: 49 | "foo" {:letter \a, :number 3.141592653589793} 50 | ``` 51 | 52 | 所有上面讨论的几个打印函数都会在它们的参数之间加一个空格。你可以通过`str` 函数来预先组装好要打印的字符串来避免这个行为,看下面例子: 53 | 54 | ``` 55 | (println "foo" 19) ; -> foo 19 56 | (println (str "foo" 19)) ; -> foo19 57 | ``` 58 | 59 | `print-str`,`println-str`,`pr-str` 以及`prn-str` 函数`print`,`println`,`pr` 跟`prn` 类似, 只是它们返回一个字符串,而不是把他们打印出来。 60 | 61 | `printf` 函数和`print` 类似。但是它接受一个 format 字符串。`format` 函数和`printf`, 类似,只是它是返回一个字符串而不是打印出来。 62 | 63 | 宏`with-out-str` 把它的方法体里面的所有输出汇总到一个字符串里面并且返回。 64 | 65 | `with-open` 可以自动关闭所关联的连接(.close)方法,这对于那种像文件啊,数据库连接啊,比较有用,它有点像 C#里面的 using 语句。 66 | 67 | `line-seq` 接受一个`java.io.BufferedReader` 参数,并且返回一个 LazySeq, 这个 LazySeq 包含所有的一行一行由 BufferedReader 读出的文本。返回一个 LazySeq 的好处在于,它不用马上读出文件的所有的内容,这会占用太大的内存。相反,它只需要在需要使用的时候每次读一行出来即可。 68 | 69 | 下面的例子演示了`with-open` 和`line-seq`的用法。它读出一个文件里面所有的行,并且打印出包含某个关键字的那些行。 70 | 71 | ``` 72 | (use '[clojure.java.io :only (reader)]) 73 | 74 | (defn print-if-contains [line word] 75 | (when (.contains line word) (println line))) 76 | 77 | (let [file "story.txt" 78 | word "fur"] 79 | 80 | ; with-open will close the reader after 81 | ; evaluating all the expressions in its body. 82 | (with-open [rdr (reader file)] 83 | (doseq [line (line-seq rdr)] (print-if-contains line word)))) 84 | ``` 85 | 86 | `slurp` 函数把一个文件里面的所有的内容读进一个字符串里面并且返回。`spit` 把一个字符串写进一个文件里面然后关闭这个文件。 87 | 88 | 这篇文章只是大概过了一下 clojure 的 io 里面提供了哪些函数来进行 I/O 操作。大家可以看下 clojure 源文件:`clojure/java/io.clj` 以了解其它一些函数。 89 | -------------------------------------------------------------------------------- /99~参考资料/《Clojure 官方教程》/03~Sequential Collections.md: -------------------------------------------------------------------------------- 1 | # Sequential Collections 2 | 3 | Clojure collections "collect" values into compound values. There are four key Clojure collection types: vectors, lists, sets, and maps. Of those four collection types, vectors and lists are ordered. 4 | 5 | ## Vectors 6 | 7 | Vectors are an indexed, sequential data structure. Vectors are represented with `[ ]` like this: 8 | 9 | ``` 10 | [1 2 3] 11 | ``` 12 | 13 | ### Indexed access 14 | 15 | "Indexed" means that elements of a vector can be retrieved by index. In Clojure (as in Java), indexes start at 0, not 1. Use the `get` function to retrieve an element at an index: 16 | 17 | ``` 18 | user=> (get ["abc" false 99] 0) 19 | "abc" 20 | user=> (get ["abc" false 99] 1) 21 | false 22 | ``` 23 | 24 | Calling get with an invalid index returns `nil`: 25 | 26 | ``` 27 | user=> (get ["abc" false 99] 14) 28 | nil 29 | ``` 30 | 31 | ### `count` 32 | 33 | All Clojure collections can be counted: 34 | 35 | ``` 36 | user=> (count [1 2 3]) 37 | 3 38 | ``` 39 | 40 | ### Constructing 41 | 42 | In addition to the literal `[ ]` syntax, Clojure vectors can be created with the `vector` function: 43 | 44 | ``` 45 | user=> (vector 1 2 3) 46 | [1 2 3] 47 | ``` 48 | 49 | ### Adding elements 50 | 51 | Elements are added to a vector with `conj` (short for conjoin). Elements are always added to a vector at the end: 52 | 53 | ``` 54 | user=> (conj [1 2 3] 4 5 6) 55 | [1 2 3 4 5 6] 56 | ``` 57 | 58 | ### Immutability 59 | 60 | Clojure collections share important properties of simple values like strings and numbers, such as immutability and equality comparison by value. 61 | 62 | For example, lets create a vector and modify it with `conj`. 63 | 64 | ``` 65 | user=> (def v [1 2 3]) 66 | #'user/v 67 | user=> (conj v 4 5 6) 68 | [1 2 3 4 5 6] 69 | ``` 70 | 71 | Here `conj` returned a new vector but if we examine the original vector, we see it’s unchanged: 72 | 73 | ``` 74 | user=> v 75 | [1 2 3] 76 | ``` 77 | 78 | Any function that "changes" a collection returns a new instance. Your program will need to remember or pass along the changed instance to take advantage of it. 79 | 80 | ## Lists 81 | 82 | Lists are sequential linked lists that add new elements at the head of the list, instead of at the tail like vectors. 83 | 84 | ### Constructing 85 | 86 | Because lists are evaluated by invoking the first element as a function, we must quote a list to prevent evaluation: 87 | 88 | ``` 89 | (def cards '(10 :ace :jack 9)) 90 | ``` 91 | 92 | Lists are not indexed so they must be walked using `first` and `rest`. 93 | 94 | ``` 95 | user=> (first cards) 96 | 10 97 | user=> (rest cards) 98 | '(:ace :jack 9) 99 | ``` 100 | 101 | ### Adding elements 102 | 103 | `conj` can be used to add elements to a list just as with vectors. However, `conj` always adds elements where it can be done in constant time for the data structure. In the case of lists, elements are added at the front: 104 | 105 | ``` 106 | user=> (conj cards :queen) 107 | (:queen 10 :ace :jack 9) 108 | ``` 109 | 110 | ### Stack access 111 | 112 | Lists can also be used as a stack with peek and pop: 113 | 114 | ``` 115 | user=> (def stack '(:a :b)) 116 | #'user/stack 117 | user=> (peek stack) 118 | :a 119 | user=> (pop stack) 120 | (:b) 121 | ``` 122 | -------------------------------------------------------------------------------- /99~参考资料/2015~《Brave Clojure》/04~深入探讨核心功能.md: -------------------------------------------------------------------------------- 1 | > [原文地址](https://www.braveclojure.com/core-functions-in-depth/) 2 | 3 | # 深入探讨核心功能 4 | 5 | 如果你和我一样是《吸血鬼日记》(The Vampire Diaries)这部以青少年为中心的准肥皂剧的忠实粉丝,你一定还记得主人公埃琳娜开始质疑她那苍白而神秘的暗恋对象的行为的那一集:"为什么我擦伤了膝盖,他就立刻消失得无影无踪?"、"为什么我划破了手指,他的脸就变成了怪异的死亡面具?"等等。 6 | 7 | 如果您已经开始使用 Clojure 的核心函数,您可能会问自己类似的问题。"为什么 `map` 返回的是一个列表,而我给它的是一个向量?"、"为什么 `reduce` 会把我的 map 当作一个向量列表?"等等。(不过,有了 Clojure,你至少不用永远沉浸在作为一个 17 岁少年的深刻的生存恐惧中了)。 8 | 9 | 在本章中,你将了解到 Clojure 的深邃、黑暗、嗜血、超自然-_咳咳_ 我是说,在本章中,你将了解到 Clojure 的抽象编程底层概念,以及序列和集合抽象。你还将学习懒序列。这将为你提供阅读未用过的函数文档所需的基础知识,并让你在尝试使用这些函数时了解发生了什么。 10 | 11 | 接下来,您将获得更多使用函数的经验。您将学习如何使用函数 `map`、`reduce`、`into`、`conj`、`concat`、`some`、`filter`、`take`、`drop`、`sort`、`sort-by` 和 `identity` 来处理列表、矢量、映射和集合。您还将学习如何使用 `apply`、`partial` 和 `complement` 创建新函数。所有这些信息将帮助您了解如何以 Clojure 的方式做事,并为您编写自己的代码以及阅读和学习他人的项目打下坚实的基础。 12 | 13 | 最后,您将学习如何解析和查询 CSV 吸血鬼数据,以确定您的家乡潜伏着哪些吸血鬼。 14 | 15 | # 从编程到抽象 16 | 17 | 为了理解抽象编程,让我们将 Clojure 与一种并非基于该原则构建的语言进行比较:Emacs Lisp (elisp)。在 elisp 中,您可以使用 mapcar 函数导出一个新 list,这与您在 Clojure 中使用 map 的方式类似。但是,如果您想在 elisp 中的散列映射(类似于 Clojure 的 map 数据结构)上进行映射,则需要使用 maphash 函数,而在 Clojure 中,您仍然可以只使用 map 函数。换句话说,elisp 使用两个不同的、特定于数据结构的函数来实现 map 操作,而 Clojure 只使用一个。在 Clojure 中,您还可以在 map 上调用 reduce ,而 elisp 并没有提供用于还原散列 map 的函数。 18 | 19 | 原因在于 Clojure 是根据序列抽象而非特定数据结构来定义 map 和 reduce 函数的。只要数据结构响应核心序列操作(函数 first、rest 和 cons ,我们稍后将仔细研究),它就可以免费使用 map、reduce 和大量其他序列函数。这就是 Clojurists 所说的抽象编程,也是 Clojure 哲学的核心原则。 20 | 21 | 我认为抽象是命名的操作集合。如果你能在一个对象上执行抽象的所有操作,那么这个对象就是抽象的一个实例。即使在编程之外,我也是这样认为的。例如,电池抽象包括 "将导电介质连接到阳极和阴极 "的操作,而该操作的输出是电流。电池是用锂还是土豆做的并不重要。只要它响应了定义电池的一系列操作,它就是电池。 22 | 23 | 同样,map 并不关心列表、向量、集合和映射是如何实现的。它只关心能否对它们执行序列操作。让我们来看看 map 是如何在序列抽象中定义的,这样您就可以理解一般的抽象编程了。 24 | 25 | ## 将列表、向量、集合和映射视为序列 26 | 27 | 如果将 `map` 操作与任何编程语言,甚至与编程完全无关,那么它的基本行为就是使用函数 ƒ 从现有序列 x 推导出一个新序列 y,即 y1 = ƒ(x1),y2 = ƒ(x2),......yn = ƒ(xn)。图 4-1 展示了如何将映射应用到序列中。 28 | 29 | ![img](https://www.braveclojure.com/assets/images/cftbat/core-functions-in-depth/mapping.png) 30 | 31 | 图 4-1: 可视化映射 32 | 33 | 这里的序列是指按线性顺序排列的元素集合,而不是无序集合或节点之间没有前后关系的图。图 4-2 展示了如何将序列可视化,与上述其他两个集合形成鲜明对比。 34 | 35 | ![img](https://www.braveclojure.com/assets/images/cftbat/core-functions-in-depth/collections.png) 36 | 37 | 图 4-2: 顺序和非顺序集合 38 | 39 | 在对映射和序列的描述中,没有提到列表、向量或其他具体的数据结构。Clojure 的设计目的是让我们尽可能以这种抽象的方式思考和编程,它通过以数据结构抽象的方式实现函数来实现这一点。在本例中,`map` 是根据序列抽象定义的。在对话中,您会说 `map`、`reduce` 和其他序列函数取一个序列,甚至取一个 seq。事实上,Clojurists 通常使用 seq 来代替 sequence,使用 seq 函数和 seq 库等术语来指代执行顺序操作的函数。无论你使用 sequence 还是 seq,你都是在表明相关的数据结构将被当作序列来处理,而在此上下文中,它的真实本质是什么并不重要。 40 | 41 | 如果核心序列函数 `first`、`rest` 和 `cons` 在数据结构上工作,那么可以说该数据结构实现了序列抽象。列表、向量、集合和映射都实现了序列抽象,因此它们都能与 `map` 一起工作,如图所示: 42 | 43 | ```clj 44 | (defn titleize 45 | [topic] 46 | (str topic " for the Brave and True")) 47 | 48 | (map titleize ["Hamsters" "Ragnarok"]) 49 | ; => ("Hamsters for the Brave and True" "Ragnarok for the Brave and True") 50 | 51 | (map titleize '("Empathy" "Decorating")) 52 | ; => ("Empathy for the Brave and True" "Decorating for the Brave and True") 53 | 54 | (map titleize #{"Elbows" "Soap Carving"}) 55 | ; => ("Elbows for the Brave and True" "Soap Carving for the Brave and True") 56 | 57 | (map #(titleize (second %)) {:uncomfortable-thing "Winking"}) 58 | ; => ("Winking for the Brave and True") 59 | ``` 60 | 61 | 前两个示例表明,`map` 对向量和列表的作用相同。第三个示例表明,`map` 可用于无排序集合。在第四个示例中,您必须在匿名函数的参数上调用 `second` 才能将其标题化,因为参数是一个 map。我很快会解释原因,但首先让我们看看定义序列抽象的三个函数。 62 | 63 | ## 第一、第二和第三点 64 | -------------------------------------------------------------------------------- /99~参考资料/2012~《Clojure - Functional Programming for the JVM》~中文版/19~命名空间.md: -------------------------------------------------------------------------------- 1 | # 命名空间 2 | 3 | Java 用 class 来组织方法,用包来组织 class。Clojure 用名字空间来组织事物。“事物”包括 Vars, Refs, Atoms, Agents, 函数, 宏 以及名字空间本。 4 | 5 | 符号(Symbols)是用来给函数、宏以及 binding 来分配名字的。符号被划分到名字空间里面去了。任何时候总有一个默认的名字空间,初始化的时候这个默认的名字空间是“user”,这个默认的名字空间的值被保存在特殊符号`*ns*`.里面。默认的名字空间可以通过两种方法来改变。`in-ns` 函数只是改变它而已. 而`ns` 宏则做得更多。其中一件就是它会使得`clojure.core` 名字空间里面的符号在新的名字空间里面都可见 (使用`refer` 命令). `ns` 宏的其它一些特性我们会在后面介绍。 6 | 7 | "user" 这个名字空间提供对于`clojure.core` 这个名字空间里面所有符号的访问。同样道理对于那些通过`ns` 宏来改变成默认名字空间的名字空间里面也是可以看到 clojure.core 里面的所有的函数的。 8 | 9 | 如果要访问哪些不在默认名字空间里面的符号、函数,那么你必须要指定全限定的完整名字。比如 clojure.string 包里面定义了一个`join` 函数。它把多个字符串用一个分隔符隔开然后连起来,返回这个连起来的字符串。它的全限定名是`clojure.string/join`. 10 | 11 | `require` 函数可以加载 Clojure 库。它接受一个或者多一个名字空间的名字(注意前面的单引号) 12 | 13 | ``` 14 | (require 'clojure.string) 15 | ``` 16 | 17 | 这个只会加载这个类库。这里面的名字还必须是一个全限定的报名,包名之间用.分割。注意,clojure 里面名字空间和方法名之间的分隔符是/而不是 java 里面使用的. 。比如: 18 | 19 | ```clj 20 | (clojure.string/join "$" [1 2 3]) ; -> "1$2$3" 21 | ``` 22 | 23 | `alias` 函数给一个名字空间指定一个别名以减少我们打字工作。当然这个别名的定义只在当前的名字空间里面有效。比如: 24 | 25 | ``` 26 | (alias 'su 'clojure.string) 27 | (su/join "$" [1 2 3]) ; -> "1$2$3" 28 | ``` 29 | 30 | `refer` 函数使得指定的名字空间里面的函数在当前名字空间里面可以访问(不用使用全限定名字)。一个特例就是如果当前名字空间有那个名字空间一样的名字,那么你访问的时候还是要制定名字空间的。看例子: 31 | 32 | ``` 33 | (refer 'clojure.string) 34 | ``` 35 | 36 | 现在,上面的代码可以写成。 37 | 38 | ``` 39 | (join "$" [1 2 3]) ; -> "1$2$3" 40 | ``` 41 | 42 | 我们通常把`require` 和`refer` 结合使用, 所以 clojure 提供了一个`use` ,它相当于 require 和 refer 的简洁形式。 43 | 44 | ```clj 45 | (use 'clojure.string) 46 | ``` 47 | 48 | 我们前面提到过的 `ns` 宏, 可以改变当前的默认名字空间。我们通常在一个源代码的最上面指定这个。它支持这些指令:`:require`,`:use`和`:import` (用来加载 Java 类的) 这些其实是它们对应的函数的另外一种方式。我们鼓励使用这些指令而不是那些函数。在下面的例子里面 注意`:as` 给名字空间创建了一个别名。同时注意使用`:only` 指令来加载 Clojure 库的一部分。 49 | 50 | ```clj 51 | (ns com.ociweb.demo 52 | (:require [clojure.string :as su]) 53 | ; assumes this dependency: [org.clojure/math.numeric-tower "0.0.1"] 54 | (:use [clojure.math.numeric-tower :only (gcd, sqrt)]) 55 | (:import (java.text NumberFormat) (javax.swing JFrame JLabel))) 56 | 57 | (println (su/join "$" [1 2 3])) ; -> 1$2$3 58 | (println (gcd 27 72)) ; -> 9 59 | (println (sqrt 5)) ; -> 2.23606797749979 60 | (println (.format (NumberFormat/getInstance) Math/PI)) ; -> 3.142 61 | 62 | ; See the screenshot that follows this code. 63 | (doto (JFrame. "Hello") 64 | (.add (JLabel. "Hello, World!")) 65 | (.pack) 66 | (.setDefaultCloseOperation JFrame/EXIT_ON_CLOSE) 67 | (.setVisible true)) 68 | ``` 69 | 70 | `create-ns` 函数可以创建一个新的名字空间。但是不会把它变成默认的名字空间。`def` 在当前名字定义一个符号,你同时还可以给它一个初始值。`intern` 函数在一个指定名字空间里面定义一个符号(如果这个符号不存在的话) ,同时还可以给它指定一个默认值。注意在`intern`里面符号的名字要括起来,但是在`def`里面不需要。这是因为`def` 是一个 special form, special form 不会 evaluate 它的参数, 而`intern` 是一个函数,它会 evaluate 它的参数。看例子: 71 | 72 | ```clj 73 | (def foo 1) 74 | (create-ns 'com.ociweb.demo) 75 | (intern 'com.ociweb.demo 'foo 2) 76 | (println (+ foo com.ociweb.demo/foo)) ; -> 3 77 | ``` 78 | 79 | `ns-interns` 函数返回一个指定的名字空间的所有的符号的 map(这个名字空间一定要在当前名字空间里面加载了), 这个 map 的 key 是符号的名字,value 是符号所对应的`Var` 对象,这个对象表示的可能是函数,宏或者 binding。比如: 80 | 81 | ```clj 82 | (ns-interns 'clojure.math.numeric-tower) 83 | ``` 84 | 85 | `all-ns` 函数返回一个包含当前所有的已经加载了的名字空间的集合。下面这些名字空间是默认加载的:`clojure.core`,`clojure.main`,`clojure.set`,`clojure.xml`,`clojure.zip` 以及`user`. 而如果是在用 REPL 的话,那么下面这些名字空间也会被加载:`clojure.repl` 和`clojure.java.javadoc`. 86 | 87 | `namespace` 函数返回一个给定符号或者关键字的名字空间。 88 | 89 | 其它一些在这里没有讨论的名字空间相关的函数还包括`ns-aliases`,`ns-imports`,`ns-map`,`ns-name`,`ns-publics`,`ns-refers`,`ns-unalias`,`ns-unmap` 和`remove-ns`. 90 | -------------------------------------------------------------------------------- /02~语法使用/01~语法基础/02~函数基础/2024.11~Clojure 函数定义.md: -------------------------------------------------------------------------------- 1 | # Clojure 函数基础 2 | 3 | ## 1. 函数定义 4 | 5 | ### 1.1 基本语法 6 | 7 | ```clj 8 | ;; 最基本的函数定义 9 | (defn hello 10 | "返回问候语 - 这是文档字符串" 11 | [name] 12 | (str "Hello, " name "!")) 13 | 14 | (hello "World") ; => "Hello, World!" 15 | 16 | ;; 多行函数体 17 | (defn greet 18 | [name] 19 | (println "Processing greeting...") 20 | (str "Hello, " name "!")) ; 返回最后一个表达式的值 21 | ``` 22 | 23 | ### 1.2 函数文档 24 | 25 | ```clj 26 | ;; 详细的文档字符串 27 | (defn calculate-discount 28 | "计算折扣金额。 29 | 30 | 参数: 31 | price - 原始价格 32 | percentage - 折扣百分比(0-100) 33 | 34 | 返回: 35 | 折扣后的价格,精确到分" 36 | [price percentage] 37 | (let [discount (* price (/ percentage 100.0))] 38 | (- price discount))) 39 | ``` 40 | 41 | ### 1.3 私有函数 42 | 43 | ```clj 44 | ;; 使用 defn- 定义私有函数 45 | (defn- internal-helper 46 | "私有函数,只在当前命名空间可见" 47 | [x] 48 | (* x x)) 49 | ``` 50 | 51 | ## 2. 参数处理 52 | 53 | ### 2.1 固定参数 54 | 55 | ```clj 56 | ;; 单个参数 57 | (defn square [x] 58 | (* x x)) 59 | 60 | ;; 多个参数 61 | (defn add [a b] 62 | (+ a b)) 63 | ``` 64 | 65 | ### 2.2 可变参数 66 | 67 | ```clj 68 | ;; 使用 & 收集剩余参数 69 | (defn sum 70 | [& numbers] 71 | (apply + numbers)) 72 | 73 | (sum 1 2 3 4) ; => 10 74 | 75 | ;; 混合固定参数和可变参数 76 | (defn make-list 77 | [first & rest] 78 | (cons first rest)) 79 | 80 | (make-list 1 2 3 4) ; => (1 2 3 4) 81 | ``` 82 | 83 | ### 2.3 多重参数列表 84 | 85 | ```clj 86 | ;; 函数重载 87 | (defn greet 88 | ([] (greet "World")) 89 | ([name] (str "Hello, " name "!")) 90 | ([title name] (str "Hello, " title " " name "!"))) 91 | 92 | (greet) ; => "Hello, World!" 93 | (greet "Alice") ; => "Hello, Alice!" 94 | (greet "Mr." "Bob") ; => "Hello, Mr. Bob!" 95 | ``` 96 | 97 | ## 3. 返回值 98 | 99 | ### 3.1 隐式返回 100 | 101 | ```clj 102 | ;; Clojure 总是返回最后一个表达式的值 103 | (defn calculate [x] 104 | (let [doubled (* x 2) 105 | squared (* doubled doubled)] 106 | squared)) ; 这个值会被返回 107 | ``` 108 | 109 | ### 3.2 条件返回 110 | 111 | ```clj 112 | ;; 使用 if 113 | (defn safe-divide [x y] 114 | (if (zero? y) 115 | 0 ; 当 y 为零时返回 116 | (/ x y))) ; 否则返回除法结果 117 | 118 | ;; 使用 cond 119 | (defn number-type [x] 120 | (cond 121 | (zero? x) "zero" 122 | (pos? x) "positive" 123 | (neg? x) "negative" 124 | :else "not a number")) 125 | ``` 126 | 127 | ## 4. 预置条件和后置条件 128 | 129 | ```clj 130 | ;; 使用 pre 和 post 条件 131 | (defn square 132 | [x] 133 | {:pre [(number? x)] ; 前置条件 134 | :post [(>= % 0)]} ; 后置条件 - % 代表返回值 135 | (* x x)) 136 | 137 | ;; 多个条件 138 | (defn set-age 139 | [age] 140 | {:pre [(number? age) 141 | (>= age 0) 142 | (< age 150)]} 143 | age) 144 | ``` 145 | 146 | ## 5. 最佳实践 147 | 148 | ### 5.1 命名约定 149 | 150 | - 使用描述性名称 151 | - 动词开头表示动作 152 | - 问号结尾表示谓词函数 153 | - 破折号分隔单词 154 | 155 | ### 5.2 函数设计 156 | 157 | - 保持函数简短且专注 158 | - 一个函数只做一件事 159 | - 参数数量控制在 4 个以内 160 | - 使用有意义的参数名 161 | 162 | ### 5.3 文档 163 | 164 | - 为重要函数添加文档字符串 165 | - 说明参数和返回值 166 | - 提供使用示例 167 | 168 | ## 6. 常见模式 169 | 170 | ```clj 171 | ;; 参数验证 172 | (defn process-user [user] 173 | (when-not (:name user) 174 | (throw (IllegalArgumentException. "User must have a name"))) 175 | ;; 处理用户数据 176 | ) 177 | 178 | ;; 默认值处理 179 | (defn connect 180 | [{:keys [host port] 181 | :or {host "localhost" 182 | port 8080}}] 183 | (str "Connecting to " host ":" port)) 184 | ``` 185 | -------------------------------------------------------------------------------- /99~参考资料/2012~《Clojure - Functional Programming for the JVM》~中文版/12~条件处理.md: -------------------------------------------------------------------------------- 1 | # 条件处理 2 | 3 | `if` 这个 special form 跟 java 里面的 if 的语义是一样的,它接受三个参数,第一个是需要判断的条件,第二个表达式是条件成立的时候要执行的表达式,第三个参数是可选的,在条件不成立的时候执行。如果需要执行多个表达式,那么把多个表达式包在 do 里面。看例子: 4 | 5 | ```clj 6 | (import '(java.util Calendar GregorianCalendar)) 7 | (let [gc (GregorianCalendar.) 8 | day-of-week (.get gc Calendar/DAY_OF_WEEK) 9 | is-weekend (or (= day-of-week Calendar/SATURDAY) (= day-of-week Calendar/SUNDAY))] 10 | (if is-weekend 11 | (println "play") 12 | (do (println "work") 13 | (println "sleep")))) 14 | ``` 15 | 16 | 宏 `when` 和 `when-not` 提供和 if 类似的功能,只是它们只在条件成立(或者不成立)时候执行一个表达式。另一个不同是,你可以执行任意数目的表达式而不用用 do 把他们包起来。 17 | 18 | ```clj 19 | (when is-weekend (println "play")) 20 | (when-not is-weekend (println "work") (println "sleep")) 21 | ``` 22 | 23 | 宏 `if-let` 把一个值 bind 到一个变量,然后根据这个 binding 的值来决定到底执行哪个表达式。下面的代码会打印队列里面第一个等待的人的名字,或者打印“no waiting”如果队列里面没有人的话。 24 | 25 | ```clj 26 | (defn process-next [waiting-line] 27 | (if-let [name (first waiting-line)] 28 | (println name "is next") 29 | (println "no waiting"))) 30 | 31 | (process-next '("Jeremy" "Amanda" "Tami")) ; -> Jeremy is next 32 | (process-next '()) ; -> no waiting 33 | ``` 34 | 35 | `when-let` 宏跟`if-let` 类似, 不同之处跟上面`if` 和`when`的不同之处是类似的。他们没有 else 部分,同时还支持执行任意多个表达式。比如: 36 | 37 | ```clj 38 | (defn summarize 39 | "prints the first item in a collection 40 | followed by a period for each remaining item" 41 | [coll] 42 | ; Execute the when-let body only if the collection isn't empty. 43 | (when-let [head (first coll)] 44 | (print head) 45 | ; Below, dec subtracts one (decrements) from 46 | ; the number of items in the collection. 47 | (dotimes [_ (dec (count coll))] (print \.)) 48 | (println))) 49 | 50 | (summarize ["Moe" "Larry" "Curly"]) ; -> Moe.. 51 | (summarize []) ; -> no output 52 | ``` 53 | 54 | `condp` 宏跟其他语言里面的 switch/case 语句差不多。它接受两个参数,一个谓词参数 (通常是`=` 或者`instance?`) 以及一个表达式作为第二个参数。在这之后,它接受任意数量的值-表达式的对子,这些对子会按顺序 evaluate。如果谓词的条件跟某个值匹配了,那么对应的表达式就被执行。一个可选的最后一个参数可以指定,这个参数指定如果一个条件都不符合的话,那么就返回这个值。如果这个值没有指定,而且没有一个条件符合谓词,那么一个`IllegalArgumentException` 异常就会被抛出。 55 | 56 | 下面的例子让用户输入一个数字,如果用户输入的数字是 1,2,3,那么程序会打印这些数字对应的英文单词。否则它会打印"unexpected value"。在那之后,它会测试一个本地 binding 的类型,如果是个数字它会打印这个数字乘以 2 的结果;如果是字符串,那么打印这个字符串的长度乘以 2 的结果。 57 | 58 | ```clj 59 | (print "Enter a number: ") (flush) ; stays in a buffer otherwise 60 | (let [reader (java.io.BufferedReader. *in*) ; stdin 61 | line (.readLine reader) 62 | value (try 63 | (Integer/parseInt line) 64 | (catch NumberFormatException e line))] ; use string value if not integer 65 | (println 66 | (condp = value 67 | 1 "one" 68 | 2 "two" 69 | 3 "three" 70 | (str "unexpected value, \"" value \"))) 71 | (println 72 | (condp instance? value 73 | Number (* value 2) 74 | String (* (count value) 2)))) 75 | ``` 76 | 77 | `cond` 宏接受任意个 谓词/结果表达式 的组合。它按照顺序来测试所有的谓词,直到有一个谓词的测试结果是 true,那么它返回其所对应的结果。如果没有一个谓词的测试结果是 true,那么会抛出一个`IllegalArgumentException` 异常。通常最后一个谓词一般都是 true, 以充当默认情况。 78 | 79 | 下面的例子让用户输入水的温度,然后打印出水的状态: 是冻住了,还是烧开了,还是一般状态。 80 | 81 | ```clj 82 | (print "Enter water temperature in Celsius: ") (flush) 83 | (let [reader (java.io.BufferedReader. *in*) 84 | line (.readLine reader) 85 | temperature (try 86 | (Float/parseFloat line) 87 | (catch NumberFormatException e line))] ; use string value if not float 88 | (println 89 | (cond 90 | (instance? String temperature) "invalid temperature" 91 | (<= temperature 0) "freezing" 92 | (>= temperature 100) "boiling" 93 | true "neither"))) 94 | ``` 95 | -------------------------------------------------------------------------------- /99~参考资料/2012~《Clojure - Functional Programming for the JVM》~中文版/21~宏.md: -------------------------------------------------------------------------------- 1 | # 宏 2 | 3 | 宏是用来给语言添加新的结构,新的元素的。它们是一些在读入期(而不是编译期)就会实际代码替换的一个机制。 4 | 5 | 对于函数来说,它们的所有的参数都会被 evaluate 的, 而宏则会自动判断哪些参数需要 evaluate。这对于实现像`(if *condition* *then-expr* *else-expr*)`这样的结构是非常重要的。如果 condition 是`true`, 那么只有 "then" 表达式需要被 evaluated. 如果条件是`false`, 那么只有 "else" 表达式应该被 evaluated. 这意味着`if` 不能被实现成一个函数 (它其实也不是宏,而是一个 special form)。其它一些因为这个原因而必须要实现成宏的包括`and` 和`or` 因为它们需要实现 "short-circuit"属性。 6 | 7 | 要想知道一个东西到底是函数还是宏,可以在 REPL 里面输入`(doc *name*)` 或者查看它的元数据。如果是一个宏的话,那么它的元数据里面包含一个`:macro` key,并且它的值为`true`。比如,我们要看看`and`, 是不是宏,在 REPL 里面输入下面的命令: 8 | 9 | ```clj 10 | ((meta (var and)) :macro) ; long way -> true 11 | (^#'and :macro) ; short way -> true 12 | ``` 13 | 14 | 让我们通过一些例子来看看如何编写并且使用宏。假设我们代码里面很多地方要对一个数字进行判断,通过判断它是接近 0,是正的,是负的来执行不同的逻辑;我们又不想这种判断的代码到处重复,那么这种情况下我们就可以使用宏了。我们使用`defmacro` 宏来定义一个宏。 15 | 16 | ```clj 17 | (defmacro around-zero [number negative-expr zero-expr positive-expr] 18 | `(let [number# ~number] ; so number is only evaluated once 19 | (cond 20 | (< (Math/abs number#) 1e-15) ~zero-expr 21 | (pos? number#) ~positive-expr 22 | true ~negative-expr))) 23 | ``` 24 | 25 | Clojure 的 reader 会把所有调用 around-aero 的地方全部换成 defmacro 这个方法体里面的具体代码。我们在这里使用 let 是为了性能,因为这个传进来的 number 是一个表达式而不是一个简单的值,而且被 cond 语句里面使用了两次。自动产生的变量 number#是为了产生一个不会和用户指定的其它 binding 冲突的一个名字。这使得我们可以创建[hygienic macros](http://en.wikipedia.org/wiki/Hygienic_macros). 26 | 27 | 宏定义开始的时候的那个反引号 (也称为语法引号) 防止宏体内的任何一个表达式被 evaluate -- 除非你显示地转义了。这意味着宏体里面的代码会原封不动地替换到使用这个宏的所有的地方 -- 除了以波浪号开始的那些表达式。(`number`,`zero-expr`,`positive-expr` 和`negative-expr`). 当一个名字前面被加了一个波浪号,并且还在反引号里面,它的值会被替换的。如果这个名字代表的是一个序列,那么我们可以用`~@` 这个语法来替换序列里面的某个具体元素。 28 | 29 | 下面是两个使用这个宏的例子:(输出都应该是 "`+`"). 30 | 31 | ``` 32 | (around-zero 0.1 (println "-") (println "0") (println "+")) 33 | (println (around-zero 0.1 "-" "0" "+")) ; same thing 34 | ``` 35 | 36 | 如果对于每种条件执行多于一个表达式,那么用 do 把他们包起来。看下面例子: 37 | 38 | ```clj 39 | (around-zero 0.1 40 | (do (log "really cold!") (println "-")) 41 | (println "0") 42 | (println "+")) 43 | ``` 44 | 45 | 为了验证这个宏是否被正确展开,在 REPL 里面输入这个: 46 | 47 | ``` 48 | (macroexpand-1 49 | '(around-zero 0.1 (println "-") (println "0") (println "+"))) 50 | ``` 51 | 52 | 它会输出下面这个(为了容易看懂,我加了缩进) 53 | 54 | ``` 55 | (clojure.core/let [number__3382__auto__ 0.1] 56 | (clojure.core/cond 57 | (clojure.core/< (Math/abs number__3382__auto__) 1.0E-15) (println "0") 58 | (clojure.core/pos? number__3382__auto__) (println "+") 59 | true (println "-"))) 60 | ``` 61 | 62 | 下面是一个使用这个宏来返回一个描述输入数字的属性的字符串的函数。 63 | 64 | ``` 65 | (defn number-category [number] 66 | (around-zero number "negative" "zero" "positive")) 67 | ``` 68 | 69 | 下面是一些示例用法: 70 | 71 | ``` 72 | (println (number-category -0.1)) ; -> negative 73 | (println (number-category 0)) ; -> zero 74 | (println (number-category 0.1)) ; -> positive 75 | ``` 76 | 77 | 因为宏不会 evaluate 它们的参数, 所以你可以在宏体里面写一个对函数的参数调用. 函数定义不能这么做,相反只能用匿名函数把它们包起来。 78 | 79 | 下面是一个接受两个参数的宏。第一个是一个接受一个参数的函数, 这个参数是一个弧度,如果它是一个三角函数 sin,cos。第二个参数是一个弧度。如果这个被写成一个函数而不是一个 宏的话,那么我们需要传递一个`#(Math/sin %)` 而不是简单的`Math/sin` 作为参数。注意 那些后面的#符号,它会产生一个唯一的、不冲突的本地 binding。`#` 和`~` 都必须在反引号引着的列表里面才能使用。 80 | 81 | ```clj 82 | (defmacro trig-y-category [fn degrees] 83 | `(let [radians# (Math/toRadians ~degrees) 84 | result# (~fn radians#)] 85 | (number-category result#))) 86 | ``` 87 | 88 | 让我们试一下。下面代码的期望输出应该是 "zero", "positive", "zero" 和 "negative". 89 | 90 | ``` 91 | (doseq [angle (range 0 360 90)] ; 0, 90, 180 and 270 92 | (println (trig-y-category Math/sin angle))) 93 | ``` 94 | 95 | 宏的名字不能作为参数传递给函数。比如一个宏的名字比如`and` 不能作为参数传递给`reduce`函数。一个绕过的方法是定义一个匿名函数把这个宏包起来。比如`(fn [x y] (and x y))` 或者`#(and %1 %2)`. 宏会在这个读入期在这个匿名函数体内解开。当这个函数被传递给函数比如`reduce`, 传递的是函数而不是宏。 96 | 97 | 宏的调用是在读入期处理的。 98 | -------------------------------------------------------------------------------- /02~语法使用/01~语法基础/02~函数基础/2024.11~Clojure 函数参数与解构.md: -------------------------------------------------------------------------------- 1 | # Clojure 函数参数解构指南 2 | 3 | 函数参数解构是 Clojure 中一个强大的特性,它允许我们直接在函数参数位置对复杂数据结构进行解构,使代码更简洁易读。 4 | 5 | ## 1. 向量参数解构 6 | 7 | ```clojure 8 | ;; 基础向量解构 9 | (defn process-pair [[first second]] 10 | (str first " and " second)) 11 | (process-pair ["apple" "banana"]) ; => "apple and banana" 12 | 13 | ;; 多组向量参数 14 | (defn process-pairs [[x1 y1] [x2 y2]] 15 | {:point1 [x1 y1] 16 | :point2 [x2 y2] 17 | :distance (Math/sqrt (+ (Math/pow (- x2 x1) 2) 18 | (Math/pow (- y2 y1) 2)))}) 19 | (process-pairs [1 2] [4 6]) ; => {:point1 [1 2], :point2 [4 6], :distance 5.0} 20 | 21 | ;; 处理多组向量并使用 & 收集剩余向量 22 | (defn process-points [[x1 y1] [x2 y2] & more-points] 23 | {:first [x1 y1] 24 | :second [x2 y2] 25 | :others more-points}) 26 | (process-points [1 2] [3 4] [5 6] [7 8]) 27 | ; => {:first [1 2], :second [3 4], :others [[5 6] [7 8]]} 28 | 29 | ;; 组合使用 :as 和多组向量 30 | (defn analyze-vectors [[x1 y1 :as v1] [x2 y2 :as v2]] 31 | {:vectors [v1 v2] 32 | :sums [(+ x1 y1) (+ x2 y2)]}) 33 | (analyze-vectors [1 2] [3 4]) 34 | ; => {:vectors [[1 2] [3 4]], :sums [3 7]} 35 | ``` 36 | 37 | ## 2. 映射参数解构 38 | 39 | ```clojure 40 | ;; :keys 语法(推荐用法) 41 | (defn greet-user [{:keys [name age]}] 42 | (str "Hello, " name "! You are " age " years old.")) 43 | (greet-user {:name "Alice" :age 25}) 44 | 45 | ;; 设置默认值 46 | (defn create-user [{:keys [name age role] 47 | :or {role :user 48 | age 18}}] 49 | {:name name 50 | :age age 51 | :role role}) 52 | (create-user {:name "Bob"}) ; => {:name "Bob", :age 18, :role :user} 53 | 54 | ;; 使用 :as 保留原始映射 55 | (defn process-order [{:keys [id items] 56 | :as order}] 57 | {:id id 58 | :item-count (count items) 59 | :original order}) 60 | ``` 61 | 62 | ## 3. 多参数解构 63 | 64 | ```clojure 65 | ;; 组合多个参数的解构 66 | (defn process-user-data 67 | [[username password] ; 认证信息 68 | {:keys [email settings]} ; 用户配置 69 | & roles] ; 可选角色列表 70 | {:auth {:username username 71 | :password password} 72 | :config {:email email 73 | :settings settings} 74 | :roles (vec roles)}) 75 | 76 | ;; 嵌套解构示例 77 | (defn analyze-nested-data 78 | [{:keys [user-id] 79 | {:keys [latitude longitude]} :location 80 | {:keys [width height]} :dimensions}] 81 | {:user user-id 82 | :coords [latitude longitude] 83 | :size [width height]}) 84 | ``` 85 | 86 | ## 4. 实用模式 87 | 88 | ```clojure 89 | ;; API 处理函数 90 | (defn handle-api-call 91 | [{:keys [method url] 92 | {:keys [auth-token]} :headers 93 | :or {method :get} 94 | :as request}] 95 | ;; ... 处理逻辑 ... 96 | ) 97 | 98 | ;; 配置处理函数 99 | (defn initialize-app 100 | [{:keys [env debug?] 101 | {:keys [host port]} :server 102 | :or {env :development 103 | debug? false 104 | port 3000}}] 105 | ;; ... 初始化逻辑 ... 106 | ) 107 | ``` 108 | 109 | ## 5. 最佳实践 110 | 111 | - 使用 `:keys` 语法来解构关键字映射 112 | - 为可选参数设置默认值 113 | - 使用 `:as` 在需要时保留原始数据 114 | - 在复杂解构时添加参数说明注释 115 | - 避免过深的嵌套解构 116 | 117 | ```clojure 118 | ;; 好的实践示例 119 | (defn process-transaction 120 | "处理交易数据 121 | 参数格式: 122 | - transaction: {:id string 123 | :amount number 124 | :user {:id string 125 | :name string} 126 | :metadata map} 127 | - options: {:validate? boolean 128 | :process-async? boolean}" 129 | [{:keys [id amount] 130 | {:keys [id name]} :user 131 | :as transaction} 132 | {:keys [validate? process-async?] 133 | :or {validate? true 134 | process-async? false}}] 135 | ;; ... 处理逻辑 ... 136 | ) 137 | ``` 138 | -------------------------------------------------------------------------------- /99~参考资料/2012~《Clojure - Functional Programming for the JVM》~中文版/16~序列.md: -------------------------------------------------------------------------------- 1 | # 序列 2 | 3 | 序列可以看成是集合的一个逻辑视图。许多事物可以看成是序列。包括 Java 的集合,Clojure 提供的集合,字符串,流,目录结构以及 XML 树。很多 Clojure 的函数返回一个 lazy 序列(LazySeq), 这种序列里面的元素不是实际的数据,而是一些方法,它们直到用户真正需要数据的时候才会被调用。LazySeq 的一个好处是在你创建这个序列的时候你不用太担心这个序列到底会有多少元素。下面是会返回 lazySeq 的一些函数:`cache-seq`,`concat`,`cycle`,`distinct`,`drop`,`drop-last`,`drop-while`,`filter`,`for`,`interleave`,`interpose`,`iterate`,`lazy-cat`,`lazy-seq`,`line-seq`,`map`,`partition`,`range`,`re-seq`,`remove`,`repeat`,`replicate`,`take`,`take-nth`,`take-while` and`tree-seq`。 4 | 5 | LazySeq 是刚接触 Clojure 的人比较容易弄不清楚的一个东西。比如你们觉得下面这个代码的输出是什么? 6 | 7 | ``` 8 | (map #(println %) [1 2 3]) 9 | ``` 10 | 11 | 当在一个 REPL 里面运行的时候,它会输出 1, 2 和 3 在单独的行上面,以及三个 nil(三个 println 的返回结果)。REPL 总是立即解析/调用我们所输入的所有的表达式。但是当作为一个脚本来运行的时候,这句代码不会输出任何东西。因为`map` 函数返回的是一个 LazySeq。 12 | 13 | 有很多方法可以强制 LazySeq 对它里面的方法进行调用。比如从序列里面获取一个元素的方法`first`,`second`,`nth` 以及`last` 都能达到这个效果。序列里面的方法是按顺序调用的,所以你如果要获取最后一个元素,那么整个 LazySeq 里面的方法都会被调用。 14 | 15 | 如果 LazySeq 的头被存在一个 binding 里面,那么一旦一个元素的方法被调用了,那么这个元素的值会被缓存起来,下次我们再来获取这个元素的时候就不用再调用函数了。 16 | 17 | `dorun` 和`doall` 函数迫使一个 LazySeq 里面的函数被调用。`doseq` 宏, 我们在 "迭代" 那一节提到过的, 会迫使一个或者多个 LazySeq 里面的函数调用。`for` 宏, 也在是"迭代"那一节提到的,不会强制调用 LazySeq 里面的方法,相反,他会返回另外一个 LazySeq。 18 | 19 | 为了只是简单的想要迫使 LazySeq 里面的方法被调用,那么`doseq` 或者`dorun` 就够了。调用的结果不会被保留的,所以占用的内存也就比较少。这两个方法的返回值都是`nil`. 如果你想调用的结果被缓存,那么你应该使用`doall`. 20 | 21 | 下面的表格列出来了强制 LazySeq 里面的方法被调用的几个办法。 22 | 23 | | | 结果要缓存 | 只要求方法被执行,不需要缓存 | 24 | | ------------------------------------------ | ---------- | ---------------------------- | 25 | | 操作单个序列 | `doall` | `dorun` | 26 | | 利用 list comprehension 语法来操作多个序列 | N/A | `doseq` | 27 | 28 | 一般来说我们比较推荐使用`doseq` 而不是`dorun` 函数,因为这样代码更加易懂。同时代码效率也更高,因为 dorun 内部使用 map 又创建了另外一个序列。比如下面的两会的结果是一样的。 29 | 30 | ``` 31 | (dorun (map #(println %) [1 2 3])) 32 | (doseq [i [1 2 3]] (println i)) 33 | ``` 34 | 35 | 如果一个方法会返回一个 LazySeq 并且在它的方法被调用的时候还会有副作用,那么大多数情况下我们应该使用`doall` 来调用并且返回它的结果。这使得副作用的出现时间更容易确定。否则的话别的调用者可能会调用这个 LazySeq 多次,那么副作用也就会出现多次 -- 从而可能出现错误的结果。 36 | 37 | 下面的几个表达式都会在不同的行输出 1, 2, 3, 但是它们的返回值是不一样的。`do` special form 是用来实现一个匿名函数,这个函数先打印这个值,然后再把这个值返回。 38 | 39 | ``` 40 | (doseq [item [1 2 3]] (println item)) ; -> nil 41 | (dorun (map #(println %) [1 2 3])) ; -> nil 42 | (doall (map #(do (println %) %) [1 2 3])) ; -> (1 2 3) 43 | ``` 44 | 45 | LazySeq 使得创建无限序列成为可能。因为只有需要使用的数据才会在用到的时候被调用创建。比如 46 | 47 | ``` 48 | (defn f 49 | "square the argument and divide by 2" 50 | [x] 51 | (println "calculating f of" x) 52 | (/ (* x x) 2.0)) 53 | 54 | ; Create an infinite sequence of results from the function f 55 | ; for the values 0 through infinity. 56 | ; Note that the head of this sequence is being held in the binding "f-seq". 57 | ; This will cause the values of all evaluated items to be cached. 58 | (def f-seq (map f (iterate inc 0))) 59 | 60 | ; Force evaluation of the first item in the infinite sequence, (f 0). 61 | (println "first is" (first f-seq)) ; -> 0.0 62 | 63 | ; Force evaluation of the first three items in the infinite sequence. 64 | ; Since the (f 0) has already been evaluated, 65 | ; only (f 1) and (f 2) will be evaluated. 66 | (doall (take 3 f-seq)) 67 | 68 | (println (nth f-seq 2)) ; uses cached result -> 2.0 69 | ``` 70 | 71 | 下面的代码和上面的代码不一样的地方是,在下面的代码里面 LazySeq 的头没有被保持在一个 binding 里面,所以被调用过的方法的返回值不会被缓存。所以它所需要的内存比较少,但是如果同一个元素被请求多次,那么它的效率会低一点。 72 | 73 | ``` 74 | (defn f-seq [] (map f (iterate inc 0))) 75 | (println (first (f-seq))) ; evaluates (f 0), but doesn't cache result 76 | (println (nth (f-seq) 2)) ; evaluates (f 0), (f 1) and (f 2) 77 | ``` 78 | 79 | 另外一种避免保持 LazySeq 的头的办法是把这个 LazySeq 直接传给函数: 80 | 81 | ``` 82 | (defn consumer [seq] 83 | ; Since seq is a local binding, the evaluated items in it 84 | ; are cached while in this function and then garbage collected. 85 | (println (first seq)) ; evaluates (f 0) 86 | (println (nth seq 2))) ; evaluates (f 1) and (f 2) 87 | 88 | (consumer (map f (iterate inc 0))) 89 | ``` 90 | -------------------------------------------------------------------------------- /02~语法使用/01~语法基础/04~绑定与作用域/let 解构.md: -------------------------------------------------------------------------------- 1 | # let 解构 2 | 3 | 解构是用来方便赋值的,否则获取值的时候就要使用大量的 next ,first 之类的 seq 操作,不方便,尤其是嵌套的 collection,取值就更麻烦。有了解构以后,把 collection 的值取出来就非常的简单和方便,除了可以用于 let 以外,还可以用于函数参数的赋值等地方。Clojure 的解构特性提供了一种简洁的语法来声明式地从一个集合里面选取某些元素,并且把这些元素绑定到一个本地 let 绑定上去。并且因为解构这个特性是由 let 提供的,它可以在任何间接使用了 let 的地方使用,比如 fn、defn、loop。 4 | 5 | # 序列解构 6 | 7 | ```clj 8 | ;; let is a Clojure special form, a fundamental building block of the language. 9 | ;; 10 | ;; In addition to parameters passed to functions, let provides a way to create 11 | ;; lexical bindings of data structures to symbols. The binding, and therefore 12 | ;; the ability to resolve the binding, is available only within the lexical 13 | ;; context of the let. 14 | ;; 15 | ;; let uses pairs in a vector for each binding you'd like to make and the value 16 | ;; of the let is the value of the last expression to be evaluated. let also 17 | ;; allows for destructuring which is a way to bind symbols to only part of a 18 | ;; collection. 19 | 20 | ;; A basic use for a let: 21 | user=> (let [x 1] 22 | x) 23 | 1 24 | 25 | ;; Note that the binding for the symbol y won't exist outside of the let: 26 | user=> (let [y 1] 27 | y) 28 | 1 29 | user=> (prn y) 30 | java.lang.Exception: Unable to resolve symbol: y in this context (NO_SOURCE_FILE:7) 31 | 32 | ;; Note that if you use def inside a let block, your interned variable is within 33 | ;; the current namespace and will appear OUTSIDE of the let block. 34 | user=> (let [y 1] 35 | (def z y) 36 | y) 37 | 1 38 | user=> z 39 | 1 40 | 41 | ;; Another valid use of let: 42 | user=> (let [a 1 b 2] 43 | (+ a b)) 44 | 3 45 | 46 | ;; The forms in the vector can be more complex (this example also uses 47 | ;; the thread macro): 48 | user=> (let [c (+ 1 2) 49 | [d e] [5 6]] 50 | (-> (+ d e) (- c))) 51 | 8 52 | 53 | ;; The bindings for let need not match up (note the result is a numeric 54 | ;; type called a ratio): 55 | user=> (let [[g h] [1 2 3]] 56 | (/ g h)) 57 | 1/2 58 | 59 | ;; From http://clojure-examples.appspot.com/clojure.core/let with permission. 60 | ``` 61 | 62 | ## 使用 `_` 占位 63 | 64 | 如果有的值不需要,但是又需要展位,clojure 中有一个**惯用法** `_` 下划线,来占用,但是不使用他的值。用下划线的方式来忽略掉一个或多个自己不在意的值。 65 | 66 | ```clojure 67 | (let [[a [_ [c]]] [1 [2 [3]]]] 68 | (println a ", " c)) 69 | ;=> 1, 2, 3 70 | ``` 71 | 72 | 可以看到,只需要我们的取值的解构和原始数据结构一致,就可以很方便的把值一次取到,方便使用。 73 | 74 | ## & 获取剩余值 75 | 76 | & 符号会把剩下的值以 list 的形式全部放入 b 中: 77 | 78 | ```clj 79 | (let [[a & b] [1 2 3]] 80 | (println a ", " b)) 81 | ;=> 1, (2 3) 82 | ``` 83 | 84 | ## :as 获取整个集合 85 | 86 | :as 可以在解构形式中使用:as 来获得对于被解构的集合的引用 87 | 88 | ```clojure 89 | (let [[a & b :as c] [1 2 3]] 90 | (println (a ", " b ", " c))) 91 | ;=> 1, (2 3), [1 2 3] 92 | ``` 93 | 94 | # Map 解构 95 | 96 | ```clj 97 | (let [{a :a b :b c :c} {:a 1 :b 2 :c 3}] 98 | (println a ", " b ", " c)) 99 | ;=> 1, 2, 3 100 | 101 | ;;; map destructuring, all features 102 | user=> 103 | (let [ 104 | ;;Binding Map 105 | {:keys [k1 k2] ;; bind vals with keyword keys 106 | :strs [s1 s2] ;; bind vals with string keys 107 | :syms [sym1 sym2] ;; bind vals with symbol keys 108 | :or {k2 :default-kw, ;; default values 109 | s2 :default-s, 110 | sym2 :default-sym} 111 | :as m} ;; bind the entire map to `m` 112 | ;;Data 113 | {:k1 :keyword1, :k2 :keyword2, ;; keyword keys 114 | "s1" :string1, "s2" :string2, ;; string keys 115 | 'sym1 :symbol1, ;; symbol keys 116 | ;; 'sym2 :symbol2 ;; `sym2` will get default value 117 | }] 118 | [k1 k2 s1 s2 sym1 sym2 m]) ;; return value 119 | 120 | [:keyword1, :keyword2, 121 | :string1, :string2, 122 | :symbol1, :default-sym, ;; key didn't exist, so got the default 123 | {'sym1 :symbol1, :k1 :keyword1, :k2 :keyword2, 124 | "s1" :string1, "s2" :string2}] 125 | 126 | ;; remember that vector and map destructuring can also be used with 127 | ;; other macros that bind variables, e.g. `for` and `doseq` 128 | ``` 129 | -------------------------------------------------------------------------------- /99~参考资料/2012~《Clojure - Functional Programming for the JVM》~中文版/03~Clojure 概述.md: -------------------------------------------------------------------------------- 1 | # Clojure 概述 2 | 3 | Clojure 是一个动态类型的,运行在 JVM(JDK5.0 以上),并且可以和 java 代码互操作的函数式语言。这个语言的主要目标之一是使得编写一个有多个线程并发访问数据的程序变得简单。 4 | 5 | Clojure 的发音和单词 closure 是一样的。Clojure 之父是这样解释 Clojure 名字来历的 6 | 7 | “我想把这就几个元素包含在里面: C (C#), L (Lisp) and J (Java). 所以我想到了 Clojure, 而且从这个名字还能想到 closure;它的域名又没有被占用;而且对于搜索引擎来说也是个很不错的关键词,所以就有了它了.” 8 | 9 | 很快 Clojure 就会移植到.NET 平台上了. ClojureCLR 是一个运行在 Microsoft 的 CLR 的 Clojure 实现. 在我写这个入门教程的时候 ClojureCLR 已经处于 alpha 阶段了. 10 | 11 | 在 2011 年 7 月, ClojureScript 项目开始了,这个项目把 Clojure 代码编译成 Javascript 代码:看这里 https://github.com/clojure/clojurescript . 12 | 13 | Clojure 是一个开源语言,licence: [Eclipse Public License v 1.0](http://www.eclipse.org/legal/epl-v10.html) (EPL). This is a very liberal license. 关于 EPL 的更多信息看这里: http://www.eclipse.org/legal/eplfaq.php . 14 | 15 | 运行在 JVM 上面使得 Clojure 代码具有可移植性,稳定性,可靠的性能以及安全性。同时也使得我们的 Clojure 代码可以访问丰富的已经存在的 java 类库:文件 I/O, 多线程, 数据库操作, GUI 编程, web 应用等等等等. 16 | 17 | Clojure 里面的每个操作被实现成以下三种形式的一种: 函数(function), 宏(macro)或者 special form. 几乎所有的函数和宏都是用 Clojure 代码实现的,它们的主要区别我们会在后面解释。Special forms 不是用 clojure 代码实现的,而且被 clojure 的编译器识别出来. special forms 的个数是很少的,而且现在也不能再实现新的 special forms 了. 它们包括: [catch](http://clojure.org/special_forms#try) , [def](http://clojure.org/special_forms#toc1) , [do](http://clojure.org/special_forms#toc3) , [dot](http://clojure.org/java_interop#dot) (‘.’), [finally](http://clojure.org/special_forms#try) , [fn](http://clojure.org/special_forms#toc7) , [if](http://clojure.org/special_forms#toc2) , [let](http://clojure.org/special_forms#toc4) , [loop](http://clojure.org/special_forms#toc9) , [monitor-enter](http://clojure.org/special_forms#toc13) , [monitor-exit](http://clojure.org/special_forms#toc14) , [new](http://clojure.org/java_interop#new) , [quote](http://clojure.org/special_forms#toc5) , [recur](http://clojure.org/special_forms#toc10) , [set!](http://clojure.org/java_interop#set) , [throw](http://clojure.org/special_forms#try) , [try](http://clojure.org/special_forms#try) 和 [var](http://clojure.org/special_forms#toc6) . 18 | 19 | Clojure 提供了很多函数来操作序列(sequence), 而序列是集合的逻辑视图。很多东西可以被看作序列:Java 集合, Clojure 的集合, 字符串, 流, 文件系统结构以及 XML 树. 从已经存在的 clojure 集合来创建新的集合的效率是非常高的,因为这里使用了 [persistent data structures](http://en.wikipedia.org/wiki/Persistent_data_structure) 的技术(这对于 clojure 在数据不可更改的情况下,同时要保持代码的高效率是非常重要的)。 20 | 21 | Clojure 提供三种方法来安全地共享可修改的数据。所有三种方法的实现方式都是持有一个可以开遍的引用指向一个不可改变的数据。Refs 通过使用 [Software Transactional Memory](http://en.wikipedia.org/wiki/Software_transactional_memory) (STM)来提供对于多块共享数据的同步访问。Atoms 提供对于单个共享数据的同步访问。Agents 提供对于单个共享数据的异步访问。这个我们会在 “引用类型”一节详细讨论。 22 | 23 | Clojure 是 [Lisp]() 的一个方言. 但是 Clojure 对于传统的 Lisp 有所发展。比如, 传统 Lisp 使用 `car` 来获取链表里面的第一个数据。而 Clojure 使用 `first。有关更多Clojure和Lisp的不同看这里:` http://clojure.org/lisps . 24 | 25 | Lisp 的语法很多人很喜欢,很多人很讨厌, 主要因为它大量的使用圆括号以及前置表达式. 如果你不喜欢这些,那么你要考虑一下是不是要学习 Clojure 了 。许多文件编辑器以及 IDE 会高亮显示匹配的圆括号, 所以你不用担心需要去人肉数有没有多加一个左括号,少写一个右括号. 同时 Clojure 的代码还要比 java 代码简洁. 一个典型的 java 方法调用是这样的: 26 | 27 | ```clj 28 | methodName(arg1, arg2, arg3); 29 | ``` 30 | 31 | 而 Clojure 的方法调用是这样的: 32 | 33 | ```clojure 34 | (function-name arg1 arg2 arg3) 35 | ``` 36 | 37 | 左括号被移到了最前面;逗号和分号不需要了. 我们称这种语法叫: “form”. 这种风格是简单而又美丽:Lisp 里面所有东西都是这种风格的.要注意的是 clojure 里面的命名规范是小写单词,如果是多个单词,那么通过中横线连接。 38 | 39 | 定义函数也比 java 里面简洁。Clojure 里面的 `println` 会在它的每个参数之间加一个空格。如果这个不是你想要的,那么你可以把参数传给 `str` ,然后再传给 `println` . 40 | 41 | ```clj 42 | // Java 43 | public void hello(String name) { 44 | System.out.println("Hello, " + name); 45 | } 46 | ; Clojure 47 | (defn hello [name] 48 | (println "Hello," name)) 49 | ``` 50 | 51 | Clojure 里面大量之用了延迟计算. 这使得只有在我们需要函数结果的时候才去调用它. “懒惰序列” 是一种集合,我们之后在需要的时候才会计算这个集合理解面的元素. 只使得创建无限集合非常高效. 52 | 53 | 对 Clojure 代码的处理分为三个阶段:读入期,编译期以及运行期。在读入期,读入期会读取 clojure 源代码并且把代码转变成数据结构,基本上来说就是一个包含列表的列表的列表。。。。在编译期,这些数据结构被转化成 java 的 bytecode。在运行期这些 java bytecode 被执行。函数只有在运行期才会执行。而宏在编译期就被展开成实际对应的代码了。 54 | 55 | Clojure 代码很难理解么?想想每次你看到 java 代码里面那些复杂语法比如: `if` , `for` , 以及匿名内部类, 你需要停一下来想想它们到底是什么意思(不是那么的直观),同时如果想要做一个高效的 Java 工程师,我们有一些工具可以利用来使得我们的代码更容易理解。同样的道理,Clojure 也有类似的工具使得我们可以更高效的读懂 clojure 代码。比如: `let` , `apply` , `map` , `filter` , `reduce` 以及匿名函数 … 所有这些我们会在后面介绍. 56 | -------------------------------------------------------------------------------- /02~语法使用/03~函数式编程/线程宏/2024.11~thread-first 与 thread-last 对比.md: -------------------------------------------------------------------------------- 1 | # Thread-first (->) vs Thread-last (->>) 宏对比 2 | 3 | ## 1. 基本概念 4 | 5 | ```clj 6 | ;; 使用 -> 7 | (-> 5 8 | (+ 3) ; (+ 5 3) 9 | (/ 2) ; (/ 8 2) 10 | (* 4)) ; (* 4 4) 11 | 12 | ;; 等价于 13 | (* (/ (+ 5 3) 2) 4) ; => 16 14 | 15 | ;; 使用 ->> 16 | (->> 5 17 | (+ 3) ; (+ 3 5) 18 | (/ 2) ; (/ 2 8) 19 | (* 4)) ; (* 4 0.25) 20 | 21 | ;; 等价于 22 | (* 4 (/ 2 (+ 3 5))) ; => 1 23 | ``` 24 | 25 | ### 1.1 Thread-first (->) 26 | 27 | - 将每个表达式的结果插入到下一个形式的第一个参数位置 28 | - 适合处理面向对象风格的调用链 29 | - 常用于处理单个对象的多个转换 30 | 31 | ### 1.2 Thread-last (->>) 32 | 33 | - 将每个表达式的结果插入到下一个形式的最后一个参数位置 34 | - 适合处理集合操作的管道 35 | - 常用于数据转换流程 36 | 37 | ## 2. 语法对比 38 | 39 | ### 2.1 Thread-first 示例 40 | 41 | ```clojure 42 | ;; 不使用线程宏 43 | (str/trim (str/replace (str/lower-case "HELLO WORLD") #"world" "clojure")) 44 | 45 | ;; 使用 -> 改写 46 | (-> "HELLO WORLD" 47 | str/lower-case 48 | (str/replace #"world" "clojure") 49 | str/trim) 50 | ``` 51 | 52 | ### 2.2 Thread-last 示例 53 | 54 | ```clojure 55 | ;; 不使用线程宏 56 | (reduce + (filter even? (map #(* % %) (range 10)))) 57 | 58 | ;; 使用 ->> 改写 59 | (->> (range 10) 60 | (map #(* % %)) 61 | (filter even?) 62 | (reduce +)) 63 | ``` 64 | 65 | ## 3. 宏展开对比 66 | 67 | ### 3.1 Thread-first 展开 68 | 69 | ```clojure 70 | ;; 原始形式 71 | (-> "HELLO WORLD" 72 | str/lower-case 73 | (str/replace #"world" "clojure")) 74 | 75 | ;; 展开后 76 | (str/replace (str/lower-case "HELLO WORLD") #"world" "clojure") 77 | ``` 78 | 79 | ### 3.2 Thread-last 展开 80 | 81 | ```clojure 82 | ;; 原始形式 83 | (->> (range 10) 84 | (map inc) 85 | (filter even?)) 86 | 87 | ;; 展开后 88 | (filter even? (map inc (range 10))) 89 | ``` 90 | 91 | ## 4. 常见使用场景 92 | 93 | ### 4.1 Thread-first (->) 适用场景 94 | 95 | ```clojure 96 | ;; 1. 对象方法调用 97 | (-> person 98 | :name 99 | str/lower-case 100 | (str/replace #"\s+" "-") 101 | keyword) 102 | 103 | ;; 2. 嵌套数据结构访问 104 | (-> data 105 | :user 106 | :address 107 | :city) 108 | 109 | ;; 3. Java 互操作 110 | (-> (java.io.File. "example.txt") 111 | .getAbsolutePath 112 | .toLowerCase) 113 | ``` 114 | 115 | ### 4.2 Thread-last (->>) 适用场景 116 | 117 | ```clojure 118 | ;; 1. 集合处理 119 | (->> items 120 | (filter valid?) 121 | (map transform) 122 | (remove nil?) 123 | (into [])) 124 | 125 | ;; 2. 数据转换管道 126 | (->> raw-data 127 | (map parse-line) 128 | (filter relevant?) 129 | (group-by :category) 130 | (map-vals count)) 131 | 132 | ;; 3. 数值计算 133 | (->> numbers 134 | (map #(* % %)) 135 | (filter #(> % 100)) 136 | (reduce +)) 137 | ``` 138 | 139 | ## 5. 混合使用技巧 140 | 141 | ### 5.1 组合使用 142 | 143 | ```clojure 144 | ;; 使用 let 绑定中间结果 145 | (let [processed-data (->> raw-data 146 | (map process) 147 | (filter valid?))] 148 | (-> processed-data 149 | count 150 | str 151 | (str " items processed"))) 152 | ``` 153 | 154 | ### 5.2 嵌套使用 155 | 156 | ```clojure 157 | ;; 在 ->> 中使用 -> 158 | (->> items 159 | (map (fn [item] 160 | (-> item 161 | :data 162 | process 163 | validate))) 164 | (filter :valid?)) 165 | ``` 166 | 167 | ## 6. 常见错误和注意事项 168 | 169 | ### 6.1 参数位置错误 170 | 171 | ```clojure 172 | ;; 错误示例 173 | (-> items 174 | (filter even?) ; filter 需要集合在最后 175 | (map inc)) ; map 需要集合在最后 176 | 177 | ;; 正确做法:使用 ->> 178 | (->> items 179 | (filter even?) 180 | (map inc)) 181 | ``` 182 | 183 | ### 6.2 括号使用 184 | 185 | ```clojure 186 | ;; 需要括号 187 | (-> x 188 | (+ 1) ; 需要括号因为有多个参数 189 | (/ 2)) 190 | 191 | ;; 不需要括号 192 | (-> x 193 | inc ; 单参数函数不需要括号 194 | dec) 195 | ``` 196 | 197 | ## 7. 最佳实践 198 | 199 | 1. 选择合适的线程宏: 200 | 201 | - 对象操作用 -> 202 | - 集合处理用 ->> 203 | 204 | 2. 保持一致性: 205 | 206 | - 在同一个转换链中使用同一种线程宏 207 | - 避免不必要的混合使用 208 | 209 | 3. 可读性: 210 | 211 | - 每行一个操作 212 | - 适当的缩进 213 | - 添加有意义的注释 214 | 215 | 4. 长度控制: 216 | - 避免过长的转换链 217 | - 考虑使用 let 分解复杂操作 218 | -------------------------------------------------------------------------------- /01~快速开始/99~参考资料/2019~《Clojure 学习笔记》/01~环境搭建.md: -------------------------------------------------------------------------------- 1 | ## Clojure 是什么? 2 | 3 | Clojure 是一门运行在**JVM 环境**下的**Lisp 方言。**笔者一直久仰 Lisp 的大名,最近终于克服了对层层括号的恐惧,准备一睹 Lisp 的芳容啦!实际上手体验之后感觉 Lisp 系语言写起来还是蛮爽的,最重要的是 REPL 实在是太方便了,有了想法之后就可以立刻付诸实践,不用再专门写一个 main 函数或是单元测试来进行验证,直接用 REPL 交互式的进行实验即可。 4 | 5 | ![img](https://ngte-superbed.oss-cn-beijing.aliyuncs.com/item/v2-0510f484f47d5396df39e6b675995100_1440w.webp) 6 | 7 | ## 运行环境搭建 8 | 9 | 由于 Clojure 是运行在 JVM 上的语言,自然需要先配置好 java 运行环境。之后我们需要安装一个 Clojure 依赖管理工具[Leiningen](https://link.zhihu.com/?target=https%3A//leiningen.org/)(以下简称 lein),安装完毕后别忘了将 bin 目录添加到 PATH 环境变量中。 10 | 11 | 现在让我们打开 cmd,执行下面这条命令来查看 lein 是否安装正确: 12 | 13 | ```text 14 | lein version 15 | ``` 16 | 17 | 如果 cmd 中显示这么一行信息,就表示 lein 已经正确安装了。 18 | 19 | ```text 20 | Leiningen 2.8.1 on Java 1.8.0_144 Java HotSpot(TM) 64-Bit Server VM 21 | ``` 22 | 23 | ## 使用 REPL 24 | 25 | 所谓的 REPL 就是读取-求值-打印-循环,当我们在 REPL 中输入一条表达式之后,表达式的结果会立即回显到 cmd 中,然后等待下一条输入。类似 Python 等很多脚本语言都带有 REPL,Clojure 自然也不例外。 26 | 27 | 在 cmd 中执行下面这条命令就能打开 Clojure 的 REPL 了: 28 | 29 | ```text 30 | lein repl 31 | ``` 32 | 33 | 之后会在 cmd 中输出下列信息: 34 | 35 | ```text 36 | nREPL server started on port 49928 on host 127.0.0.1 - nrepl://127.0.0.1:49928 37 | REPL-y 0.3.7, nREPL 0.2.12 38 | Clojure 1.8.0 39 | Java HotSpot(TM) 64-Bit Server VM 1.8.0_144-b01 40 | Docs: (doc function-name-here) 41 | (find-doc "part-of-name-here") 42 | Source: (source function-name-here) 43 | Javadoc: (javadoc java-object-or-class-here) 44 | Exit: Control+D or (exit) or (quit) 45 | Results: Stored in vars *1, *2, *3, an exception in *e 46 | 47 | user=> 48 | ``` 49 | 50 | 我们试着在 REPL 中运行一个 Hello World 程序: 51 | 52 | ```clojure 53 | user=> (println "Hello World!") 54 | ;; Hello World! 55 | ;=> nil 56 | ``` 57 | 58 | user=>表示我们目前处于 user 命名空间当中。user 命名空间是 REPL 环境的默认命名空间。之后如无必要将会在例程中省略 user=>提示符。 59 | 60 | Lisp 系语言以分号(;)表示注释,相当于 C 语言中的(//)。我会用双分号注释(;;)表示命令行输出,箭头注释(;=>)表示表达式的值。 61 | 62 | 现在让我们把重点放到程序本身: 63 | 64 | ```clojure 65 | (println “Hello World!”) 66 | ``` 67 | 68 | 在 Lisp 中,一对括号表示一个**列表**结构,其中列表项以空格进行分隔(注:在 Clojure 中,逗号(,)和空格是等价的,因此也可以像其他语言一样用逗号进行分隔)。不同于其他语言的数组,Lisp 列表再被求值时,会被解释成一个函数调用。列表的第一项会被解释成函数名,其余则是函数的参数。 69 | 70 | 至于 Lisp 为何要这么做?那可就说来话长了,现阶段我们只需要简单的记下即可。 71 | 72 | 让我们再看一个简单的例子,使用 Clojure 计算 1+1: 73 | 74 | ```clojure 75 | (+ 1 1) 76 | ;=> 2 77 | ``` 78 | 79 | 没错,在 Clojure 中,(+)也是一个函数,因此它看起来是前缀形式的。这看起来比较别扭,不过好处是我们不再需要考虑运算符优先级的问题了: 80 | 81 | ```clojure 82 | (* 3 (+ 1 2)) 83 | ;=> 9 84 | ``` 85 | 86 | 另一个优势是,前缀形式的(+)函数可以很自然的接受任意数量的参数: 87 | 88 | ```clojure 89 | (+ 1 2 3 4 5) 90 | ;=> 15 91 | ``` 92 | 93 | 因此(+)函数很自然的能起到列表求和的作用: 94 | 95 | ```clojure 96 | (apply + [1 2 3 4 5]) 97 | ;=> 15 98 | ``` 99 | 100 | apply 函数的作用是将一个列表中的元素作为函数参数传递给一个函数。 101 | 102 | ## 文档查询 103 | 104 | REPL 还提供了非常方便的文档查询功能。 105 | 106 | - 使用 doc 宏查询文档 107 | 108 | 我们可以用 doc 来查看一下+函数的文档: 109 | 110 | ```clojure 111 | (doc +) 112 | ;;------------------------- 113 | ;;clojure.core/+ 114 | ;;([] [x] [x y] [x y & more]) 115 | ;; Returns the sum of nums. (+) returns 0. Does not auto-promote 116 | ;; longs, will throw on overflow. See also: +' 117 | ;=> nil 118 | ``` 119 | 120 | 在阅读 Clojure 代码时,如果遇到没见过的函数或宏,马上打开 REPL 查询一下吧! 121 | 122 | - 使用 find-doc 寻找文档 123 | 124 | 我们试着来求一下 1 和 0 的异或: 125 | 126 | ```clojure 127 | (xor 1 0) 128 | ;=> CompilerException java.lang.RuntimeException: Unable to resolve symbol: xor in this context, compiling:(null:1:1) 129 | ``` 130 | 131 | 哎呀!Clojure 似乎没有一个叫 xor 的函数。不用担心,我们试试用 find-doc 来查找一下相关函数吧: 132 | 133 | ```clojure 134 | (find-doc "xor") 135 | ;;------------------------- 136 | ;;clojure.core/bit-xor 137 | ;;([x y] [x y & more]) 138 | ;; Bitwise exclusive or 139 | ;=> nil 140 | ``` 141 | 142 | 原来函数名是 bit-xor!让我们来尝试一下: 143 | 144 | ```clojure 145 | (bit-xor 0 1) 146 | ;=> 1 147 | ``` 148 | 149 | - 使用 javadoc 查询 java 类库的文档: 150 | 151 | ```clojure 152 | (javadoc java.util.ArrayList) 153 | ``` 154 | 155 | REPL 会为我们打开一个浏览器页面,显示 ArrayList 的文档。 156 | 157 | ## 使用 Lein 新建 Clojure 项目 158 | 159 | 使用 Lein 创建 Clojure 项目也非常简单,我们先在 cmd 中 cd 到项目要保存的目录下,然后执行这个命令即可: 160 | 161 | ```text 162 | lein new learning-clojure 163 | ``` 164 | 165 | 这样我们就创建了一个名为 learning-clojure 的项目: 166 | 167 | ![img](https://ngte-superbed.oss-cn-beijing.aliyuncs.com/item/v2-6abf7e7d99af2fac3bf2166e47dd69a4_1440w.webp) 168 | 169 | 项目的目录结构有点像 maven,包含一个包含源文件的 src 文件夹,和一个包含测试文件的 test 文件夹。 170 | 171 | ## 使用 REPL 对项目进行测试 172 | 173 | 后缀名为.clj 的文件就是 Clojure 的源文件了。让我们来看一下 src/learning_clojure/core.clj 里面都有些什么内容吧: 174 | 175 | ```clojure 176 | (ns learning-clojure.core) ; 声明命名空间 177 | 178 | (defn foo ; 定义函数foo 179 | "I don't do a whole lot." ; 这是foo函数的说明文档 180 | [x] ; 这是foo函数的参数列表 181 | (println x "Hello, World!")) ; 这是foo函数的函数体 182 | ``` 183 | 184 | 上面的代码定义了一个简单的函数 foo。让我们试着使用 REPL 来测试一下这段代码,首先需要在 cmd 中 cd 到项目的根目录下,然后运行 lein repl 启动 REPL。 185 | 186 | 接下来我们使用 use 指令将 learning-clojure.core 命名空间中的内容导入到当前命名空间: 187 | 188 | ```clojure 189 | (use ‘learning-clojure.core) ; <-注意此处的单引号 190 | ``` 191 | 192 | 为什么这里要加上一个单引号呢?这是因为当 Lisp 对列表进行求值时,会先对列表的每一项进行求值。然而,在我们引入命名空间之前,learning-clojure.core 还不存在,对它求值会发生错误。而单引号(读作 quote)的作用就是阻止对其进行求值: 193 | 194 | ```clojure 195 | (a b c) ; 标识符a、b、c还不存在,无法求值 196 | ;=> CompilerException java.lang.RuntimeException: Unable to resolve symbol: a in this context, compiling:(null:1:1) 197 | ‘(a b c) ; 加上单引号之后会返回列表本身 198 | ;=> (a b c) 199 | (quote (a b c)) ; 单引号实际上是quote宏的简写形式 200 | ;=> (a b c) 201 | ``` 202 | 203 | 引入命名空间之后我们就能使用 REPL 对 foo 函数进行测试了: 204 | 205 | ```clojure 206 | (foo "Bar") 207 | ;; Bar Hello, World! 208 | ;=> nil 209 | ``` 210 | 211 | 现在让我们在 core.clj 文件中添加一个 hello 函数: 212 | 213 | ```clojure 214 | (defn hello 215 | "Say hello to someone." 216 | [name] 217 | (str "Hello, " name)) 218 | ``` 219 | 220 | 保存修改之后,我们需要在 REPL 中使用:reload 命令重新加载模块 221 | 222 | ```clojure 223 | (use :reload ‘learning-clojure.core) 224 | (hello “World!”) 225 | ;=> Hello, World! 226 | ``` 227 | -------------------------------------------------------------------------------- /99~参考资料/2012~《Clojure - Functional Programming for the JVM》~中文版/11~Java 互操作.md: -------------------------------------------------------------------------------- 1 | # Java 互操作 2 | 3 | Clojure 程序可以使用所有的 Java 类以及接口。和在 Java 里面一样 `java.lang` 这个包里面的类是默认导入的。你可以手动的用 `import` 函数来导入其它包的类。看例子: 4 | 5 | ```clj 6 | (import 7 | '(java.util Calendar GregorianCalendar) 8 | '(javax.swing JFrame JLabel)) 9 | ``` 10 | 11 | 同时也可以看下宏 ns 下面的 `[:import](http://xumingming.sinaapp.com/302/clojure-functional-programming-for-the-jvm-clojure-tutorial/#nsMacro) 指令,我们会在后面介绍的。` 12 | 13 | 有两种方式可以访问类里面的常量的: 14 | 15 | ```clj 16 | (. java.util.Calendar APRIL) ; -> 3 17 | (. Calendar APRIL) ; works if the Calendar class was imported 18 | java.util.Calendar/APRIL 19 | Calendar/APRIL ; works if the Calendar class was imported 20 | ``` 21 | 22 | 在 Clojure 代码里面调用 java 的方法是很简单的。因此很多 java 里面已经实现的功能 Clojure 就没有实现自己的了。比如, Clojure 里面没有提供函数来计算一个数的绝对值,因为可以用 `java.lang.Math` 里面的 abs 方法。而另一方面,比如这个类里面还提供了一个 `max` 方法来计算两个数里面比较大的一个, 但是它只接受两个参数,因此 Clojure 里面自己提供了一个可以接受多个参数的 max 函数。 23 | 24 | 有两种方法可以调用 java 里面的静态方法: 25 | 26 | ```clj 27 | (. Math pow 2 4) ; -> 16.0 28 | (Math/pow 2 4) 29 | ``` 30 | 31 | 同样也有两种方法来创建一个新的 java 的对象,看下面的例子。这里注意一下我们用 `def` 创建的对象 bind 到一个全局的 binding。这个其实不是必须的。有好几种方式可以得到一个对象的引用比如把它加入一个集合或者把它传入一个函数。 32 | 33 | ```clj 34 | (import '(java.util Calendar GregorianCalendar)) 35 | (def calendar (new GregorianCalendar 2008 Calendar/APRIL 16)) ; April 16, 2008 36 | (def calendar (GregorianCalendar. 2008 Calendar/APRIL 16)) 37 | ``` 38 | 39 | 同样也有两种方法可以调用 java 对象的方法: 40 | 41 | ```clj 42 | (. calendar add Calendar/MONTH 2) 43 | (. calendar get Calendar/MONTH) ; -> 5 44 | (.add calendar Calendar/MONTH 2) 45 | (.get calendar Calendar/MONTH) ; -> 7 46 | ``` 47 | 48 | 一般来说我们比较推荐使用下面那种用法(.add, .get), 上面那种用法在定义宏的时候用得比较多,这个等到我们讲到宏的时候再做详细介绍。 49 | 50 | 方法调用可以用 `..` 宏串起来: 51 | 52 | ```clj 53 | (. (. calendar getTimeZone) getDisplayName) ; long way 54 | (.. calendar getTimeZone getDisplayName) ; -> "Central Standard Time" 55 | ``` 56 | 57 | 还一个宏: `.?.` 在 `clojure.contrib.core` 名字空间里面,它和上面..这个宏的区别是,在调用的过程中如果有一个返回结果是 nil, 它就不再继续调用了,可以防止出现 `NullPointerException` 异常。 58 | 59 | `doto` 函数可以用来调用一个对象上的多个方法。它返回它的第一个参数,也就是所要调用方法的对象。这对于初始化一个对象的对各属性是非常方便的。(看下面”Namespaces“那一节的 `JFrame` GUI 对象的例子). 比如: 60 | 61 | ```clj 62 | (doto calendar 63 | (.set Calendar/YEAR 1981) 64 | (.set Calendar/MONTH Calendar/AUGUST) 65 | (.set Calendar/DATE 1)) 66 | (def formatter (java.text.DateFormat/getDateInstance)) 67 | (.format formatter (.getTime calendar)) ; -> "Aug 1, 1981" 68 | ``` 69 | 70 | `memfn` 宏可以自动生成代码以使得 java 方法可以当成 clojure 里面的“一等公民”来对待。这个可以用来替代 clojure 里面的匿名方法。当用 `memfn` 来调用 java 里面那些需要参数的方法的时候,你必须给每个参数指定一个名字,以让 clojure 知道你要调用的方法需要几个参数。这些名字到底是什么不重要,但是它们必须要是唯一的,因为要用这些名字来生成 Clojure 代码的。下面的代码用了一个 map 方法来从第二个集合里面取 beginIndex 来作为参数调用第一个集合里面的字符串的 substring 方法。大家可以看一下用匿名函数和用 memfn 来直接调用 java 的方法的区别。 71 | 72 | ```clj 73 | (println (map #(.substring %1 %2) 74 | ["Moe" "Larry" "Curly"] [1 2 3])) ; -> (oe rry ly) 75 | 76 | (println (map (memfn substring beginIndex) 77 | ["Moe" "Larry" "Curly"] [1 2 3])) ; -> same 78 | ``` 79 | 80 | # 代理 81 | 82 | `proxy` 创建一个继承了指定类并且/或者实现了 0 个或者多个接口的类的对象。这对于创建那种必须要实现某个接口才能得到通知的 listener 对象很有用。举一个例子,大家可以看下面 “Desktop Applications” 那一节的例子。那里我们创建了一个继承 JFrame 类并且实现 ActionListener 接口的类的对象。 83 | 84 | # 线程 85 | 86 | 所有的 Clojure 方法都实现了 `[java.lang.Runnable](http://java.sun.com/javase/6/docs/api/java/lang/Runnable.html)` 接口和 `[java.util.concurrent.Callable](http://java.sun.com/javase/6/docs/api/java/util/concurrent/Callable.html)` 接口。这使得非常容易把 Clojure 里面函数和 java 里面的线程一起使用。比如: 87 | 88 | ```clj 89 | (defn delayed-print [ms text] 90 | (Thread/sleep ms) 91 | (println text)) 92 | 93 | ; Pass an anonymous function that invokes delayed-print 94 | ; to the Thread constructor so the delayed-print function 95 | ; executes inside the Thread instead of 96 | ; while the Thread object is being created. 97 | (.start (Thread. #(delayed-print 1000 ", World!"))) ; prints 2nd 98 | (print "Hello") ; prints 1st 99 | ; output is "Hello, World!" 100 | ``` 101 | 102 | # 异常处理 103 | 104 | Clojure 代码里面抛出来的异常都是运行时异常。当然从 Clojure 代码里面调用的 java 代码还是可能抛出那种需要检查的异常的。`try` , `catch` , `finally` 以及 `throw` 提供了和 java 里面类似的功能: 105 | 106 | ```clj 107 | (defn collection? [obj] 108 | (println "obj is a" (class obj)) 109 | ; Clojure collections implement clojure.lang.IPersistentCollection. 110 | (or (coll? obj) ; Clojure collection? 111 | (instance? java.util.Collection obj))) ; Java collection? 112 | 113 | (defn average [coll] 114 | (when-not (collection? coll) 115 | (throw (IllegalArgumentException. "expected a collection"))) 116 | (when (empty? coll) 117 | (throw (IllegalArgumentException. "collection is empty"))) 118 | ; Apply the + function to all the items in coll, 119 | ; then divide by the number of items in it. 120 | (let [sum (apply + coll)] 121 | (/ sum (count coll)))) 122 | 123 | (try 124 | (println "list average =" (average '(2 3))) ; result is a clojure.lang.Ratio object 125 | (println "vector average =" (average [2 3])) ; same 126 | (println "set average =" (average #{2 3})) ; same 127 | (let [al (java.util.ArrayList.)] 128 | (doto al (.add 2) (.add 3)) 129 | (println "ArrayList average =" (average al))) ; same 130 | (println "string average =" (average "1 2 3 4")) ; illegal argument 131 | (catch IllegalArgumentException e 132 | (println e) 133 | ;(.printStackTrace e) ; if a stack trace is desired 134 | ) 135 | (finally 136 | (println "in finally"))) 137 | ``` 138 | 139 | 上面代码的输出是这样的: 140 | 141 | ```clj 142 | obj is a clojure.lang.PersistentList 143 | list average = 5/2 144 | obj is a clojure.lang.LazilyPersistentVector 145 | vector average = 5/2 146 | obj is a clojure.lang.PersistentHashSet 147 | set average = 5/2 148 | obj is a java.util.ArrayList 149 | ArrayList average = 5/2 150 | obj is a java.lang.String 151 | # 153 | in finally 154 | ``` 155 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Java-Series 7 | 8 | 9 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 34 | 38 | 40 | 45 | 46 |
47 | 64 | 97 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 143 | 144 | 145 | 146 | 155 | 156 | 157 | -------------------------------------------------------------------------------- /99~参考资料/《Clojure 官方教程》/06~Namespaces.md: -------------------------------------------------------------------------------- 1 | ## Namespaces and names 2 | 3 | Namespaces provide a means to organize our code and the names we use in our code. Specifically, they let us give new unambiguous names to functions or other values. These full names are naturally long because they include context. Thus namespaces also provide a means to unambiguously reference the names of other functions and values but using names that are shorter and easier to type. 4 | 5 | A namespace is both a name context and a container for vars. Namespace names are symbols where periods are used to separate namespace parts, such as `clojure.string`. By convention, namespace names are typically lower-case and use `-` to separate words, although this is not required. 6 | 7 | ### Vars 8 | 9 | Vars are associations between a name (a symbol) and a value. Vars in a namespace have a fully-qualified name that is the combination of the namespace name and the var name. For example, `clojure.string/join` is a fully-qualified var name where `clojure.string` refers to the namespace and `join` refers to the var inside the namespace. All vars are globally accessible via their fully-qualified name. By convention vars have lower case names with `-` separating words, although this is also not required. Var names may contain most non-whitespace characters. 10 | 11 | Vars are created using `def` and other special forms or macros that start with `def`, like `defn`. Vars are created in the "current" namespace. The Clojure runtime tracks the current namespace in the var `clojure.core/*ns*`. The current namespace can be changed using the `in-ns` function. 12 | 13 | ### Loading 14 | 15 | In addition to providing a naming context, namespace names also provide a convention for where a namespace’s code should be found for loading. A path is created based on the namespace name: 16 | 17 | - Periods become directory separators 18 | - Hyphens become underscores 19 | - The file extension `.clj` is added 20 | 21 | Thus the namespace name `com.some-example.my-app` becomes the load path `com/some_example/my_app.clj`. Load paths are searched using the JVM classpath. The classpath is a series of directory locations or JAR files (JARs are essentially just zip files). 22 | 23 | When a resource is needed, the JVM searches each classpath location in order for a file at the relative location of the load path. So if the classpath was `src:test`, the load path would be checked at `src/com/some_example/my_app.clj` then `test/com/some_example/my_app.clj`. 24 | 25 | There are several ways to load code in Clojure, but most commonly loading is accomplished via `require`. 26 | 27 | Due to this loading convention, most Clojure is structured with a 1-to-1 mapping of namespaces to files, stored in hierarchical fashion that maps to the namespace structure. 28 | 29 | ## Declaring namespaces 30 | 31 | Most Clojure files represent a single namespace and declare the dependencies for that namespace at the top of the file using the `ns` macro, which often looks like this: 32 | 33 | ``` 34 | (ns com.some-example.my-app 35 | "My app example" 36 | (:require 37 | [clojure.set :as set] 38 | [clojure.string :as str])) 39 | ``` 40 | 41 | The `ns` macro specifies the namespace name (this should match the file path location using the conventions above), an optional docstring, and then one or more clauses that declare things about the namespace. 42 | 43 | ### Refer 44 | 45 | By default, we can refer to or invoke vars in the current namespace without specifying the namespace (the current namespace is the "default"). 46 | 47 | Additionally, you may have noticed that we can usually refer to `clojure.core` library functions without fully qualifying them either. The reason for that is that all of the `clojure.core` library vars have been `referred` into the current namespace. `refer` makes an entry in the current namespace’s symbol table that refers to the var in the other namespace. 48 | 49 | The `clojure.core` referral is done by the `ns` macro. (There are ways to suppress this in part if you’d like to re-use names in core without warnings.) 50 | 51 | ### require 52 | 53 | The `:require` clause corresponds to the `require` function which specifies one or more namespaces to load that this namespace depends on. For each namespace, `require` can do several things: 54 | 55 | - Load (or reload) the namespace 56 | - Optionally assign an _alias_ that can be used to refer to vars from the loaded namespace only in the scope of this namespace 57 | - Optionally _refer_ vars from the loaded namespace for use by unqualified name in this namespace 58 | 59 | The last two parts are all about making names easier to use. While vars can always be referred to by their fully-qualified name, we rarely want to type fully-qualified names in our code. Aliases let us use shorter versions of longer fully-qualified aliases. Refer allows us to use names without a namespace qualifier at all. 60 | 61 | In `require`, namespaces most commonly take one of four forms: 62 | 63 | - `clojure.set` - just loads `clojure.set` namespace (if not already loaded) 64 | - `[clojure.set :as set]` - load and create an alias `set` for the namespace `clojure.set` 65 | - This allows you to refer to vars in `set` with for example `set/union` instead of `clojure.set/union` 66 | - `[clojure.set :refer [union intersection]]` - load and refer specific vars into this namespace 67 | - This allows you to use just `union` instead of `clojure.set/union` 68 | - `[company.application.component.user :as-alias user]` - create an alias `user` for the namespace `company.application.component.user` without loading the namespace 69 | - Typically, when using `:as-alias`, the namespace is being used as a qualifier but is not a loadable namespace 70 | - This allows you to use a shorthand for a namespace qualifier, e.g. when creating maps: `{::user/id 1}`, registering specs: `(s/def ::user/id int?)` or destructuring: `(defn find-by-id [{::user/keys [id]}] ,,,)` 71 | 72 | ### Java classes and imports 73 | 74 | In addition to vars, Clojure also provides support for Java interop and access to Java classes, which live in packages. Java classes can always be referred to using their fully-qualified class name, such as `java.util.Date`. 75 | 76 | The `ns` macro also imports the classes in the java.lang package so that they can be used as just the class name, rather than the fully-qualified class name. For example, just `String` rather than `java.lang.String`. 77 | 78 | Similar to `:refer`, the `ns` macro has an `:import` clause (that is supported by the `import` macro) that lets you import other classes so they can be used with unqualified names: 79 | 80 | ```clj 81 | (ns com.some-example.my-app2 82 | (:import 83 | [java.util Date UUID] 84 | [java.io File])) 85 | ``` 86 | 87 | This example imports the `Date` and `UUID` class from the `java.util` package and the `File` class from the `java.io` package. 88 | -------------------------------------------------------------------------------- /99~参考资料/2012~《Clojure - Functional Programming for the JVM》~中文版/10~函数定义.md: -------------------------------------------------------------------------------- 1 | # 函数定义 2 | 3 | defn 宏用来定义一个函数。它的参数包括一个函数名字,一个可选的注释字符串,参数列表,然后一个方法体。而函数的返回值则是方法体里面最后一个表达式的值。所有的函数都会返回一个值,只是有的返回的值是 nil。看例子: 4 | 5 | ```clj 6 | (defn parting 7 | "returns a String parting" 8 | [name] 9 | (str "Goodbye, " name)) ; concatenation 10 | 11 | (println (parting "Mark")) ; -> Goodbye, Mark 12 | ``` 13 | 14 | 函数必须先定义再使用。有时候可能做不到,比如两个方法项目调用,clojure 采用了和 C 语言里面类似的做法: declare, 看例子: 15 | 16 | ``` 17 | (declare function-names) 18 | ``` 19 | 20 | 通过宏 `defn-` 定义的函数是私有的. 这意味着它们只在定义它们的名字空间里面可见. 其它一些类似定义私有函数/宏的还有: `defmacro-` 和 `defstruct-` (在 `clojure.contrib.def` 里面)。 21 | 22 | 函数的参数个数可以是不定的。可选的那些参数必须放在最后面(这一点跟其它语言是一样的), 你可以通过加个&符号把它们收集到一个 list 里面去 Functions can take a variable number of parameters. Optional parameters must appear at the end. They are gathered into a list by adding an ampersand and a name for the list at the end of the parameter list. 23 | 24 | ``` 25 | (defn power [base & exponents] 26 | ; Using java.lang.Math static method pow. 27 | (reduce #(Math/pow %1 %2) base exponents)) 28 | (power 2 3 4) ; 2 to the 3rd = 8; 8 to the 4th = 4096 29 | ``` 30 | 31 | 函数定义可以包含多个参数列表以及对应的方法体。每个参数列表必须包含不同个数的参数。这通常用来给一些参数指定默认值。看例子: 32 | 33 | ``` 34 | (defn parting 35 | "returns a String parting in a given language" 36 | ([] (parting "World")) 37 | ([name] (parting name "en")) 38 | ([name language] 39 | ; condp is similar to a case statement in other languages. 40 | ; It is described in more detail later. 41 | ; It is used here to take different actions based on whether the 42 | ; parameter "language" is set to "en", "es" or something else. 43 | (condp = language 44 | "en" (str "Goodbye, " name) 45 | "es" (str "Adios, " name) 46 | (throw (IllegalArgumentException. 47 | (str "unsupported language " language)))))) 48 | 49 | (println (parting)) ; -> Goodbye, World 50 | (println (parting "Mark")) ; -> Goodbye, Mark 51 | (println (parting "Mark" "es")) ; -> Adios, Mark 52 | (println (parting "Mark", "xy")) 53 | ; -> java.lang.IllegalArgumentException: unsupported language xy 54 | ``` 55 | 56 | 匿名函数是没有名字的。他们通常被当作参数传递给其他有名函数(相对于匿名函数)。匿名函数对于那些只在一个地方使用的函数比较有用。下面是定义匿名函数的两种方法: 57 | 58 | ``` 59 | (def years [1940 1944 1961 1985 1987]) 60 | (filter (fn [year] (even? year)) years) ; long way w/ named arguments -> (1940 1944) 61 | (filter #(even? %) years) ; short way where % refers to the argument 62 | ``` 63 | 64 | 通过 `fn` 定义的匿名函数可以包含任意个数的表达式;而通过 `#(...)` , 定义的匿名函数则只能包含一个表达式,如果你想包含多个表达式,那么把它用 `do` 包起来。如果只有一个参数,那么你可以通过 `%` 来引用它;如果有多个参数,那么可以通过 `%1` , `%2` 等等来引用。看例子: 65 | 66 | ``` 67 | (defn pair-test [test-fn n1 n2] 68 | (if (test-fn n1 n2) "pass" "fail")) 69 | 70 | ; Use a test-fn that determines whether 71 | ; the sum of its two arguments is an even number. 72 | (println (pair-test #(even? (+ %1 %2)) 3 5)) ; -> pass 73 | ``` 74 | 75 | Java 里面的方法可以根据参数的类型来进行重载。而 Clojure 里面则只能根据参数的个数来进行重载。不过 Clojure 里面的 multimethods 技术可以实现任意 类型的重载。 76 | 77 | 宏 `defmulti` 和 `defmethod` 经常被用在一起来定义 multimethod. 宏 `defmulti` 的参数包括一个方法名以及一个 dispatch 函数,这个 dispatch 函数的返回值会被用来选择到底调用哪个重载的函数。宏 `defmethod` 的参数则包括方法名,dispatch 的值,参数列表以及方法体。一个特殊的 dispatch 值 `:default` 是用来表示默认情况的 — 即如果其它的 dispatch 值都不匹配的话,那么就调用这个方法。`defmethod` 多定义的名字一样的方法,它们的参数个数必须一样。传给 multimethod 的参数会传给 dipatch 函数的。 78 | 79 | 下面是一个用 multimethod 来实现基于参数的类型来进行重载的例子: 80 | 81 | ```clj 82 | (defmulti what-am-i class) ; class is the dispatch function 83 | (defmethod what-am-i Number [arg] (println arg "is a Number")) 84 | (defmethod what-am-i String [arg] (println arg "is a String")) 85 | (defmethod what-am-i :default [arg] (println arg "is something else")) 86 | (what-am-i 19) ; -> 19 is a Number 87 | (what-am-i "Hello") ; -> Hello is a String 88 | (what-am-i true) ; -> true is something else 89 | ``` 90 | 91 | 因为 dispatch 函数可以是任意一个函数,所以你也可以写你自己的 dispatch 函数。比如一个自定义的 dispatch 函数可以会根据一个东西的尺寸大小来返回 `:small` , `:medium` 以及 `:large` 。然后对应每种尺寸有一个方法。 92 | 93 | 下划线可以用来作为参数占位符 ?– 如果你不要使用这个参数的话。这个特性在回调函数里面比较有用,因为回调函数的设计者通常想把尽可能多的信息给你,而你通常可能只需要其中的一部分。看例子: 94 | 95 | ``` 96 | (defn callback1 [n1 n2 n3] (+ n1 n2 n3)) ; uses all three arguments 97 | (defn callback2 [n1 _ n3] (+ n1 n3)) ; only uses 1st & 3rd arguments 98 | (defn caller [callback value] 99 | (callback (+ value 1) (+ value 2) (+ value 3))) 100 | (caller callback1 10) ; 11 + 12 + 13 -> 36 101 | (caller callback2 10) ; 11 + 13 -> 24 102 | ``` 103 | 104 | `complement` 函数接受一个函数作为参数,如果这个参数返回值是 true,那么它就返回 false, 相当于一个取反的操作。看例子: 105 | 106 | ``` 107 | (defn teenager? [age] (and (>= age 13) (< age 20))) 108 | (def non-teen? (complement teenager?)) 109 | (println (non-teen? 47)) ; -> true 110 | ``` 111 | 112 | `comp` 把任意多个函数组合成一个,前面一个函数的返回值作为后面一个函数的参数。**调用的顺序是从右到左(注意不是从左到右)** 看例子: 113 | 114 | ```clj 115 | (defn times2 [n] (* n 2)) 116 | (defn minus3 [n] (- n 3)) 117 | ; Note the use of def instead of defn because comp returns 118 | ; a function that is then bound to "my-composition". 119 | (def my-composition (comp minus3 times2)) 120 | (my-composition 4) ; 4*2 - 3 -> 5 121 | ``` 122 | 123 | `partial` 函数创建一个新的函数 — 通过给旧的函数制定一个初始值,然后再调用原来的函数。比如 `*` 是一个可以接受多个参数的函数,它的作用就是计算它们的乘积,如果我们想要一个新的函数,使的返回结果始终是乘积的 2 倍,我们可以这样做: 124 | 125 | ```clj 126 | ; Note the use of def instead of defn because partial returns 127 | ; a function that is then bound to "times2". 128 | (def times2 (partial * 2)) 129 | (times2 3 4) ; 2 * 3 * 4 -> 24 130 | ``` 131 | 132 | 下面是一个使用 `map` 和 `partial` 的有趣的例子. 133 | 134 | ```clj 135 | (defn- polynomial 136 | "computes the value of a polynomial 137 | with the given coefficients for a given value x" 138 | [coefs x] 139 | ; For example, if coefs contains 3 values then exponents is (2 1 0). 140 | (let [exponents (reverse (range (count coefs)))] 141 | ; Multiply each coefficient by x raised to the corresponding exponent 142 | ; and sum those results. 143 | ; coefs go into %1 and exponents go into %2. 144 | (apply + (map #(* %1 (Math/pow x %2)) coefs exponents)))) 145 | 146 | (defn- derivative 147 | "computes the value of the derivative of a polynomial 148 | with the given coefficients for a given value x" 149 | [coefs x] 150 | ; The coefficients of the derivative function are obtained by 151 | ; multiplying all but the last coefficient by its corresponding exponent. 152 | ; The extra exponent will be ignored. 153 | (let [exponents (reverse (range (count coefs))) 154 | derivative-coefs (map #(* %1 %2) (butlast coefs) exponents)] 155 | (polynomial derivative-coefs x))) 156 | 157 | (def f (partial polynomial [2 1 3])) ; 2x^2 + x + 3 158 | (def f-prime (partial derivative [2 1 3])) ; 4x + 1 159 | 160 | (println "f(2) =" (f 2)) ; -> 13.0 161 | (println "f'(2) =" (f-prime 2)) ; -> 9.0 162 | ``` 163 | 164 | 下面是另外一种做法 (Francesco Strino 建议的). 165 | 166 | %1 = a, %2 = b, result is ax + b 167 | 168 | %1 = ax + b, %2 = c, result is (ax + b)x + c = ax^2 + bx + c 169 | 170 | ```clj 171 | (defn- polynomial 172 | "computes the value of a polynomial 173 | with the given coefficients for a given value x" 174 | [coefs x] 175 | (reduce #(+ (* x %1) %2) coefs)) 176 | ``` 177 | 178 | `memoize` 函数接受一个参数,它的作用就是给原来的函数加一个缓存,所以如果同样的参数被调用了两次,那么它就直接从缓存里面返回缓存了的结果,以提高效率,但是当然它会需要更多的内存。(其实也只有函数式编程里面能用这个技术,因为函数没有 side-effect, 多次调用的结果保证是一样的) 179 | 180 | `time` 宏可以看成一个 wrapper 函数,它会打印被它包起来的函数的执行时间,并且返回这个函数的返回值。看下面例子里面是怎么用的。 181 | 182 | 下面的例子演示在多项式的的计算里面使用 memoize: 183 | 184 | ```clj 185 | ; Note the use of def instead of defn because memoize returns 186 | ; a function that is then bound to "memo-f". 187 | (def memo-f (memoize f)) 188 | 189 | (println "priming call") 190 | (time (f 2)) 191 | 192 | (println "without memoization") 193 | ; Note the use of an underscore for the binding that isn't used. 194 | (dotimes [_ 3] (time (f 2))) 195 | 196 | (println "with memoization") 197 | (dotimes [_ 3] (time (memo-f 2))) 198 | ``` 199 | 200 | 上面代码的输出是这样的: 201 | 202 | ``` 203 | priming call 204 | "Elapsed time: 4.128 msecs" 205 | without memoization 206 | "Elapsed time: 0.172 msecs" 207 | "Elapsed time: 0.365 msecs" 208 | "Elapsed time: 0.19 msecs" 209 | with memoization 210 | "Elapsed time: 0.241 msecs" 211 | "Elapsed time: 0.033 msecs" 212 | "Elapsed time: 0.019 msecs" 213 | ``` 214 | 215 | 从上面的输出我们可以看到好几个东西。首先第一个方法调用比其它的都要长很多。– 其实这和用不用 memonize 没有什么关系。第一个 memoize 调用所花的时间也要比其他 memoize 调用花的时间要长,因为要操作缓存,其它的 memoize 调用就要快很多了。 216 | -------------------------------------------------------------------------------- /99~参考资料/《Clojure 官方教程》/05~Flow Control.md: -------------------------------------------------------------------------------- 1 | ## Statements vs. Expressions 2 | 3 | In Java, expressions return values, whereas statements do not. 4 | 5 | ```clj 6 | // "if" is a statement because it doesn't return a value: 7 | String s; 8 | if (x > 10) { 9 | s = "greater"; 10 | } else { 11 | s = "less or equal"; 12 | } 13 | obj.someMethod(s); 14 | 15 | // Ternary operator is an expression; it returns a value: 16 | obj.someMethod(x > 10 ? "greater" : "less or equal"); 17 | ``` 18 | 19 | In Clojure, however, everything is an expression! _Everything_ returns a value, and a block of multiple expressions returns the last value. Expressions that exclusively perform side-effects return `nil`. 20 | 21 | ## Flow Control Expressions 22 | 23 | Accordingly, flow control operators are expressions, too! 24 | 25 | Flow control operators are composable, so we can use them anywhere. This leads to less duplicate code, as well as fewer intermediate variables. 26 | 27 | Flow control operators are also extensible via macros, which allow the compiler to be extended by user code. We won’t be discussing macros today, but you can read more about them at [Macros](https://clojure.org/reference/macros), [Clojure from the Ground Up](https://aphyr.com/posts/305-clojure-from-the-ground-up-macros), or [Clojure for the Brave and True](http://www.braveclojure.com/writing-macros/), among many other places. 28 | 29 | ### `if` 30 | 31 | `if` is the most important conditional expression - it consists of a condition, a "then", and an "else". `if` will only evaluate the branch selected by the conditional. 32 | 33 | ``` 34 | user=> (str "2 is " (if (even? 2) "even" "odd")) 35 | 2 is even 36 | 37 | user=> (if (true? false) "impossible!") ;; else is optional 38 | nil 39 | ``` 40 | 41 | ### Truth 42 | 43 | In Clojure, all values are logically true or false. The only "false" values are `false` and `nil` - all other values are logically true. 44 | 45 | ``` 46 | user=> (if true :truthy :falsey) 47 | :truthy 48 | user=> (if (Object.) :truthy :falsey) ; objects are true 49 | :truthy 50 | user=> (if [] :truthy :falsey) ; empty collections are true 51 | :truthy 52 | user=> (if 0 :truthy :falsey) ; zero is true 53 | :truthy 54 | user=> (if false :truthy :falsey) 55 | :falsey 56 | user=> (if nil :truthy :falsey) 57 | :falsey 58 | ``` 59 | 60 | ### `if` and `do` 61 | 62 | The `if` only takes a single expression for the "then" and "else". Use `do` to create larger blocks that are a single expression. 63 | 64 | Note that the only reason to do this is if your bodies have side effects! (Why?) 65 | 66 | ``` 67 | (if (even? 5) 68 | (do (println "even") 69 | true) 70 | (do (println "odd") 71 | false)) 72 | ``` 73 | 74 | ### `when` 75 | 76 | `when` is an `if` with only a `then` branch. It checks a condition and then evaluates any number of statements as a body (so no `do` is required). The value of the last expression is returned. If the condition is false, nil is returned. 77 | 78 | `when` communicates to a reader that there is no "else" branch. 79 | 80 | ``` 81 | (when (neg? x) 82 | (throw (RuntimeException. (str "x must be positive: " x)))) 83 | ``` 84 | 85 | ### `cond` 86 | 87 | `cond` is a series of tests and expressions. Each test is evaluated in order and the expression is evaluated and returned for the first true test. 88 | 89 | ``` 90 | (let [x 5] 91 | (cond 92 | (< x 2) "x is less than 2" 93 | (< x 10) "x is less than 10")) 94 | ``` 95 | 96 | ### `cond` and `else` 97 | 98 | If no test is satisfied, nil is returned. A common idiom is to use a final test of `:else`. Keywords (like `:else`) always evaluate to true so this will always be selected as a default. 99 | 100 | ``` 101 | (let [x 11] 102 | (cond 103 | (< x 2) "x is less than 2" 104 | (< x 10) "x is less than 10" 105 | :else "x is greater than or equal to 10")) 106 | ``` 107 | 108 | ### `case` 109 | 110 | `case` compares an argument to a series of values to find a match. This is done in constant (not linear) time! However, each value must be a compile-time literal (numbers, strings, keywords, etc). 111 | 112 | Unlike `cond`, `case` will throw an exception if no value matches. 113 | 114 | ```clj 115 | user=> (defn foo [x] 116 | (case x 117 | 5 "x is 5" 118 | 10 "x is 10")) 119 | #'user/foo 120 | 121 | user=> (foo 10) 122 | x is 10 123 | 124 | user=> (foo 11) 125 | IllegalArgumentException No matching clause: 11 126 | ``` 127 | 128 | ### `case` with `else`-expression 129 | 130 | `case` can have a final trailing expression that will be evaluated if no test matches. 131 | 132 | ``` 133 | user=> (defn foo [x] 134 | (case x 135 | 5 "x is 5" 136 | 10 "x is 10" 137 | "x isn't 5 or 10")) 138 | #'user/foo 139 | 140 | user=> (foo 11) 141 | x isn't 5 or 10 142 | ``` 143 | 144 | ## Iteration for Side Effects 145 | 146 | ### `dotimes` 147 | 148 | - Evaluate expression _n_ times 149 | - Returns `nil` 150 | 151 | ``` 152 | user=> (dotimes [i 3] 153 | (println i)) 154 | 0 155 | 1 156 | 2 157 | nil 158 | ``` 159 | 160 | ### `doseq` 161 | 162 | - Iterates over a sequence 163 | - If a lazy sequence, forces evaluation 164 | - Returns `nil` 165 | 166 | ``` 167 | user=> (doseq [n (range 3)] 168 | (println n)) 169 | 0 170 | 1 171 | 2 172 | nil 173 | ``` 174 | 175 | ### `doseq` with multiple bindings 176 | 177 | - Similar to nested `foreach` loops 178 | - Processes all permutations of sequence content 179 | - Returns `nil` 180 | 181 | ``` 182 | user=> (doseq [letter [:a :b] 183 | number (range 3)] ; list of 0, 1, 2 184 | (prn [letter number])) 185 | [:a 0] 186 | [:a 1] 187 | [:a 2] 188 | [:b 0] 189 | [:b 1] 190 | [:b 2] 191 | nil 192 | ``` 193 | 194 | ## Clojure’s `for` 195 | 196 | - List comprehension, **not** a for-loop 197 | - Generator function for sequence permutation 198 | - Bindings behave like `doseq` 199 | 200 | ``` 201 | user=> (for [letter [:a :b] 202 | number (range 3)] ; list of 0, 1, 2 203 | [letter number]) 204 | ([:a 0] [:a 1] [:a 2] [:b 0] [:b 1] [:b 2]) 205 | ``` 206 | 207 | ## Recursion 208 | 209 | ### Recursion and Iteration 210 | 211 | - Clojure provides recur and the sequence abstraction 212 | - `recur` is "classic" recursion 213 | - Consumers don’t control it, considered a lower-level facility 214 | - Sequences represent iteration as values 215 | - Consumers can partially iterate 216 | - Reducers represent iteration as function composition 217 | - Added in Clojure 1.5, not covered here 218 | 219 | ### `loop` and `recur` 220 | 221 | - Functional looping construct 222 | - `loop` defines bindings 223 | - `recur` re-executes `loop` with new bindings 224 | - Prefer higher-order library functions instead 225 | 226 | ``` 227 | (loop [i 0] 228 | (if (< i 10) 229 | (recur (inc i)) 230 | i)) 231 | ``` 232 | 233 | ### `defn` and `recur` 234 | 235 | - Function arguments are implicit `loop` bindings 236 | 237 | ``` 238 | (defn increase [i] 239 | (if (< i 10) 240 | (recur (inc i)) 241 | i)) 242 | ``` 243 | 244 | ### `recur` for recursion 245 | 246 | - `recur` must be in "tail position" 247 | - The last expression in a branch 248 | - `recur` must provide values for all bound symbols by position 249 | - Loop bindings 250 | - defn/fn arguments 251 | - Recursion via `recur` does not consume stack 252 | 253 | ## Exceptions 254 | 255 | ### Exception handling 256 | 257 | - `try`/`catch`/`finally` as in Java 258 | 259 | ``` 260 | (try 261 | (/ 2 1) 262 | (catch ArithmeticException e 263 | "divide by zero") 264 | (finally 265 | (println "cleanup"))) 266 | ``` 267 | 268 | ### Throwing exceptions 269 | 270 | ``` 271 | (try 272 | (throw (Exception. "something went wrong")) 273 | (catch Exception e (.getMessage e))) 274 | ``` 275 | 276 | ### Exceptions with Clojure data 277 | 278 | - `ex-info` takes a message and a map 279 | - `ex-data` gets the map back out 280 | - Or `nil` if not created with `ex-info` 281 | 282 | ```clj 283 | (try 284 | (throw (ex-info "There was a problem" {:detail 42})) 285 | (catch Exception e 286 | (prn (:detail (ex-data e))))) 287 | ``` 288 | 289 | ### `with-open` 290 | 291 | ```clj 292 | (let [f (clojure.java.io/writer "/tmp/new")] 293 | (try 294 | (.write f "some text") 295 | (finally 296 | (.close f)))) 297 | 298 | ;; Can be written: 299 | (with-open [f (clojure.java.io/writer "/tmp/new")] 300 | (.write f "some text")) 301 | ``` 302 | -------------------------------------------------------------------------------- /99~参考资料/2012~《Clojure - Functional Programming for the JVM》~中文版/05~Clojure 语法.md: -------------------------------------------------------------------------------- 1 | # Clojure 语法 2 | 3 | Lisp 方言有一个非常简洁的语法 — 有些人觉得很美的语法。数据和代码的表达形式是一样的,一个列表的列表很自然地在内存里面表达成一个 tree。(a b c)表示一个对函数 a 的调用,而参数是 b 和 c。如果要表示数据,你需要使用 `'(a b c)` o 或者 `(quote (a b c))` 。通常情况下就是这样了,除了一些特殊情况 — 到底有多少特殊情况取决于你所使用的方言。 4 | 5 | 我们把这些特殊情况称为语法糖。语法糖越多代码写起来越简介,但是同时我们也要学习更多的东西以读懂这些代码。这需要找到一个平衡点。很多语法糖都有对应的函数可以调用。到底语法糖是多了还是少了还是你们自己来判断吧。 6 | 7 | 下面这个表格简要地列举了 Clojure 里面的一些语法糖,这些语法糖我们会在后面详细讲解的,所以如果你现在没办法完全理解它们不用担心。 8 | 9 | | 作用 | 语法糖 | 对应函数 | 10 | | ------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------- | --------------------------------------------------------------- | 11 | | 注释 | `; _text_` _单行注释_ | `宏(comment _text_)可以用来写多行注释` | 12 | | 字符 (Java `char` 类型) | `\_char_` `\tab` `\newline` `\space` `\u_unicode-hex-value_` | `(char _ascii-code_)` `(char \u_unicode_` ) | 13 | | 字符串 (Java `String` 对象) | `"_text_"` | `(str _char1_ _char2_ ...)` 可以把各种东西串成一个字符串 | 14 | | 关键字是一个内部字符串; 两个同样的关键字指向同一个对象; 通常被用来作为 map 的 key | `:_name_` | `(keyword "_name_")` | 15 | | 当前命名空间的关键字 | `::_name_` | N/A | 16 | | 正则表达式 | `#"_pattern_"` | `(re-pattern _pattern_)` | 17 | | 逗号被当成空白(通常用在集合里面用来提高代码可读性) | `,` (逗号) | N/A | 18 | | 链表(linked list) | `'(_items_)` (不会 evaluate 每个元素) | `(list _items_)` 会 evaluate 每个元素 | 19 | | vector(和数组类似) | `[_items_]` | `(vector _items_)` | 20 | | set | `#{_items_}` 建立一个 hash-set | `(hash-set _items_)` `(sorted-set _items_)` | 21 | | map | `{_key-value-pairs_}` 建立一个 hash-map | `(hash-map _key-value-pairs_)` `(sorted-map _key-value-pairs_)` | 22 | | 给 symbol 或者集合绑定元数据 | `#^{_key-value-pairs_} _object_` 在读入期处理 | `(with-meta _object_ _metadata-map_)` 在运行期处理 | 23 | | 获取 symbol 或者集合的元数据 | `^_object_` | `(meta _object_)` | 24 | | 获取一个函数的参数列表(个数不定的) | `& _name_` | N/A | 25 | | 函数的不需要的参数的默认名字 | `_` (下划线) | N/A | 26 | | 创建一个 java 对象(注意 class-name 后面的点) | `(_class-name_. _args_)` | `(new _class-name_ _args_)` | 27 | | 调用 java 方法 | `(. _class-or-instance_ _method-name_ _args_)` 或者 `(._method-name_ _class-or-instance_ _args_)` | N/A | 28 | | 串起来调用多个函数,前面一个函数的返回值会作为后面一个函数的第一个参数;你还可以在括号里面指定额外参数;注意前面的两个点 | `(.. _class-or-object_ (_method1 args_) (_method2 args_) ...)` | N/A | 29 | | 创建一个匿名函数 | `#(_single-expression_)` 用 `%` (等同于 `%1` ), `%1` , `%2来表示参数` | `(fn [_arg-names_] _expressions_)` | 30 | | 获取 Ref, Atom 和 Agent 对应的 valuea | `@_ref_` | `(deref _ref_)` | 31 | | get `Var` object instead of the value of a symbol (var-quote) | `#'_name_` | `(var _name_)` | 32 | | syntax quote (使用在宏里面) | ``` | none | 33 | | unquote (使用在宏里面) | `~_value_` | `(unquote _value_)` | 34 | | unquote splicing (使用在宏里面) | `~@_value_` | none | 35 | | auto-gensym (在宏里面用来产生唯一的 symbol 名字) | `_prefix_#` | `(gensym _prefix_ )` | 36 | 37 | 对于二元操作符比如 +和\*, Lisp 方言使用前置表达式而不是中止表达式,这和一般的语言是不一样的。比如在 java 里面你可能会写 `a + b + c` , 而在 Lisp 里面它相当于 `(+ a b c) 。这种表达方式的一个好处是如果操作数有多个,那么操作符只用写一次` . 其它语言里面的二元操作符在 lisp 里面是函数,所以可以有多个操作数。 38 | 39 | Lisp 代码比其它语言的代码有更多的小括号的一个原因是 Lisp 里面不使用其它语言使用的大括号,比如在 java 里面,方法代码是被包含在大括号里面的,而在 lisp 代码里面是包含在小括号里面的。 40 | 41 | 比较下面两段简单的 Java 和 Clojure 代码,它们实现相同的功能。它们的输出都是: “edray” 和 “orangeay”. 42 | 43 | ```java 44 | // This is Java code. 45 | public class PigLatin { 46 | 47 | public static String pigLatin(String word) { 48 | char firstLetter = word.charAt(0); 49 | if ("aeiou".indexOf(firstLetter) != -1) return word + "ay"; 50 | return word.substring(1) + firstLetter + "ay"; 51 | } 52 | 53 | public static void main(String args[]) { 54 | System.out.println(pigLatin("red")); 55 | System.out.println(pigLatin("orange")); 56 | } 57 | } 58 | ``` 59 | 60 | ```clojure 61 | ; This is Clojure code. 62 | ; When a set is used as a function, it returns a boolean 63 | ; that indicates whether the argument is in the set. 64 | (def vowel? (set "aeiou")) 65 | 66 | (defn pig-latin [word] ; defines a function 67 | ; word is expected to be a string 68 | ; which can be treated like a sequence of characters. 69 | (let [first-letter (first word)] ; assigns a local binding 70 | (if (vowel? first-letter) 71 | (str word "ay") ; then part of if 72 | (str (subs word 1) first-letter "ay")))) ; else part of if 73 | 74 | (println (pig-latin "red")) 75 | (println (pig-latin "orange")) 76 | ``` 77 | 78 | Clojure 支持所有的常见数据类型比如 booleans ( `true` and `false` ), 数字, 高精度浮点数, 字符(上面表格里面提到过 ) 以及字符串. 同时还支持分数 — 不是浮点数,因此在计算的过程中不会损失精度. 79 | 80 | Symbols 是用来给东西命名的. 这些名字是被限制在名字空间里面的,要么是指定的名字空间,要么是当前的名字空间. Symbols 的值是它所代表的名字的值. 要使用 Symbol 的值,你必须把它用引号引起来. 81 | 82 | 关键字以冒号打头,被用来当作唯一标示符,通常用在 map 里面 (比如 `:red` , `:green` 和 `:blue` ). 83 | 84 | 和任何语言一样,你可以写出很难懂的 Clojure 代码。遵循一些最佳实践可以避免这个。写一些简短的,专注自己功能的函数可以使函数变得容易读,测试以及重复利用。经常使用“抽取方法”的模式来对你的代码进行重构。高度内嵌的函数是非常难懂得,千万不要这么写,你可以使用 let 来帮助你。把匿名函数传递给命名函数是非常常见的,但是不要把一个匿名函数传递给另外一个匿名函数,这样代码就很难懂了。 85 | -------------------------------------------------------------------------------- /header.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 101 | 366 |
367 |

368 | Java Series by 王下邀月熊 369 |

370 |

371 | 现代 Java 开发与工程实践 372 |

373 |
374 | 375 |
376 |
377 |
378 |
379 |
380 |
381 |
382 |
383 |
384 |
385 |
386 |
387 |
388 |
389 |
390 | 391 | 392 |
393 |
394 |
-------------------------------------------------------------------------------- /99~参考资料/《Clojure 官方教程》/04~Hashed Collections.md: -------------------------------------------------------------------------------- 1 | # Hashed Collections 2 | 3 | As described in the previous section, there are four key Clojure collection types: vectors, lists, sets, and maps. Of those four collection types, sets and maps are hashed collections, designed for efficient lookup of elements. 4 | 5 | ## Sets 6 | 7 | Sets are like mathematical sets - unordered and with no duplicates. Sets are ideal for efficiently checking whether a collection contains an element, or to remove any arbitrary element. 8 | 9 | ``` 10 | (def players #{"Alice", "Bob", "Kelly"}) 11 | ``` 12 | 13 | ### Adding to a set 14 | 15 | As with vectors and lists, `conj` is used to add elements. 16 | 17 | ``` 18 | user=> (conj players "Fred") 19 | #{"Alice" "Fred" "Bob" "Kelly"} 20 | ``` 21 | 22 | ### Removing from a set 23 | 24 | The `disj` ("disjoin") function is used to remove one or more elements from a set. 25 | 26 | ``` 27 | user=> players 28 | #{"Alice" "Kelly" "Bob"} 29 | user=> (disj players "Bob" "Sal") 30 | #{"Alice" "Kelly"} 31 | ``` 32 | 33 | As you can see, it’s fine to `disj` elements that don’t exist in the set. 34 | 35 | ### Checking containment 36 | 37 | ``` 38 | user=> (contains? players "Kelly") 39 | true 40 | ``` 41 | 42 | ### Sorted sets 43 | 44 | Sorted sets are sorted according to a comparator function which can compare two elements. By default, Clojure’s `compare` function is used, which sorts in "natural" order for numbers, strings, etc. 45 | 46 | ``` 47 | user=> (conj (sorted-set) "Bravo" "Charlie" "Sigma" "Alpha") 48 | #{"Alpha" "Bravo" "Charlie" "Sigma"} 49 | ``` 50 | 51 | A custom comparator can also be used with `sorted-set-by`. 52 | 53 | ### `into` 54 | 55 | `into` is used for putting one collection into another. 56 | 57 | ``` 58 | user=> (def players #{"Alice" "Bob" "Kelly"}) 59 | user=> (def new-players ["Tim" "Sue" "Greg"]) 60 | user=> (into players new-players) 61 | #{"Alice" "Greg" "Sue" "Bob" "Tim" "Kelly"} 62 | ``` 63 | 64 | `into` returns a collection of the same type as its first argument. 65 | 66 | ## Maps 67 | 68 | Maps are commonly used for two purposes - to manage an association of keys to values and to represent domain application data. The first use case is often referred to as dictionaries or hash maps in other languages. 69 | 70 | ### Creating a literal map 71 | 72 | Maps are represented as alternating keys and values surrounded by `{` and `}`. 73 | 74 | ``` 75 | (def scores {"Fred" 1400 76 | "Bob" 1240 77 | "Angela" 1024}) 78 | ``` 79 | 80 | When Clojure prints a map at the REPL, it will put `,’s between each key/value pair. These are purely used for readability - commas are treated as whitespace in Clojure. Feel free to use them in cases where they help you! 81 | 82 | ``` 83 | ;; same as the last one! 84 | (def scores {"Fred" 1400, "Bob" 1240, "Angela" 1024}) 85 | ``` 86 | 87 | ### Adding new key-value pairs 88 | 89 | New values are added to maps with the `assoc` (short for "associate") function: 90 | 91 | ``` 92 | user=> (assoc scores "Sally" 0) 93 | {"Angela" 1024, "Bob" 1240, "Fred" 1400, "Sally" 0} 94 | ``` 95 | 96 | If the key used in `assoc` already exists, the value is replaced. 97 | 98 | ``` 99 | user=> (assoc scores "Bob" 0) 100 | {"Angela" 1024, "Bob" 0, "Fred" 1400} 101 | ``` 102 | 103 | ### Removing key-value pairs 104 | 105 | The complementary operation for removing key-value pairs is `dissoc` ("dissociate"): 106 | 107 | ``` 108 | user=> (dissoc scores "Bob") 109 | {"Angela" 1024, "Fred" 1400} 110 | ``` 111 | 112 | ### Looking up by key 113 | 114 | There are several ways to look up a value in a map. The most obvious is the function `get`: 115 | 116 | ``` 117 | user=> (get scores "Angela") 118 | 1024 119 | ``` 120 | 121 | When the map in question is being treated as a constant lookup table, it’s common to invoke the map itself, treating it as a function: 122 | 123 | ``` 124 | user=> (def directions {:north 0 125 | :east 1 126 | :south 2 127 | :west 3}) 128 | #'user/directions 129 | 130 | user=> (directions :north) 131 | 0 132 | ``` 133 | 134 | You should not directly invoke a map unless you can guarantee it will be non-nil: 135 | 136 | ``` 137 | user=> (def bad-lookup-map nil) 138 | #'user/bad-lookup-map 139 | 140 | user=> (bad-lookup-map :foo) 141 | Execution error (NullPointerException) at user/eval154 (REPL:1). 142 | null 143 | ``` 144 | 145 | ### Looking up with a default 146 | 147 | If you want to do a lookup and fall back to a default value when the key is not found, specify the default as an extra parameter: 148 | 149 | ``` 150 | user=> (get scores "Sam" 0) 151 | 0 152 | 153 | user=> (directions :northwest -1) 154 | -1 155 | ``` 156 | 157 | Using a default is also helpful to distinguish between a missing key and an existing key with a `nil` value. 158 | 159 | ### Checking contains 160 | 161 | There are two other functions that are helpful in checking whether a map contains an entry. 162 | 163 | ``` 164 | user=> (contains? scores "Fred") 165 | true 166 | 167 | user=> (find scores "Fred") 168 | ["Fred" 1400] 169 | ``` 170 | 171 | The `contains?` function is a predicate for checking containment. The `find` function finds the key/value entry in a map, not just the value. 172 | 173 | ### Keys or values 174 | 175 | You can also get just the keys or just the values in a map: 176 | 177 | ``` 178 | user=> (keys scores) 179 | ("Fred" "Bob" "Angela") 180 | 181 | user=> (vals scores) 182 | (1400 1240 1024) 183 | ``` 184 | 185 | While maps are unordered, there is a guarantee that keys, vals, and other functions that walk in "sequence" order will always walk a particular map instance entries in the same order. 186 | 187 | ### Building a map 188 | 189 | The `zipmap` function can be used to "zip" together two sequences (the keys and vals) into a map: 190 | 191 | ``` 192 | user=> (def players #{"Alice" "Bob" "Kelly"}) 193 | #'user/players 194 | 195 | user=> (zipmap players (repeat 0)) 196 | {"Kelly" 0, "Bob" 0, "Alice" 0} 197 | ``` 198 | 199 | There are a variety of other ways to build up a map using Clojure’s sequence functions (which we have not yet discussed). Come back to these later! 200 | 201 | ``` 202 | ;; with map and into 203 | (into {} (map (fn [player] [player 0]) players)) 204 | 205 | ;; with reduce 206 | (reduce (fn [m player] 207 | (assoc m player 0)) 208 | {} ; initial value 209 | players) 210 | ``` 211 | 212 | ### Combining maps 213 | 214 | The `merge` function can be used to combine multiple maps into a single map: 215 | 216 | ``` 217 | user=> (def new-scores {"Angela" 300 "Jeff" 900}) 218 | #'user/new-scores 219 | 220 | user=> (merge scores new-scores) 221 | {"Fred" 1400, "Bob" 1240, "Jeff" 900, "Angela" 300} 222 | ``` 223 | 224 | We merged two maps here but you can pass more as well. 225 | 226 | If both maps contain the same key, the rightmost one wins. Alternately, you can use `merge-with` to supply a function to invoke when there is a conflict: 227 | 228 | ``` 229 | user=> (def new-scores {"Fred" 550 "Angela" 900 "Sam" 1000}) 230 | #'user/new-scores 231 | 232 | user=> (merge-with + scores new-scores) 233 | {"Sam" 1000, "Fred" 1950, "Bob" 1240, "Angela" 1924} 234 | ``` 235 | 236 | In the case of a conflict, the function is called on both values to get the new value. 237 | 238 | ### Sorted maps 239 | 240 | Similar to sorted sets, sorted maps maintain the keys in sorted order based on a comparator, using `compare` as the default comparator function. 241 | 242 | ``` 243 | user=> (def sm (sorted-map 244 | "Bravo" 204 245 | "Alfa" 35 246 | "Sigma" 99 247 | "Charlie" 100)) 248 | {"Alfa" 35, "Bravo" 204, "Charlie" 100, "Sigma" 99} 249 | 250 | user=> (keys sm) 251 | ("Alfa" "Bravo" "Charlie" "Sigma") 252 | 253 | user=> (vals sm) 254 | (35 204 100 99) 255 | ``` 256 | 257 | ## Representing application domain information 258 | 259 | When we need to represent many domain information with the same set of fields known in advance, you can use a map with keyword keys. 260 | 261 | ``` 262 | (def person 263 | {:first-name "Kelly" 264 | :last-name "Keen" 265 | :age 32 266 | :occupation "Programmer"}) 267 | ``` 268 | 269 | ### Field accessors 270 | 271 | Since this is a map, the ways we’ve already discussed for looking up a value by key also work: 272 | 273 | ``` 274 | user=> (get person :occupation) 275 | "Programmer" 276 | 277 | user=> (person :occupation) 278 | "Programmer" 279 | ``` 280 | 281 | But really, the most common way to get field values for this use is by invoking the keyword. Just like with maps and sets, keywords are also functions. When a keyword is invoked, it looks itself up in the associative data structure that it was passed. 282 | 283 | ``` 284 | user=> (:occupation person) 285 | "Programmer" 286 | ``` 287 | 288 | Keyword invocation also takes an optional default value: 289 | 290 | ``` 291 | user=> (:favorite-color person "beige") 292 | "beige" 293 | ``` 294 | 295 | ### Updating fields 296 | 297 | Since this is a map, we can just use `assoc` to add or modify fields: 298 | 299 | ``` 300 | user=> (assoc person :occupation "Baker") 301 | {:age 32, :last-name "Keen", :first-name "Kelly", :occupation "Baker"} 302 | ``` 303 | 304 | ### Removing a field 305 | 306 | Use dissoc to remove fields: 307 | 308 | ``` 309 | user=> (dissoc person :age) 310 | {:last-name "Keen", :first-name "Kelly", :occupation "Programmer"} 311 | ``` 312 | 313 | ### Nested entities 314 | 315 | It is common to see entities nested within other entities: 316 | 317 | ``` 318 | (def company 319 | {:name "WidgetCo" 320 | :address {:street "123 Main St" 321 | :city "Springfield" 322 | :state "IL"}}) 323 | ``` 324 | 325 | You can use `get-in` to access fields at any level inside nested entities: 326 | 327 | ``` 328 | user=> (get-in company [:address :city]) 329 | "Springfield" 330 | ``` 331 | 332 | You can also use `assoc-in` or `update-in` to modify nested entities: 333 | 334 | ``` 335 | user=> (assoc-in company [:address :street] "303 Broadway") 336 | {:name "WidgetCo", 337 | :address 338 | {:state "IL", 339 | :city "Springfield", 340 | :street "303 Broadway"}} 341 | ``` 342 | 343 | ### Records 344 | 345 | An alternative to using maps is to create a "record". Records are designed specifically for this use case and generally have better performance. In addition, they have a named "type" which can be used for polymorphic behavior (more on that later). 346 | 347 | Records are defined with the list of field names for record instances. These will be treated as keyword keys in each record instance. 348 | 349 | ```clj 350 | ;; Define a record structure 351 | (defrecord Person [first-name last-name age occupation]) 352 | 353 | ;; Positional constructor - generated 354 | (def kelly (->Person "Kelly" "Keen" 32 "Programmer")) 355 | 356 | ;; Map constructor - generated 357 | (def kelly (map->Person 358 | {:first-name "Kelly" 359 | :last-name "Keen" 360 | :age 32 361 | :occupation "Programmer"})) 362 | ``` 363 | 364 | Records are used almost exactly the same as maps, with the caveat that they cannot be invoked as a function like maps. 365 | 366 | ``` 367 | user=> (:occupation kelly) 368 | "Programmer" 369 | ``` 370 | -------------------------------------------------------------------------------- /99~参考资料/2012~《Clojure - Functional Programming for the JVM》~中文版/08~集合.md: -------------------------------------------------------------------------------- 1 | # 集合 2 | 3 | Clojure 提供这些集合类型: list, vector, set, map。同时 Clojure 还可以使用 Java 里面提供的将所有的集合类型,但是通常不会这样做的,因为 Clojure 自带的集合类型更适合函数式编程。 4 | 5 | Clojure 集合有着 java 集合所不具备的一些特性。所有的 clojure 集合是不可修改的、异源的以及持久的。不可修改的意味着一旦一个集合产生之后,你不能从集合里面删除一个元素,也往集合里面添加一个元素。异源的意味着一个集合里面可以装进任何东西(而不必须要这些东西的类型一样)。持久的以为着当一个集合新的版本产生之后,旧的版本还是在的。CLojure 以一种非常高效的,共享内存的方式来实现这个的。比如有一个 map 里面有一千个 name-valuea pair, 现在要往 map 里面加一个,那么对于那些没有变化的元素,新的 map 会共享旧的 map 的内存,而只需要添加一个新的元素所占用的内存。 6 | 7 | 有很多核心的函数可以用来操作所有这些类型的集合。。多得以至于无法在这里全部描述。其中的一小部分我们会在下面介绍 vector 的时候介绍一下。要记住的是,因为 clojure 里面的集合是不可修改的,所以也就没有对集合进行修改的函数。相反 clojure 里面提供了一些函数来从一个已有的集合来高效地创建新的集合 — 使用 [persistent data structures](http://en.wikipedia.org/wiki/Persistent_data_structure) 。同时也有一些函数操作一个已有的集合(比如 vector)来产生另外一种类型的集合(比如 LazySeq), 这些函数有不同的特性。 8 | 9 | 提醒: 这一节里面介绍的 Clojure 集合对于学习 clojure 来说是非常的重要。但是这里介绍一个函数接着一个函数,所以你如果觉得有点烦,有点乏味,你可以跳过,等用到的时候再回过头来查询。 10 | 11 | `count` 返回集合里面的元素个数,比如: 12 | 13 | ``` 14 | (count [19 "yellow" true]) ; -> 3 15 | ``` 16 | 17 | `conj` 函数是 conjoin 的缩写, 添加一个元素到集合里面去,到底添加到什么位置那就取决于具体的集合了,我们会在下面介绍具体集合的时候再讲。 18 | 19 | `reverse` 把集合里面的元素反转。 20 | 21 | ``` 22 | (reverse [2 4 7]) ; -> (7 4 2) 23 | ``` 24 | 25 | `map` 对一个给定的集合里面的每一个元素调用一个指定的方法,然后这些方法的所有返回值构成一个新的集合(LazySeq)返回。这个指定了函数也可以有多个参数,那么你就需要给 map 多个集合了。如果这些给的集合的个数不一样,那么执行这个函数的次数取决于个数最少的集合的长度。比如: 26 | 27 | ``` 28 | ; The next line uses an anonymous function that adds 3 to its argument. 29 | (map #(+ % 3) [2 4 7]) ; -> (5 7 10) 30 | (map + [2 4 7] [5 6] [1 2 3 4]) ; adds corresponding items -> (8 12) 31 | ``` 32 | 33 | `apply` 把给定的集合里面的所有元素一次性地给指定的函数作为参数调用,然后返回这个函数的返回值。所以 apply 与 map 的区别就是 map 返回的还是一个集合,而 apply 返回的是一个元素,可以把 apply 看作是 SQL 里面的聚合函数。比如: 34 | 35 | ```clj 36 | (apply + [2 4 7]); -> 13 37 | ``` 38 | 39 | 有很多函数从一个集合里面获取一个元素,比如: 40 | 41 | ```clj 42 | (def stooges ["Moe" "Larry" "Curly" "Shemp"]) 43 | (first stooges) ; -> "Moe" 44 | (second stooges) ; -> "Larry" 45 | (last stooges) ; -> "Shemp" 46 | (nth stooges 2) ; indexes start at 0 -> "Curly" 47 | ``` 48 | 49 | 也有一些函数从一个集合里面获取多个元素,比如: 50 | 51 | ```clj 52 | (next stooges) ; -> ("Larry" "Curly" "Shemp") 53 | (butlast stooges) ; -> ("Moe" "Larry" "Curly") 54 | (drop-last 2 stooges) ; -> ("Moe" "Larry") 55 | ; Get names containing more than three characters. 56 | (filter #(> (count %) 3) stooges) ; -> ("Larry" "Curly" "Shemp") 57 | (nthnext stooges 2) ; -> ("Curly" "Shemp") 58 | ``` 59 | 60 | 有一些谓词函数测试集合里面每一个元素然后返回一个布尔值,这些函数都是”short-circuit”的,一旦它们的返回值能确定它们就不再继续测试剩下的元素了,有点像 java 的&&和 or, 比如: 61 | 62 | ```clj 63 | (every? #(instance? String %) stooges) ; -> true 64 | (not-every? #(instance? String %) stooges) ; -> false 65 | (some #(instance? Number %) stooges) ; -> nil 66 | (not-any? #(instance? Number %) stooges) ; -> true 67 | ``` 68 | 69 | # 列表 70 | 71 | Lists 是一个有序的元素的集合 — 相当于 java 里面的 LinkedList。这种集合对于那种一直要往最前面加一个元素,干掉最前面一个元素是非常高效的(O(1)) — 想到于 java 里面的堆栈, 但是没有高效的方法来获取第 N 个元素,也没有高效的办法来修改第 N 个元素。 72 | 73 | 下面是创建同样的 list 的多种不同的方法: 74 | 75 | ``` 76 | (def stooges (list "Moe" "Larry" "Curly")) 77 | (def stooges (quote ("Moe" "Larry" "Curly"))) 78 | (def stooges '("Moe" "Larry" "Curly")) 79 | ``` 80 | 81 | `some` 可以用来检测一个集合是否含有某个元素. 它的参数包括一个谓词函数以及一个集合。你可以能会想了,为了要看一个 list 到底有没有某个元素为什么要指定一个谓词函数呢?其实我们是故意这么做来让你尽量不要这么用的。从一个 list 里面搜索一个元素是线性的操作(不高效),而要从一个 set 里面搜索一个元素就容易也高效多了,看下面的例子对比: 82 | 83 | ``` 84 | (some #(= % "Moe") stooges) ; -> true 85 | (some #(= % "Mark") stooges) ; -> nil 86 | ; Another approach is to create a set from the list 87 | ; and then use the contains? function on the set as follows. 88 | (contains? (set stooges) "Moe") ; -> true 89 | ``` 90 | 91 | `conj` 和 `cons` 函数的作用都是通过一个已有的集合来创建一个新的包含更多元素的集合 — 新加的元素在最前面。`remove` 函数创建一个只包含所指定的谓词函数测试结果为 false 的元素的集合: 92 | 93 | ``` 94 | (def more-stooges (conj stooges "Shemp")) -> ("Shemp" "Moe" "Larry" "Curly") 95 | (def less-stooges (remove #(= % "Curly") more-stooges)) ; -> ("Shemp" "Moe" "Larry") 96 | ``` 97 | 98 | `into` 函数把两个 list 里面的元素合并成一个新的大 list 99 | 100 | ``` 101 | (def kids-of-mike '("Greg" "Peter" "Bobby")) 102 | (def kids-of-carol '("Marcia" "Jan" "Cindy")) 103 | (def brady-bunch (into kids-of-mike kids-of-carol)) 104 | (println brady-bunch) ; -> (Cindy Jan Marcia Greg Peter Bobby) 105 | ``` 106 | 107 | `peek` 和 `pop` 可以用来把 list 当作一个堆栈来操作. 她们操作的都是 list 的第一个元素。 108 | 109 | # 向量 110 | 111 | Vectors 也是一种有序的集合。这种集合对于从最后面删除一个元素,或者获取最后面一个元素是非常高效的(O(1))。这意味着对于向 vector 里面添加元素使用 conj 被使用 cons 更高效。Vector 对于以索引的方式访问某个元素(用 nth 命令)或者修改某个元素(用 assoc)来说非常的高效。函数定义的时候指定参数列表用的就是 vector。 112 | 113 | 下面是两种创建 vector 的方法: 114 | 115 | ``` 116 | (def stooges (vector "Moe" "Larry" "Curly")) 117 | (def stooges ["Moe" "Larry" "Curly"]) 118 | ``` 119 | 120 | 除非你要写的程序要特别用到 list 的从前面添加/删除效率很高的这个特性,否则一般来说我们鼓励你们用 vector 而不是 lists。这主要是因为语法上 `[...]` 比 ‘ `(...)` 更自然,更不容易弄混淆。因为函数,宏以及 special form 的语法也是 `(...)。` 121 | 122 | `get` 获取 vector 里面指定索引的元素. 我们后面会看到 get 也可以从 map 里面获取指定 key 的 value。索引是从 0 开始的。`get` 函数和函数 `nth` 类似. 它们都接收一个可选的默认值参数 — 如果给定的索引超出边界,那么会返回这个默认值。如果没有指定默认值而索引又超出边界了,`get` 函数会返回 `nil` 而 `nth` 会抛出一个异常. 看例子: 123 | 124 | ``` 125 | (get stooges 1 "unknown") ; -> "Larry" 126 | (get stooges 3 "unknown") ; -> "unknown" 127 | ``` 128 | 129 | `assoc` 可以对 vectors 和 maps 进行操作。当用在 vector 上的时候, 它会从给定的 vector 创建一个新的 vector, 而指定的那个索引所对应的元素被替换掉。如果指定的这个索引等于 vector 里面元素的数目,那么我们会把这个元素加到新 vector 的最后面去;如果指定的索引比 vector 的大小要大,那么一个 `IndexOutOfBoundsException` 异常会被抛出来。看代码: 130 | 131 | ``` 132 | (assoc stooges 2 "Shemp") ; -> ["Moe" "Larry" "Shemp"] 133 | ``` 134 | 135 | `subvec` 获取一个给定 vector 的子 vector。它接受三个参数,一个 vectore, 一个起始索引以及一个可选的结束索引。如果结束索引没有指定,那么默认的结束索引就是 vector 的大小。新的 vector 和原来的 vector 共享内存(所以高效)。 136 | 137 | 所有上面的对于 list 的例子代码对于 vector 同样适用。`peek` 和 `pop` 函数对于 vector 同样适用, 只是它们操作的是 vector 的最后一个元素,而对于 list 操作的则是第一个函数。`conj` 函数从一个给定的 vector 创建一个新的 vector — 添加一个元素到新的 vector 的最后面去. `cons` 函数从一个给定的 vector 创建一个新的 vector — 添加一个新的元素到 vector 的最前面去。 138 | 139 | # 集合 140 | 141 | Sets 是一个包含不重复元素的集合。当我们要求集合里面的元素不可以重复,并且我们不要求集合里面的元素保持它们添加时候的顺序,那么 sets 是比较适合的。Clojure 支持两种不同的 set: 排序的和不排序的。如果添加到 set 里面的元素相互之间不能比较大小,那么一个 `ClassCastException` 异常会被抛出来。下面是一些创建 set 的方法: 142 | 143 | ```clj 144 | (def stooges (hash-set "Moe" "Larry" "Curly")) ; not sorted 145 | (def stooges #{"Moe" "Larry" "Curly"}) ; same as previous 146 | (def stooges (sorted-set "Moe" "Larry" "Curly")) 147 | ``` 148 | 149 | `contains?` 函数可以操作 sets 和 maps. 当操作 set 的时候, 它返回给定的 set 是否包含某个元素。这比在 list 和 vector 上面使用的 `some函数就简单多了` . 看例子: 150 | 151 | ``` 152 | (contains? stooges "Moe") ; -> true 153 | (contains? stooges "Mark") ; -> false 154 | ``` 155 | 156 | Sets 可以被当作它里面的元素的函数来使用. 当以这种方式来用的时候,返回值要么是这个元素,要么是 nil. 这个比起 contains?函数来说更简洁. 比如: 157 | 158 | ``` 159 | (stooges "Moe") ; -> "Moe" 160 | (stooges "Mark") ; -> nil 161 | (println (if (stooges person) "stooge" "regular person")) 162 | ``` 163 | 164 | 在介绍 list 的时候提到的函数 `conj` 和 `into` 对于 set 也同样适用. 只是元素的顺序只有对 sorted-set 才有定义. 165 | 166 | `disj` 函数通过去掉给定的 set 里面的一些元素来创建一个新的 set. 看例子: 167 | 168 | ``` 169 | (def more-stooges (conj stooges "Shemp")) ; -> #{"Moe" "Larry" "Curly" "Shemp"} 170 | (def less-stooges (disj more-stooges "Curly")) ; -> #{"Moe" "Larry" "Shemp"} 171 | ``` 172 | 173 | 你也可以看看 `clojure.set` 名字空间里面的一些函数: `difference` , `index` , `intersection` , `join` , `map-invert` , `project` , `rename` , `rename-keys` , `select` 和 `union` . 其中有些函数的操作的对象是 map 而不是 set。 174 | 175 | # 映射 176 | 177 | Maps 保存从 key 到 value 的 a 对应关系 — key 和 value 都可以是任意对象。key-value 组合被以一种可以按照 key 的顺序高效获取的方式保存着。 178 | 179 | 下面是创建 map 的一些方法,其中逗号是为了提高可读性的,它是可选的,解析的时候会被当作空格忽略掉的。 180 | 181 | ``` 182 | (def popsicle-map 183 | (hash-map :red :cherry, :green :apple, :purple :grape)) 184 | (def popsicle-map 185 | {:red :cherry, :green :apple, :purple :grape}) ; same as previous 186 | (def popsicle-map 187 | (sorted-map :red :cherry, :green :apple, :purple :grape)) 188 | ``` 189 | 190 | Map 可以作为它的 key 的函数,同时如果 key 是 keyword 的话,那么 key 也可以作为 map 的函数。下面是三种获取:green 所对应的值的方法: 191 | 192 | ``` 193 | (get popsicle-map :green) 194 | (popsicle-map :green) 195 | (:green popsicle-map) 196 | ``` 197 | 198 | `contains?` 方法可以操作 sets 和 maps. 当被用在 map 上的时候,它返回 map 是否包含给定的 key. `keys` 函数返回 map 里面的所有的 key 的集合. `vals` 函数返回 map 里面所有值的集合. 看例子: 199 | 200 | ``` 201 | (contains? popsicle-map :green) ; -> true 202 | (keys popsicle-map) ; -> (:red :green :purple) 203 | (vals popsicle-map) ; -> (:cherry :apple :grape) 204 | ``` 205 | 206 | `assoc` 函数可以操作 maps 和 vectors. 当被用在 map 上的时候,它会创建一个新的 map,同时添加任意对新的 name-value pair, 如果某个给定的 key 已经存在了,那么它的值会被更新。看例子: 207 | 208 | ``` 209 | (assoc popsicle-map :green :lime :blue :blueberry) 210 | ; -> {:blue :blueberry, :green :lime, :purple :grape, :red :cherry} 211 | ``` 212 | 213 | `dissoc` 创建一个新的 map,同时忽略掉给定的那么些 key,看例子: 214 | 215 | ``` 216 | (dissoc popsicle-map :green :blue) ; -> {:purple :grape, :red :cherry} 217 | ``` 218 | 219 | 我们也可以把 map 看成一个简单的集合,集合里面的每个元素是一个 pair: name-value: `clojure.lang.MapEntry` 对象. 这样就可以和 doseq 跟 destructuring 一起使用了, 它们的作用都是更简单地来遍历 map,我们会在后面详细地介绍这些函数. 下面的这个例子会遍历 `popsicle-map` 里面的所有元素,把 key bind 到 `color,` 把 value bind 到 `flavor。` `name函数返回一个keyword的字符串名字。` 220 | 221 | ``` 222 | (doseq [[color flavor] popsicle-map] 223 | (println (str "The flavor of " (name color) 224 | " popsicles is " (name flavor) "."))) 225 | ``` 226 | 227 | 上面的代码的输出是这样的: 228 | 229 | ``` 230 | The flavor of green popsicles is apple. 231 | The flavor of purple popsicles is grape. 232 | The flavor of red popsicles is cherry. 233 | ``` 234 | 235 | `select-keys` 函数接收一个 map 对象,以及一个 key 的集合的参数,它返回这个集合里面 key 在那个集合里面的一个子 map。看例子: 236 | 237 | ``` 238 | (select-keys popsicle-map [:red :green :blue]) ; -> {:green :apple, :red :cherry} 239 | ``` 240 | 241 | `conj` 函数添加一个 map 里面的所有元素到另外一个 map 里面去。如果目标 map 里面的 key 在源 map 里面也有,那么目标 map 的值会被更新成源 map 里面的值。 242 | 243 | map 里面的值也可以是一个 map,而且这样嵌套无限层。获取嵌套的值是非常简单的。同样的,更新一个嵌套的值也是很简单的。 244 | 245 | 为了证明这个,我们会创建一个描述人(person)的 map。其中内嵌了一个表示人的地址的 map,同时还有一个叫做 employer 的内嵌 map。 246 | 247 | ``` 248 | (def person { 249 | :name "Mark Volkmann" 250 | :address { 251 | :street "644 Glen Summit" 252 | :city "St. Charles" 253 | :state "Missouri" 254 | :zip 63304} 255 | :employer { 256 | :name "Object Computing, Inc." 257 | :address { 258 | :street "12140 Woodcrest Executive Drive, Suite 250" 259 | :city "Creve Coeur" 260 | :state "Missouri" 261 | :zip 63141}}}) 262 | ``` 263 | 264 | `get-in` 函数、宏 `->` 以及函数 `reduce` 都可以用来获得内嵌的 key. 下面展示了三种获取这个人的 employer 的 address 的 city 的值的方法: 265 | 266 | ``` 267 | (get-in person [:employer :address :city]) 268 | (-> person :employer :address :city) ; explained below 269 | (reduce get person [:employer :address :city]) ; explained below 270 | ``` 271 | 272 | 宏 `->` 我们也称为 “thread” 宏, 它本质上是调用一系列的函数,前一个函数的返回值作为后一个函数的参数. 比如下面两行代码的作用是一样的: 273 | 274 | ``` 275 | (f1 (f2 (f3 x))) 276 | (-> x f3 f2 f1) 277 | ``` 278 | 279 | 在名字空间 `clojure.contrib.core` 里面还有个 -?>宏,它会马上返回 nil,如果它的调用链上的任何一个函数返回 nil (short-circiut)。这会避免抛出 `NullPointerException` 异常。 280 | 281 | `reduce` 函数接收一个需要两个参数的函数, 一个可选的 value 以及一个集合。它会以 value 以及集合的第一个元素作为参数来调用给定的函数(如果指定了 value 的话),要么以集合的第一个元素以及第二个元素为参数来调用给定的函数(如果没有指定 value 的话)。接着就以这个返回值以及集合里面的下一个元素为参数来调用给定的函数,知道集合里面的元素都被计算了 — 最后返回一个值. 这个函数与 ruby 里面的 `inject` 以及 Haskell 里面的 `foldl` 作用是一样的。 282 | 283 | `assoc-in` 函数可以用来修改一个内嵌的 key 的值。看下面的例子把 person 的 employer->address->city 修改成 Clayton 了。 284 | 285 | ``` 286 | (assoc-in person [:employer :address :city] "Clayton") 287 | ``` 288 | 289 | `update-in` 函数也是用来更新给定的内嵌的 key 对应的值,只是这个新值是通过一个给定的函数来计算出来。下面的例子里面会把 person 的 employer->address->zip 改成旧的 zip + “-1234″。看例子: 290 | 291 | ``` 292 | (update-in person [:employer :address :zip] str "-1234") ; using the str function 293 | ``` 294 | -------------------------------------------------------------------------------- /99~参考资料/《Clojure 官方教程》/02~Functions.md: -------------------------------------------------------------------------------- 1 | ## Creating Functions 2 | 3 | Clojure is a functional language. Functions are first-class and can be passed-to or returned-from other functions. Most Clojure code consists primarily of pure functions (no side effects), so invoking with the same inputs yields the same output. 4 | 5 | `defn` defines a named function: 6 | 7 | ``` 8 | ;; name params body 9 | ;; ----- ------ ------------------- 10 | (defn greet [name] (str "Hello, " name) ) 11 | ``` 12 | 13 | This function has a single parameter `name`, however you may include any number of parameters in the params vector. 14 | 15 | Invoke a function with the name of the function in "function position" (the first element of a list): 16 | 17 | ```clj 18 | user=> (greet "students") 19 | "Hello, students" 20 | ``` 21 | 22 | ### Multi-arity functions 23 | 24 | Functions can be defined to take different numbers of parameters (different "arity"). Different arities must all be defined in the same `defn` - using `defn` more than once will replace the previous function. 25 | 26 | Each arity is a list `([param*] body*)`. One arity can invoke another. The body can contain any number of expressions and the return value is the result of the last expression. 27 | 28 | ``` 29 | (defn messenger 30 | ([] (messenger "Hello world!")) 31 | ([msg] (println msg))) 32 | ``` 33 | 34 | This function declares two arities (0 parameters and 1 parameter). The 0-parameter arity calls the 1-parameter arity with a default value to print. We invoke these functions by passing the appropriate number of arguments: 35 | 36 | ``` 37 | user=> (messenger) 38 | Hello world! 39 | nil 40 | 41 | user=> (messenger "Hello class!") 42 | Hello class! 43 | nil 44 | ``` 45 | 46 | ### Variadic functions 47 | 48 | Functions may also define a variable number of parameters - this is known as a "variadic" function. The variable parameters must occur at the end of the parameter list. They will be collected in a sequence for use by the function. 49 | 50 | The beginning of the variable parameters is marked with `&`. 51 | 52 | ``` 53 | (defn hello [greeting & who] 54 | (println greeting who)) 55 | ``` 56 | 57 | This function takes a parameter `greeting` and a variable number of parameters (0 or more) that will be collected in a list named `who`. We can see this by invoking it with 3 arguments: 58 | 59 | ``` 60 | user=> (hello "Hello" "world" "class") 61 | Hello (world class) 62 | ``` 63 | 64 | You can see that when `println` prints `who`, it is printed as a list of two elements that were collected. 65 | 66 | ### Anonymous Functions 67 | 68 | An anonymous function can be created with `fn`: 69 | 70 | ``` 71 | ;; params body 72 | ;; --------- ----------------- 73 | (fn [message] (println message) ) 74 | ``` 75 | 76 | Because the anonymous function has no name, it cannot be referred to later. Rather, the anonymous function is typically created at the point it is passed to another function. 77 | 78 | Or it’s possible to immediately invoke it (this is not a common usage): 79 | 80 | ``` 81 | ;; operation (function) argument 82 | ;; -------------------------------- -------------- 83 | ( (fn [message] (println message)) "Hello world!" ) 84 | 85 | ;; Hello world! 86 | ``` 87 | 88 | Here we defined the anonymous function in the function position of a larger expression that immediately invokes the expression with the argument. 89 | 90 | Many languages have both statements, which imperatively do something and do not return a value, and expressions which do. Clojure has **only** expressions that return a value. We’ll see later that this includes even flow control expressions like `if`. 91 | 92 | ### `defn` vs `fn` 93 | 94 | It might be useful to think of `defn` as a contraction of `def` and `fn`. The `fn` defines the function and the `def` binds it to a name. These are equivalent: 95 | 96 | ``` 97 | (defn greet [name] (str "Hello, " name)) 98 | 99 | (def greet (fn [name] (str "Hello, " name))) 100 | ``` 101 | 102 | ### Anonymous function syntax 103 | 104 | There is a shorter form for the `fn` anonymous function syntax implemented in the Clojure reader: `#()`. This syntax omits the parameter list and names parameters based on their position. 105 | 106 | - `%` is used for a single parameter 107 | - `%1`, `%2`, `%3`, etc are used for multiple parameters 108 | - `%&` is used for any remaining (variadic) parameters 109 | 110 | Nested anonymous functions would create an ambiguity as the parameters are not named, so nesting is not allowed. 111 | 112 | ```clj 113 | ;; Equivalent to: (fn [x] (+ 6 x)) 114 | #(+ 6 %) 115 | 116 | ;; Equivalent to: (fn [x y] (+ x y)) 117 | #(+ %1 %2) 118 | 119 | ;; Equivalent to: (fn [x y & zs] (println x y zs)) 120 | #(println %1 %2 %&) 121 | ``` 122 | 123 | ### Gotcha 124 | 125 | One common need is an anonymous function that takes an element and wraps it in a vector. You might try writing that as: 126 | 127 | ``` 128 | ;; DO NOT DO THIS 129 | #([%]) 130 | ``` 131 | 132 | This anonymous function expands to the equivalent: 133 | 134 | ``` 135 | (fn [x] ([x])) 136 | ``` 137 | 138 | This form will wrap in a vector **and** try to invoke the vector with no arguments (the extra pair of parentheses). Instead: 139 | 140 | ``` 141 | ;; Instead do this: 142 | #(vector %) 143 | 144 | ;; or this: 145 | (fn [x] [x]) 146 | 147 | ;; or most simply just the vector function itself: 148 | vector 149 | ``` 150 | 151 | ## Applying Functions 152 | 153 | ### `apply` 154 | 155 | The `apply` function invokes a function with 0 or more fixed arguments, and draws the rest of the needed arguments from a final sequence. The final argument **must** be a sequence. 156 | 157 | ```clj 158 | (apply f '(1 2 3 4)) ;; same as (f 1 2 3 4) 159 | (apply f 1 '(2 3 4)) ;; same as (f 1 2 3 4) 160 | (apply f 1 2 '(3 4)) ;; same as (f 1 2 3 4) 161 | (apply f 1 2 3 '(4)) ;; same as (f 1 2 3 4) 162 | ``` 163 | 164 | All 4 of these calls are equivalent to `(f 1 2 3 4)`. `apply` is useful when arguments are handed to you as a sequence but you must invoke the function with the values in the sequence. 165 | 166 | For example, you can use `apply` to avoid writing this: 167 | 168 | ``` 169 | (defn plot [shape coords] ;; coords is [x y] 170 | (plotxy shape (first coords) (second coords))) 171 | ``` 172 | 173 | Instead you can simply write: 174 | 175 | ``` 176 | (defn plot [shape coords] 177 | (apply plotxy shape coords)) 178 | ``` 179 | 180 | ## Locals and Closures 181 | 182 | ### `let` 183 | 184 | `let` binds symbols to values in a "lexical scope". A lexical scope creates a new context for names, nested inside the surrounding context. Names defined in a `let` take precedence over the names in the outer context. 185 | 186 | ``` 187 | ;; bindings name is defined here 188 | ;; ------------ ---------------------- 189 | (let [name value] (code that uses name)) 190 | ``` 191 | 192 | Each `let` can define 0 or more bindings and can have 0 or more expressions in the body. 193 | 194 | ``` 195 | (let [x 1 196 | y 2] 197 | (+ x y)) 198 | ``` 199 | 200 | This `let` expression creates two local bindings for `x` and `y`. The expression `(+ x y)` is in the lexical scope of the `let` and resolves x to 1 and y to 2. Outside the `let` expression, x and y will have no continued meaning, unless they were already bound to a value. 201 | 202 | ``` 203 | (defn messenger [msg] 204 | (let [a 7 205 | b 5 206 | c (clojure.string/capitalize msg)] 207 | (println a b c) 208 | ) ;; end of let scope 209 | ) ;; end of function 210 | ``` 211 | 212 | The messenger function takes a `msg` argument. Here the `defn` is also creating lexical scope for `msg` - it only has meaning within the `messenger` function. 213 | 214 | Within that function scope, the `let` creates a new scope to define `a`, `b`, and `c`. If we tried to use `a` after the let expression, the compiler would report an error. 215 | 216 | ### Closures 217 | 218 | The `fn` special form creates a "closure". It "closes over" the surrounding lexical scope (like `msg`, `a`, `b`, or `c` above) and captures their values beyond the lexical scope. 219 | 220 | ``` 221 | (defn messenger-builder [greeting] 222 | (fn [who] (println greeting who))) ; closes over greeting 223 | 224 | ;; greeting provided here, then goes out of scope 225 | (def hello-er (messenger-builder "Hello")) 226 | 227 | ;; greeting value still available because hello-er is a closure 228 | (hello-er "world!") 229 | ;; Hello world! 230 | ``` 231 | 232 | ## Java Interop 233 | 234 | ### Invoking Java code 235 | 236 | Below is a summary of calling conventions for calling into Java from Clojure: 237 | 238 | | Task | Java | Clojure | | 239 | | :-------------- | :------------------ | :----------------- | :-- | 240 | | Instantiation | `new Widget("foo")` | `(Widget. "foo")` | | 241 | | Instance method | `rnd.nextInt()` | `(.nextInt rnd)` | | 242 | | Instance field | `object.field` | `(.-field object)` | | 243 | | Static method | `Math.sqrt(25)` | `(Math/sqrt 25)` | | 244 | | Static field | `Math.PI` | `Math/PI` | | 245 | 246 | ### Java Methods vs Functions 247 | 248 | - Java methods are not Clojure functions 249 | - Can’t store them or pass them as arguments 250 | - Can wrap them in functions when necessary 251 | 252 | ``` 253 | ;; make a function to invoke .length on arg 254 | (fn [obj] (.length obj)) 255 | 256 | ;; same thing 257 | #(.length %) 258 | ``` 259 | 260 | ## Test your knowledge 261 | 262 | _[Check your answers](https://clojure.org/guides/learn/answers#_functions)_ 263 | 264 | 1. Define a function `greet` that takes no arguments and prints "Hello". Replace the `___` with the implementation: `(defn greet [] *_*)` 265 | 2. Redefine `greet` using `def`, first with the `fn` special form and then with the `#()` reader macro. 266 | 267 | ``` 268 | ;; using fn 269 | (def greet __) 270 | 271 | ;; using #() 272 | (def greet __) 273 | ``` 274 | 275 | 3. Define a function `greeting` which: 276 | 277 | - Given no arguments, returns "Hello, World!" 278 | - Given one argument x, returns "Hello, **x**!" 279 | - Given two arguments x and y, returns "**x**, **y**!" 280 | 281 | ``` 282 | ;; Hint use the str function to concatenate strings 283 | (doc str) 284 | 285 | (defn greeting ___) 286 | 287 | ;; For testing 288 | (assert (= "Hello, World!" (greeting))) 289 | (assert (= "Hello, Clojure!" (greeting "Clojure"))) 290 | (assert (= "Good morning, Clojure!" (greeting "Good morning" "Clojure"))) 291 | ``` 292 | 293 | 4. Define a function `do-nothing` which takes a single argument `x` and returns it, unchanged. 294 | 295 | ``` 296 | (defn do-nothing [x] ___) 297 | ``` 298 | 299 | In Clojure, this is the `identity` function. By itself, identity is not very useful, but it is sometimes necessary when working with higher-order functions. 300 | 301 | ``` 302 | (source identity) 303 | ``` 304 | 305 | 5. Define a function `always-thing` which takes any number of arguments, ignores all of them, and returns the number `100`. 306 | 307 | ``` 308 | (defn always-thing [__] ___) 309 | ``` 310 | 311 | 6. Define a function `make-thingy` which takes a single argument `x`. It should return another function, which takes any number of arguments and always returns x. 312 | 313 | ``` 314 | (defn make-thingy [x] ___) 315 | 316 | ;; Tests 317 | (let [n (rand-int Integer/MAX_VALUE) 318 | f (make-thingy n)] 319 | (assert (= n (f))) 320 | (assert (= n (f 123))) 321 | (assert (= n (apply f 123 (range))))) 322 | ``` 323 | 324 | In Clojure, this is the `constantly` function. 325 | 326 | ``` 327 | (source constantly) 328 | ``` 329 | 330 | 7. Define a function `triplicate` which takes another function and calls it three times, without any arguments. 331 | 332 | ```clj 333 | (defn triplicate [f] ___) 334 | ``` 335 | 336 | 8. Define a function `opposite` which takes a single argument `f`. It should return another function which takes any number of arguments, applies `f` on them, and then calls `not` on the result. The `not` function in Clojure does logical negation. 337 | 338 | ``` 339 | (defn opposite [f] 340 | (fn [& args] ___)) 341 | ``` 342 | 343 | In Clojure, this is the complement function. 344 | 345 | ```clj 346 | (defn complement 347 | "Takes a fn f and returns a fn that takes the same arguments as f, 348 | has the same effects, if any, and returns the opposite truth value." 349 | [f] 350 | (fn 351 | ([] (not (f))) 352 | ([x] (not (f x))) 353 | ([x y] (not (f x y))) 354 | ([x y & zs] (not (apply f x y zs))))) 355 | ``` 356 | 357 | 9. Define a function `triplicate2` which takes another function and any number of arguments, then calls that function three times on those arguments. Re-use the function you defined in the earlier triplicate exercise. 358 | 359 | ``` 360 | (defn triplicate2 [f & args] 361 | (triplicate ___)) 362 | ``` 363 | 364 | 10. Using the [java.lang.Math](http://docs.oracle.com/javase/8/docs/api/java/lang/Math.html) class (`Math/pow`, `Math/cos`, `Math/sin`, `Math/PI`), demonstrate the following mathematical facts: 365 | 366 | - The cosine of pi is -1 367 | - For some x, sin(x)^2 + cos(x)^2 = 1 368 | - Define a function that takes an HTTP URL as a string, fetches that URL from the web, and returns the content as a string. 369 | 370 | Hint: Using the [java.net.URL](http://docs.oracle.com/javase/8/docs/api/java/net/URL.html) class and its `openStream` method. Then use the Clojure `slurp` function to get the content as a string. 371 | 372 | ``` 373 | (defn http-get [url] 374 | ___) 375 | 376 | (assert (.contains (http-get "https://www.w3.org") "html")) 377 | ``` 378 | 379 | In fact, the Clojure `slurp` function interprets its argument as a URL first before trying it as a file name. Write a simplified http-get: 380 | 381 | ``` 382 | (defn http-get [url] 383 | ___) 384 | ``` 385 | 386 | 12. Define a function `one-less-arg` that takes two arguments: 387 | 388 | - `f`, a function 389 | - `x`, a value 390 | 391 | and returns another function which calls `f` on `x` plus any additional arguments. 392 | 393 | ```clj 394 | (defn one-less-arg [f x] 395 | (fn [& args] ___)) 396 | ``` 397 | 398 | In Clojure, the `partial` function is a more general version of this. 399 | 400 | 13. Define a function `two-fns` which takes two functions as arguments, `f` and `g`. It returns another function which takes one argument, calls `g` on it, then calls `f` on the result, and returns that. 401 | 402 | That is, your function returns the composition of `f` and `g`. 403 | 404 | ``` 405 | (defn two-fns [f g] 406 | ___) 407 | ``` 408 | -------------------------------------------------------------------------------- /99~参考资料/《Clojure 官方教程》/01~Syntax.md: -------------------------------------------------------------------------------- 1 | ## Literals 2 | 3 | Below are some examples of literal representations of common primitives in Clojure. All of these literals are valid Clojure expressions. 4 | 5 | The `;` creates a comment to the end of the line. Sometimes multiple semicolons are used to indicate header comment sections, but this is just a convention. 6 | 7 | ### Numeric types 8 | 9 | ``` 10 | 42 ; integer 11 | -1.5 ; floating point 12 | 22/7 ; ratio 13 | ``` 14 | 15 | Integers are read as fixed precision 64-bit integers when they are in range and arbitrary precision otherwise. A trailing `N` can be used to force arbitrary precision. Clojure also supports the Java syntax for octal (prefix `0`), hexadecimal (prefix `0x`) and arbitrary radix (prefix base, then `r`, e.g. `2r` for binary) integers. Ratios are provided as their own type combining a numerator and denominator. 16 | 17 | Floating point values are read as double-precision 64-bit floats, or arbitrary precision with an `M` suffix. Exponential notation is also supported. The special symbolic values `##Inf`, `##-Inf`, and `##NaN` represent positive infinity, negative infinity, and "not a number" values respectively. 18 | 19 | ### Character types 20 | 21 | ``` 22 | "hello" ; string 23 | \e ; character 24 | #"[0-9]+" ; regular expression 25 | ``` 26 | 27 | Strings are contained in double quotes and may span multiple lines. Individual characters are represented with a leading backslash. There are a few special named characters: `\newline` `\space` `\tab`, etc. Unicode characters can be represented with `\uNNNN` or in octal with `\oNNN`. 28 | 29 | Literal regular expressions are strings with a leading `#`. These are compiled to java.util.regex.Pattern objects. 30 | 31 | ### Symbols and idents 32 | 33 | ``` 34 | map ; symbol 35 | + ; symbol - most punctuation allowed 36 | clojure.core/+ ; namespaced symbol 37 | nil ; null value 38 | true false ; booleans 39 | :alpha ; keyword 40 | :release/alpha ; keyword with namespace 41 | ``` 42 | 43 | Symbols are composed of letters, numbers, and other punctuation and are used to refer to something else, like a function, value, namespace, etc. Symbols may optionally have a namespace, separated with a forward slash from the name. 44 | 45 | There are three special symbols that are read as different types - `nil` is the null value, and `true` and `false` are the boolean values. 46 | 47 | Keywords start with a leading colon and always evaluate to themselves. They are frequently used as enumerated values or attribute names in Clojure. 48 | 49 | ### Literal collections 50 | 51 | Clojure also includes literal syntax for four collection types: 52 | 53 | ``` 54 | '(1 2 3) ; list 55 | [1 2 3] ; vector 56 | #{1 2 3} ; set 57 | {:a 1, :b 2} ; map 58 | ``` 59 | 60 | We’ll talk about these in much greater detail later - for now it’s enough to know that these four data structures can be used to create composite data. 61 | 62 | ## Evaluation 63 | 64 | Next we will consider how Clojure reads and evaluates expressions. 65 | 66 | ### Traditional Evaluation (Java) 67 | 68 | ![Java evaluation](https://clojure.org/images/content/guides/learn/syntax/traditional-evaluation.png) 69 | 70 | In Java, source code (.java files) are read as characters by the compiler (javac), which produces bytecode (.class files) which can be loaded by the JVM. 71 | 72 | ### Clojure Evaluation 73 | 74 | ![Clojure evaluation](https://clojure.org/images/content/guides/learn/syntax/clojure-evaluation.png) 75 | 76 | In Clojure, source code is read as characters by the [Reader](https://clojure.org/reference/reader). The Reader may read the source either from .clj files or be given a series of expressions interactively. The Reader produces Clojure data. The Clojure compiler then produces the bytecode for the JVM. 77 | 78 | There are two important points here: 79 | 80 | 1. The unit of source code is a **Clojure expression**, not a Clojure source file. Source files are read as a series of expressions, just as if you typed those expressions interactively at the REPL. 81 | 2. Separating the Reader and the Compiler is a key separation that makes room for macros. Macros are special functions that take code (as data), and emit code (as data). Can you see where a loop for macro expansion could be inserted in the evaluation model? 82 | 83 | ### Structure vs Semantics 84 | 85 | Consider a Clojure expression: 86 | 87 | ![Structure and semantics](https://clojure.org/images/content/guides/learn/syntax/structure-and-semantics.png) 88 | 89 | This diagram illustrates the difference between syntax in green (the Clojure data structure produced by the Reader) and semantics in blue (how that data is understood by the Clojure runtime). 90 | 91 | Most literal Clojure forms evaluate to themselves, **except** symbols and lists. Symbols are used to refer to something else and when evaluated, return what they refer to. Lists (as in the diagram) are evaluated as invocation. 92 | 93 | In the diagram, (+ 3 4) is read as a list containing the symbol (+) and two numbers (3 and 4). The first element (where + is found) can be called "function position", that is, a place to find the thing to invoke. While functions are an obvious thing to invoke, there are also a few special operators known to the runtime, macros, and a handful of other invokable things. 94 | 95 | Considering the evaluation of the expression above: 96 | 97 | - 3 and 4 evaluate to themselves (longs) 98 | - \+ evaluates to a function that implements `+` 99 | - evaluating the list will invoke the `+` function with 3 and 4 as arguments 100 | 101 | Many languages have both statements and expressions, where statements have some stateful effect but do not return a value. In Clojure, everything is an expression that evaluates to a value. Some expressions (but not most) also have side effects. 102 | 103 | Now let’s consider how we can interactively evaluate expressions in Clojure. 104 | 105 | ### Delaying evaluation with quoting 106 | 107 | Sometimes it’s useful to suspend evaluation, in particular for symbols and lists. Sometimes a symbol should just be a symbol without looking up what it refers to: 108 | 109 | ``` 110 | user=> 'x 111 | x 112 | ``` 113 | 114 | And sometimes a list should just be a list of data values (not code to evaluate): 115 | 116 | ``` 117 | user=> '(1 2 3) 118 | (1 2 3) 119 | ``` 120 | 121 | One confusing error you might see is the result of accidentally trying to evaluate a list of data as if it were code: 122 | 123 | ``` 124 | user=> (1 2 3) 125 | Execution error (ClassCastException) at user/eval156 (REPL:1). 126 | class java.lang.Long cannot be cast to class clojure.lang.IFn 127 | ``` 128 | 129 | For now, don’t worry too much about quote but you will see it occasionally in these materials to avoid evaluation of symbols or lists. 130 | 131 | ## REPL 132 | 133 | Most of the time when you are using Clojure, you will do so in an editor or a REPL (Read-Eval-Print-Loop). The REPL has the following parts: 134 | 135 | 1. Read an expression (a string of characters) to produce Clojure data. 136 | 2. Evaluate the data returned from #1 to yield a result (also Clojure data). 137 | 3. Print the result by converting it from data back to characters. 138 | 4. Loop back to the beginning. 139 | 140 | One important aspect of #2 is that Clojure always compiles the expression before executing it; Clojure is **always** compiled to JVM bytecode. There is no Clojure interpreter. 141 | 142 | ``` 143 | user=> (+ 3 4) 144 | 7 145 | ``` 146 | 147 | The box above demonstrates evaluating an expression (+ 3 4) and receiving a result. 148 | 149 | ### Exploring at the REPL 150 | 151 | Most REPL environments support a few tricks to help with interactive use. For example, some special symbols remember the results of evaluating the last three expressions: 152 | 153 | - `*1` (the last result) 154 | - `*2` (the result two expressions ago) 155 | - `*3` (the result three expressions ago) 156 | 157 | ``` 158 | user=> (+ 3 4) 159 | 7 160 | user=> (+ 10 *1) 161 | 17 162 | user=> (+ *1 *2) 163 | 24 164 | ``` 165 | 166 | In addition, there is a namespace `clojure.repl` that is included in the standard Clojure library that provides a number of helpful functions. To load that library and make its functions available in our current context, call: 167 | 168 | ``` 169 | (require '[clojure.repl :refer :all]) 170 | ``` 171 | 172 | For now, you can treat that as a magic incantation. Poof! We’ll unpack it when we get to namespaces. 173 | 174 | We now have access to some additional functions that are useful at the REPL: `doc`, `find-doc`, `apropos`, `source`, and `dir`. 175 | 176 | The `doc` function displays the documentation for any function. Let’s call it on `+`: 177 | 178 | ``` 179 | user=> (doc +) 180 | 181 | clojure.core/+ 182 | ([] [x] [x y] [x y & more]) 183 | Returns the sum of nums. (+) returns 0. Does not auto-promote 184 | longs, will throw on overflow. See also: +' 185 | ``` 186 | 187 | The `doc` function prints the documentation for `+`, including the valid signatures. 188 | 189 | The doc function prints the documentation, then returns nil as the result - you will see both in the evaluation output. 190 | 191 | We can invoke `doc` on itself too: 192 | 193 | ``` 194 | user=> (doc doc) 195 | 196 | clojure.repl/doc 197 | ([name]) 198 | Macro 199 | Prints documentation for a var or special form given its name 200 | ``` 201 | 202 | Not sure what something is called? You can use the `apropos` command to find functions that match a particular string or regular expression. 203 | 204 | ``` 205 | user=> (apropos "+") 206 | (clojure.core/+ clojure.core/+') 207 | ``` 208 | 209 | You can also widen your search to include the docstrings themselves with `find-doc`: 210 | 211 | ``` 212 | user=> (find-doc "trim") 213 | 214 | clojure.core/subvec 215 | ([v start] [v start end]) 216 | Returns a persistent vector of the items in vector from 217 | start (inclusive) to end (exclusive). If end is not supplied, 218 | defaults to (count vector). This operation is O(1) and very fast, as 219 | the resulting vector shares structure with the original and no 220 | trimming is done. 221 | 222 | clojure.string/trim 223 | ([s]) 224 | Removes whitespace from both ends of string. 225 | 226 | clojure.string/trim-newline 227 | ([s]) 228 | Removes all trailing newline \n or return \r characters from 229 | string. Similar to Perl's chomp. 230 | 231 | clojure.string/triml 232 | ([s]) 233 | Removes whitespace from the left side of string. 234 | 235 | clojure.string/trimr 236 | ([s]) 237 | Removes whitespace from the right side of string. 238 | ``` 239 | 240 | If you’d like to see a full listing of the functions in a particular namespace, you can use the `dir` function. Here we can use it on the `clojure.repl` namespace: 241 | 242 | ``` 243 | user=> (dir clojure.repl) 244 | 245 | apropos 246 | demunge 247 | dir 248 | dir-fn 249 | doc 250 | find-doc 251 | pst 252 | root-cause 253 | set-break-handler! 254 | source 255 | source-fn 256 | stack-element-str 257 | thread-stopper 258 | ``` 259 | 260 | And finally, we can see not only the documentation but the underlying source for any function accessible by the runtime: 261 | 262 | ``` 263 | user=> (source dir) 264 | 265 | (defmacro dir 266 | "Prints a sorted directory of public vars in a namespace" 267 | [nsname] 268 | `(doseq [v# (dir-fn '~nsname)] 269 | (println v#))) 270 | ``` 271 | 272 | As you go through this workshop, please feel free to examine the docstring and source for the functions you are using. Exploring the implementation of the Clojure library itself is an excellent way to learn more about the language and how it is used. 273 | 274 | It is also an excellent idea to keep a copy of the [Clojure Cheatsheet](https://clojure.org/api/cheatsheet) open while you are learning Clojure. The cheatsheet categorizes the functions available in the standard library and is an invaluable reference. 275 | 276 | Now let’s consider some Clojure basics to get you going…. 277 | 278 | ## Clojure basics 279 | 280 | ### `def` 281 | 282 | When you are evaluating things at a REPL, it can be useful to save a piece of data for later. We can do this with `def`: 283 | 284 | ``` 285 | user=> (def x 7) 286 | #'user/x 287 | ``` 288 | 289 | `def` is a special form that associates a symbol (x) in the current namespace with a value (7). This linkage is called a `var`. In most actual Clojure code, vars should refer to either a constant value or a function, but it’s common to define and re-define them for convenience when working at the REPL. 290 | 291 | Note the return value above is `#'user/x` - that’s the literal representation for a var: `#'` followed by the namespaced symbol. `user` is the default namespace. 292 | 293 | Recall that symbols are evaluated by looking up what they refer to, so we can get the value back by just using the symbol: 294 | 295 | ``` 296 | user=> (+ x x) 297 | 14 298 | ``` 299 | 300 | ### Printing 301 | 302 | One of the most common things you do when learning a language is to print out values. Clojure provides several functions for printing values: 303 | 304 | | | For humans | Readable as data | | 305 | | :-------------- | :--------- | :--------------- | :-- | 306 | | With newline | println | prn | | 307 | | Without newline | print | pr | | 308 | 309 | The human-readable forms will translate special print characters (like newlines and tabs) to their printed form and omit quotes in strings. We often use `println` to debug functions or print a value at the REPL. `println` takes any number of arguments and interposes a space between each argument’s printed value: 310 | 311 | ``` 312 | user=> (println "What is this:" (+ 1 2)) 313 | What is this: 3 314 | ``` 315 | 316 | The println function has side-effects (printing) and returns nil as a result. 317 | 318 | Note that "What is this:" above did not print the surrounding quotes and is not a string that the Reader could read again as data. 319 | 320 | For that purpose, use prn to print as data: 321 | 322 | ``` 323 | user=> (prn "one\n\ttwo") 324 | "one\n\ttwo" 325 | ``` 326 | 327 | Now the printed result is a valid form that the Reader could read again. Depending on context, you may prefer either the human form or the data form. 328 | 329 | ## Test your knowledge 330 | 331 | 1. Using the REPL, compute the sum of 7654 and 1234. 332 | 2. Rewrite the following algebraic expression as a Clojure expression: `( 7 + 3 * 4 + 5 ) / 10`. 333 | 3. Using REPL documentation functions, find the documentation for the `rem` and `mod` functions. Compare the results of the provided expressions based on the documentation. 334 | 4. Using `find-doc`, find the function that prints the stack trace of the most recent REPL exception. 335 | -------------------------------------------------------------------------------- /01~快速开始/99~参考资料/2019~《Clojure 学习笔记》/03~小火汁,没想到你也是序列.md: -------------------------------------------------------------------------------- 1 | 在上一节中,我们粗略的概览了 Clojure 的复合类型,在这一节以及下一节中,我们会更加深入这个主题。首先,我们会对 Clojure 中序列的概念做一个简单的介绍。然后我们会引入高阶函数的概念——Clojure 大量使用了高阶函数作为操作序列的工具。接着我们会列举一些 Clojure 中各种常用的序列操作函数。 2 | 3 | 在本章节的最后,我们会动手实现两个函数——一个 compress 函数,用于对序列进行“压缩”;一个 count-map 函数,用于对序列中重复出现的值进行计数。 4 | 5 | --- 6 | 7 | ## 一切皆序列? 8 | 9 | 上一节我们提到的字符串、列表、向量、映射表、集合之间并不是毫无关联的——它们有一个共同的抽象:**序列(seq)**。我们可以用函数 seq 将一个复合结构转化成序列: 10 | 11 | ```clojure 12 | (class (seq "abc")) 13 | ;=> clojure.lang.StringSeq 14 | (class (seq [1 2 3])) 15 | ;=> clojure.lang.PersistentVector$ChunkedSeq 16 | ``` 17 | 18 | 序列并没有底层的实现,而是一个抽象的接口。序列拥有三个基本操作: 19 | 20 | - (**first** _seq_) ——返回序列的第一项 21 | - (**rest\***seq\*) ——返回除第一项以外的余项 22 | - (**cons** _elem seq_) ——返回一个新序列,将 elem 添加到 seq 的前面 23 | 24 | ```clojure 25 | (def nums [1 2 3]) 26 | (first nums) ;=> 1 27 | (rest nums) ;=> (2 3) 28 | (cons 0 nums) ;=> (0 1 2 3) 29 | ``` 30 | 31 | 可以看到,任何一个可以切割成头尾两部分的对象都可以是序列。我们再举几个序列的例子: 32 | 33 | - 一个字符串(字符序列) 34 | - 孙悟空的妖怪女朋友们(谢罪序列) 35 | - 野兽先辈是女生的 114514 种假说(恶 臭 序 列) 36 | - 全体自然数的集合(无限长序列) 37 | - 系统中的状态变化(惰性序列) 38 | 39 | 可以看到,序列是一个非常抽象的概念,除了能够表示像向量这样的集合以外,序列还能表示各种存在先后顺序的事物。我们将一个可以转化为序列的结构称作**可序化结构**。 40 | 41 | 有了序列我们就可以实现各种通用的算法,而不用针对每种特定的序列单独实现了。 42 | 43 | 值得一提的是,对空列表、空向量等对象调用 seq 函数时,将会返回 nil。我们之前提到过空列表('( ))、空向量([ ])等对象的真值是 true,这在实现一些算法时很不方便,而使用 seq 就能解决这个问题: 44 | 45 | ```clojure 46 | ;无法正常工作的my-empty 47 | (defn my-empty? [xs] (if xs true false)) 48 | ;=> #'user/my-empty? 49 | (my-empty? []) 50 | ;=> true 51 | 52 | ;使用seq修复这个bug 53 | (defn my-empty? [xs] (if (seq xs) true false)) 54 | ;=> #'user/my-empty? 55 | (my-empty? []) 56 | ;=> false 57 | ``` 58 | 59 | 将一个复合结构转化成 seq 再进行操作,是一种 Clojure 的惯用法。 60 | 61 | ## 高阶函数 62 | 63 | Clojure 提供了一组用于操作序列的**高阶函数**,所谓高阶函数就是满足下面两个条件中至少一个的函数: 64 | 65 | - 接受一个函数作为参数的函数 66 | - 返回一个函数作为返回值的函数 67 | 68 | 我们在 Java 中常用的一些设计模式,如*策略模式*,实际上就是使用面向对象的方式实现高阶函数的效果。 69 | 70 | 使用高阶函数操作数据结构已经被证明是一种行之有效的方法。包括 C++、Java、C#在内的许多语言都已经加入了使用高阶函数操作数据的接口(如 Java8 引入的 Stream 和 C#的 Linq)。 71 | 72 | 在过程式的编程思想中,我们通常使用**循环**来操作数据结构。下面看几个 Java 中的例子: 73 | 74 | ```java 75 | // 列表求和 76 | public static int sum(List ints) { 77 | int acc = 0; 78 | for (int n : ints) { 79 | acc += n; 80 | } 81 | return acc; 82 | } 83 | 84 | // 找出列表中所有的正整数 85 | public static List filterPos(List ints) { 86 | List temp = new ArrayList<>(); 87 | for (int n : ints) { 88 | if (n > 0) { 89 | temp.add(n); 90 | } 91 | } 92 | return temp; 93 | } 94 | 95 | // 将列表中所有的整数翻倍 96 | public static List doubled(List ints) { 97 | List temp = new ArrayList<>(); 98 | for (int n : ints) { 99 | temp.add(2 * n); 100 | } 101 | return temp; 102 | } 103 | ``` 104 | 105 | 下面让我们找出循环中存在的问题,然后循序渐进的过渡到高阶函数的世界。 106 | 107 | 我们先从 sum 开始,sum 现在工作的很好: 108 | 109 | ```java 110 | List ints = Arrays.asList(1, 2, 3, 4); 111 | System.out.println(sum(ints)); 112 | //-> 10 113 | ``` 114 | 115 | 但现在我们又有了新的需求,我们要对这个列表进行累乘。对于循环来说,我们没有办法,只好重新写一个新的静态方法: 116 | 117 | ```java 118 | public static int product(List ints) { 119 | int acc = 1; 120 | for (int n : ints) { 121 | acc *= n; 122 | } 123 | return acc; 124 | } 125 | ``` 126 | 127 | 现在让我来描述一下我是如何实现 product 的:首先我将 sum 的方法体**整!个!拷!贝!下!来!**,然后将变量 acc 的初始值改成 1,接着将+=改成\*=,product 就完成了。 128 | 129 | 你或许已经意识到了:sum 和 product 有着惊人的相似程度,那么,有没有办法对它们进行抽象呢? 130 | 131 | 让我们把视线转回 Clojure,看看 Clojure 是怎么做的: 132 | 133 | ```clojure 134 | (defn sum [xs] (reduce + 0 xs)) 135 | (defn product [xs] (reduce * 1 xs)) 136 | 137 | (def ints [1 2 3 4]) 138 | (sum ints) ;=> 10 139 | (product ints) ;=> 24 140 | ``` 141 | 142 | reduce 就是我们想要的抽象,它表示“**规约**”操作,所谓规约就是把一系列值“压缩”成一个值,诸如累加、累乘都是规约。我们可以试着自己实现一个 reduce: 143 | 144 | ```clojure 145 | (defn myreduce [fn acc xs] 146 | (if-let [[head & tail] (seq xs)] 147 | (recur fn (fn acc head) tail) 148 | acc)) 149 | (myreduce + 0 [1 2 3 4]) ;=> 10 150 | ``` 151 | 152 | myreduce 接受 3 个参数,fn 表示一个二元函数(比如(+)、(\*)等),acc 表示累积量的初始值,xs 表示传入的可序化结构。 153 | 154 | if-let 是 if 和 let 的结合体,myreduce 首先会判断(seq xs)是否为 nil,然后: 155 | 156 | - 如果不为 nil,则对 seq 进行解构,分成头尾两部分。之后将 seq 的第一项(头部)和 acc 使用函数 fn 结合,然后对 myreduce 进行递归。 157 | - 如果为 nil,说明容器中所有的元素都已经消耗完毕,此时 acc 就是我们求得的结果了。 158 | 159 | 通过 reduce,我们成功的将一种常见的循环结构进行了抽象,变成了一个可复用的高阶函数。 160 | 161 | 现在再来看看那个循环写法的例子,filterPos 和 doubled 静态方法实际上也代表着两种常见的循环结构:**过滤(filter)**和**映射(map)**。来看看 Clojure 中如何实现它们: 162 | 163 | ```clojure 164 | (defn filter-pos [xs] 165 | (filter pos? xs)) 166 | (defn doubled [xs] 167 | (map #(* % 2) xs)) 168 | (filter-pos [1 -1 -2 3 4]) ;=> (1 3 4) 169 | (doubled [1 2 3 4]) ;=> (2 4 6 8) 170 | ``` 171 | 172 | 来看看 map 和 filter 应该如何实现,其结果可能会让你大吃一惊: 173 | 174 | ```clojure 175 | (defn mymap [f xs] 176 | (seq (reduce #(conj %1 (f %2)) [] xs))) 177 | (defn myfilter [pred xs] 178 | (seq (reduce #(if (pred %2) (conj %1 %2) %1) [] xs))) 179 | 180 | (mymap str [1 2 3]) ;=> ("1" "2" "3") 181 | (myfilter pos? [1 -1 2 -2]) ;=> (1 2) 182 | ``` 183 | 184 | reduce 看起来没有我们想象的那么简单——map 和 filter 居然也可以使用 reduce 实现!这是怎么回事呢?让我们对比一下循环版本的 sum 和 doubled 的实现: 185 | 186 | ```java 187 | public static int sum(List ints) { 188 | int acc = 0; // ① 189 | for (int n : ints) { 190 | acc += n;// ② 191 | } 192 | return acc; 193 | } 194 | public static List doubled(List ints) { 195 | List temp = new ArrayList<>(); // ① 196 | for (int n : ints) { 197 | temp.add(2 * n); // ② 198 | } 199 | return temp; 200 | } 201 | ``` 202 | 203 | 比较一下两处 ① 和两处 ②,我们可以发现,doubled 实际上也是一种规约操作,只不过规约之后的结果仍是一个列表。在 sum 中,我们使用(+)作为累加器,而在 doubled 中,累加器的效果是将一个变换后的值添加到列表的尾部。 204 | 205 | 在 Clojure 中,如果我们想在向量尾部添加元素,可以使用 conj 函数: 206 | 207 | ```clojure 208 | (conj [1 2 3] 4) ;=> [1 2 3 4] 209 | ``` 210 | 211 | **!注:conj 函数也可以对其他容器使用,但是添加元素的方式视容器而定,对于向量来说 conj 是在尾部添加元素;对于 list 来说则是在头部添加元素。** 212 | 213 | 因此,我们可以使用 reduce+conj 来不断累积列表,比如:拷贝整个列表: 214 | 215 | ```clojure 216 | ;拷贝列表 217 | (reduce conj [] [1 2 3]) ;=> [1 2 3] 218 | ;反转列表 219 | (reduce #(cons %2 %1) [] [1 2 3]) ;=> (3 2 1) 220 | ``` 221 | 222 | 而 map 和 filter 只不过是在这个累积列表的过程中,加上映射和过滤的操作。 223 | 224 | ## 太多括号了? 225 | 226 | 现在让我们实现这样一个需求: 227 | 228 | - 输入一个整数列表 229 | - 去掉其中的负数 230 | - 将每一项加一 231 | - 最后求和 232 | 233 | 我们可以活用刚才学到的高阶函数的知识: 234 | 235 | ```clojure 236 | (defn filter-pos-inc-sum [xs] 237 | (reduce + 0 238 | (map inc 239 | (filter pos? xs)))) 240 | (filter-pos-inc-sum [1 2 -1 -2 3]) ;=> 9 241 | ``` 242 | 243 | emmm,这个函数确实工作的很好,但是可读性不佳——首先,括号嵌套了太多层,显得不太优雅。其次,map 明明是比 reduce 先发生的,但在我们的函数中,却是先写 reduce 在写 map。我们可以使用 ->> 宏改善我们的代码: 244 | 245 | ```clojure 246 | (defn filter-pos-inc-sum [xs] 247 | (->> xs 248 | (filter pos?) 249 | (map inc) 250 | (reduce + 0))) 251 | (filter-pos-inc-sum [1 2 -1 -2 3]) ;=> 9 252 | ``` 253 | 254 | ->>宏将一个个函数串联成一条“流水线”,xs 首先会被 filter“加工”(也就是说,xs 被传递给函数的最后一个参数),其结果会被传递给 map,最后是 reduce。可以看到,在使用了->>宏之后,filter、map 和 reduce 被组织到了同一层次当中,而不是一个嵌套的调用结构。 255 | 256 | 除了操作数据结构以外,->>也可以用于数学计算之类的地方: 257 | 258 | ```clojure 259 | (->> 1114514 260 | (+ 1919) 261 | (/ 810)) 262 | ;=> 90/12930 263 | ``` 264 | 265 | ->>还有一个亲戚->宏,不过它只能使用接受一个参数的函数作为“管道”: 266 | 267 | ```clojure 268 | (-> 25 Math/sqrt int inc str) ;=> "6" 269 | ``` 270 | 271 | ## 序列操作一览 272 | 273 | 了解了高阶函数的概念之后,让我们来看看 Clojure 提供了哪些序列操作。 274 | 275 | **构造序列** 276 | 277 | 除了最常用的 seq 函数以外,Clojure 还提供了一些其他的构造序列的函数。 278 | 279 | **range**函数用于表示一个区间(左闭右开): 280 | 281 | ```clojure 282 | (range start? end step?) 283 | (range 5) ;=> {0 1 2 3 4) 284 | (range 2 10) ;=> (2 3 4 5 6 7 8 9) 285 | (range 0 10 2) ;=> (0 2 4 6 8) 286 | ``` 287 | 288 | **repeat**函数用于不断重复某一个值: 289 | 290 | ```clojure 291 | (repeat "+1") ; 无限+1,就和你的复读机群友一样 292 | ;=> ...永远不会停下 293 | (repeat 3 "hello") 294 | ;=> ("hello" "hello" "hello") 295 | ``` 296 | 297 | repeat 还有一个亲戚**repeatedly**,不同之处在于 repeatedly 接受一个无参函数,通过不断重复调用这个函数来构建序列。这很适合用来构造随机数序列: 298 | 299 | ```clojure 300 | (defn rand-int-seq [max] 301 | (repeatedly #(rand-int max))) 302 | 303 | (->> (rand-int-seq 10) 304 | (map #(+ % 10)) ;=>随机数取值范围为[10, 20) 305 | (take 10) 306 | ) 307 | ``` 308 | 309 | iterate 函数用于构造一个生成器: 310 | 311 | ```clojure 312 | (iterate fnext seed) 313 | ``` 314 | 315 | 使用 iterate 构造的序列满足以下条件: 316 | 317 | - 序列的首项为 seed 318 | - 设序列的低 n-1 项为 x,那么序列的第 n 项为(fnext x) 319 | 320 | 在之前的章节中我们已经试过使用 iterate 表示全体自然数的集合,现在让我们来看一个稍复杂的例子:构造一个斐波那契数列。 321 | 322 | ```clojure 323 | (def fib 324 | (letfn [(fnext [[a b]] [b (+ a b)])] ;定义局部函数 325 | (->> (iterate fnext [0 1]) 326 | (map first)))) 327 | (take 10 fib) 328 | ;=> (0 1 1 2 3 5 8 13 21 34) 329 | ``` 330 | 331 | 这段代码不是特别直观,我们先将(map first)去掉,看看这个“半成品”是什么样子的: 332 | 333 | ```clojure 334 | (take 10 335 | (iterate 336 | (fn [[a b]] [b (+ a b)]) 337 | [0 1])) 338 | ;=> ([0 1] [1 1] [1 2] [2 3] [3 5] [5 8] [8 13] [13 21] [21 34] [34 55]) 339 | ``` 340 | 341 | 可以看到,fib 首先生成了一个二维的序列,其首项是斐波那契数列的两个初值,0 和 1 。fib 序列的后一项的第一个值是前一项的第二个值,而第二个值则可以通过序列的前一项求和得到。之后我们对 fib 的半成品进行了 map,只取其第一项就得到了斐波那契数列。 342 | 343 | 之所以这么实现是因为计算 fib 的第 n 项需要用到第 n-1 和 n-2 项两个值。 344 | 345 | **cycle**函数用于对一个序列进行无线循环: 346 | 347 | ```clojure 348 | (def zero-one [0 1]) 349 | (take 10 (cycle zero-one)) 350 | ;=> (0 1 0 1 0 1 0 1 0 1) 351 | ``` 352 | 353 | **concat**函数用于连接多个序列: 354 | 355 | ```clojure 356 | ;字母表序列 357 | (defn range-char [begin end] 358 | (map char 359 | (range (int begin) (inc (int end))))) 360 | (def charset 361 | (concat 362 | (range-char \A \Z) 363 | (range-char \a \z))) 364 | charset 365 | ;=> (\A \B \C ...\Z \a \b \c ...\z) 366 | ``` 367 | 368 | **interleave**使用交错的方式将多个容器合并成一个: 369 | 370 | ```clojure 371 | (interleave natural-nums charset) 372 | ;=> (0 \A 1 \B 2 \C ... 49 \x 50 \y 51 \z) 373 | ``` 374 | 375 | 如你所见,interleave 可以将无限序列和有限序列交织在一起,当其中一个序列被消耗完毕时就会停止。 376 | 377 | interleave 的亲戚**interpose**将一个**分隔符**插入到序列的每两项之间,我们可以使用 interpose 在字符串中间插入男魂(♂)从而让它变得哲 ♂ 学 378 | 379 | ```clojure 380 | (defn philosophize [s] 381 | (apply str (interpose \♂ s))) 382 | (philosophize "幻想乡") 383 | ;=> "幻♂想♂乡" 384 | ``` 385 | 386 | 类似的还有使用分隔符连接字符串的**join**函数: 387 | 388 | ```clojure 389 | (join " " ["Hello" "World"]) 390 | ;=> "Hello World" 391 | ``` 392 | 393 | 每一种 Clojure 容器都有一个对应的函数用来构造它们,这些函数都接受任意参数: 394 | 395 | ```clojure 396 | (list & elems) 397 | (vector & elems) 398 | (hash-set & emels) 399 | (hash-map & keys-values) 400 | ``` 401 | 402 | 通过 apply 函数我们可以让一个序列在各个容器间转换: 403 | 404 | ```clojure 405 | (apply hash-map [:a 1 :b 2 :c 3]) 406 | {:c 3, :b 2, :a 1} 407 | ``` 408 | 409 | vector 和 hash-set 分别有一个亲戚 vec 和 set,它们不接受可变参数,而是接受一个容器: 410 | 411 | ```clojure 412 | (set [:a :b :c]) 413 | ;=> #{:c :b :a} 414 | (vec {:a 1, :b 2, :c 3}) 415 | ;=> [[:a 1] [:b 2] [:c 3]] 416 | ``` 417 | 418 | **过滤、截取序列** 419 | 420 | Clojure 中常用的过滤函数 filter 我们在上文中已经介绍过了,这里不再赘述。 421 | 422 | **remove**与 filter 相反,去掉序列中满足谓词的元素 423 | 424 | ```clojure 425 | (remove even? (range 10)) 426 | ;=> (1 3 5 7 9) 427 | ``` 428 | 429 | **take**函数截取序列的前 n 项,如果 n 大于序列的总长度,则返回整个序列: 430 | 431 | ```clojure 432 | (take 3 natural-nums) 433 | ;=> (0 1 2) 434 | ``` 435 | 436 | **take-while**截取前 n 个满足条件的元素 437 | 438 | ```clojure 439 | (take-while #(< % 10) natural-nums) 440 | ;=> (0 1 2 3 4 5 6 7 8 9) 441 | ``` 442 | 443 | **drop、drop-while**与 take 相反,舍弃序列的前 n 项 444 | 445 | ```clojure 446 | (drop 5 (range 10)) 447 | ;=> (5 6 7 8 9) 448 | ``` 449 | 450 | **split-at**在给出的索引处将序列切成两份 451 | 452 | ```clojure 453 | (split-at 5 (range 10)) 454 | ;=> [(0 1 2 3 4) (5 6 7 8 9)] 455 | ``` 456 | 457 | **split-with**接受一个谓词 pred,将序列分成(take-while pred coll)和(drop-while pred coll)两份 458 | 459 | ```clojure 460 | (split-with #(< % 3) (range 10)) 461 | ;=> [(0 1 2) (3 4 5 6 7 8 9)] 462 | ``` 463 | 464 | **序列谓词** 465 | 466 | 序列谓词用于判断序列是否满足一定的条件。 467 | 468 | **every?**判断是否序列的所有项都满足谓词 469 | 470 | ```clojure 471 | (every? pos? [0 1 2]) ;=> true 472 | (every? pos? [-1 1 2]);=> false 473 | ``` 474 | 475 | **some**用于判断序列中是否存在满足谓词的元素 476 | 477 | ```clojure 478 | (some pos? [-1 1 2]) ;=> true 479 | (some pos? [-1 -2 0]) ;=> nil 480 | ``` 481 | 482 | 注意,some 不以问号结尾,并且在没有任意项满足条件时返回 nil(而不是 false),这是因为尽管 some 总是被当做谓词使用,但其本身并非谓词: 483 | 484 | ```clojure 485 | ;identity总是返回其参数本身 486 | (some identity [nil false 1 2 3]) 487 | ;=> 1 488 | ``` 489 | 490 | 实际上 some 的运作方式是挨个对元素调用传入的函数,当找到第一个返回值不为 nil 或 false 的项 x 时,返回 f(x)。之所以(some pos? [-1 1 2])返回 true 是因为 pos?本身就返回布尔值。 491 | 492 | **not-every?**行为与 every 相反,如果存在不满足谓词的元素则返回真,否则返回假;**not-any**则与 some 相反,当所有元素都不满足谓词时才返回真,这里不再赘述。 493 | 494 | **序列变换** 495 | 496 | 常用的序列变换操作 map 和 reduce 已经在上文中介绍过了,这里不再赘述。 497 | 498 | map 有一个特点就是它永远无法改变序列的总长度,但**mapcat**可以做到。顾名思义,mapcat 就是 map + concat。 499 | 500 | ```clojure 501 | (defn uncompress [coll] 502 | (mapcat (fn [[x n]] (repeat n x)) coll)) 503 | (uncompress [[:a 2] [:b 3] [:c 4]]) 504 | ;=> (:a :a :b :b :b :c :c :c :c) 505 | ``` 506 | 507 | 有趣的是,mapcat 也可以用来实现 map、filte,这证明 mapcat 是一个非常强大的函数 508 | 509 | ```clojure 510 | (defn mymap [f coll] 511 | (mapcat #(cons (f %) nil) coll)) 512 | 513 | (defn myfilter [pred coll] 514 | (mapcat (fn [x] (if (pred x) (cons x nil) nil)) coll)) 515 | ``` 516 | 517 | **sort**函数和其他语言的牌序函数没什么不同: 518 | 519 | ```clojure 520 | (sort [4 1 3 2]) 521 | ;=> (1 2 3 4) 522 | ``` 523 | 524 | sort 函数能够接受一个谓词作为比较方法: 525 | 526 | ```clojure 527 | (sort > [4 1 3 2]) 528 | ;=> (4 3 2 1) 529 | ``` 530 | 531 | sort-by 会先对容器中的元素调用一个函数 fn,根据 fn 的结果来进行排序 532 | 533 | ```clojure 534 | (def scores [ 535 | {:name "Khellendros", :score 100} 536 | {:name "KMR", :score 114514} 537 | {:name "Jack", :score 80} 538 | ]) 539 | (sort-by :score > scores) 540 | ;=> ({:name "KMR", :score 114514} {:name "Khellendros", :score 100} {:name "Jack", :score 80}) 541 | ``` 542 | 543 | 上面的代码会根据每个人的成绩降序排列 scores 序列。 544 | 545 | group-by 能够对序列进行分组,首先对序列中的每一项调用一个函数 f,然后将返回值相同的项放在同一个组里,以 f 的返回值作键: 546 | 547 | (group-by identity [:a :a :b :c :b]) 548 | ;=> {:a [:a :a], :b [:b :b], :c [:c]} 549 | **partition**用于将序列分为 n 个一组: 550 | 551 | ```clojure 552 | (partition 2 (range 10)) 553 | ;=> ((0 1) (2 3) (4 5) (6 7) (8 9)) 554 | ``` 555 | 556 | --- 557 | 558 | ## 计数函数 count-map 559 | 560 | 我们先从一个简单的例子开始:统计序列中相同的元素的数量: 561 | 562 | ```clojure 563 | (count-map [:a :b :a :c :a :b]) 564 | ;=> ([:a 3], [:b 2], [:c 1]) 565 | ``` 566 | 567 | 我们可以使用 group-by 对序列进行分组,得到一个映射表,然后用 map 和 count 将映射表的 value 转换成长度: 568 | 569 | ```clojure 570 | (defn count-map [coll] 571 | (->> coll 572 | (group-by identity) 573 | (map (fn [[k vs]] [k (count vs)])))) 574 | ``` 575 | 576 | ## 压缩函数 compress 577 | 578 | 当我们需要处理大量数据时,最好先对数据进行压缩在进行运算。compress 就是一个简单的压缩算法。他会将邻接的 n 个相同的元素 x 压缩成 x n 的形式: 579 | 580 | ```clojure 581 | (compress [:a :a :a :a :b :b :b :c :a :a]) 582 | ;=> (:a 4 :b 3 :c 1 :a 2) 583 | ``` 584 | 585 | 当我们需要将多个元素压缩成 1 个时,我们首先会想到 reduce。 586 | 587 | reduce 的本质是对列表进行“累积”,而 compress 需要累积的值有三项: 588 | 589 | 1. 前一个元素的值 590 | 2. 前一个元素已累积的数量 591 | 3. 已完成压缩的序列 592 | 593 | 有了思路之后我们就能写下 cmpr 函数: 594 | 595 | ```clojure 596 | ;x-前一个元素 597 | ;n-前一个元素已积攒的l数量 598 | ;acc-已完成压缩的序列 599 | ;elem-当前元素 600 | (defn cmpr [[x n acc] elem] 601 | (if (= x elem) 602 | [x (inc n) acc] ;如果x与elem相等,增加n的值 603 | [elem 1 (conj acc x n)] ;否则开始积攒elem,将x和n加入已完成压缩序列 604 | )) 605 | ``` 606 | 607 | 显而易见的,reduce 的初值应当设为[nil 0 []]。 608 | 609 | 注意,当 reduce 完成后,序列的最后一项还没有被加进 acc 里面,我们需要进行一个收尾操作: 610 | 611 | ```clojure 612 | (defn end-reduce [[x n acc]] (conj acc x n )) 613 | ``` 614 | 615 | 另外记得结果序列的前两项是 nil 0,需要用 drop 2 舍弃它们。 616 | 617 | 将以上内容整合起来,compress 函数就完成了: 618 | 619 | ```clojure 620 | (defn compress [coll] 621 | (letfn [ 622 | (cmpr [[x n acc] elem] 623 | (if (= x elem) 624 | [x (inc n) acc] 625 | [elem 1 (conj acc x n)])) 626 | (end-reduce [[x n acc]] (conj acc x n))] 627 | (->> coll 628 | (reduce cmpr [nil 0 []]) 629 | end-reduce 630 | (drop 2))) 631 | ``` 632 | -------------------------------------------------------------------------------- /01~快速开始/99~参考资料/2019~《Clojure 学习笔记》/02~快速上手.md: -------------------------------------------------------------------------------- 1 | # 快速上手 2 | 3 | 这一节让我们来了解一下那些“使用 Clojure 编程必须要掌握的东西”。 4 | 5 | 其实 Clojure(以及其他 lisp 系语言)的学习曲线还是比较陡峭的。学习 Clojure 之前必须要掌握一些基础的数据结构和算法知识。 6 | 7 | ## 同像性 8 | 9 | 其实 Lisp 系语言的语法并没有什么可说的,因为 Lisp 系语言的代码实际上是由**Lisp 数据**构成的。换句话说,当我们编写 Lisp 代码时,我们事实上是在描述一个**数据结构**。这种特性被称作**同像性**。例如,当我们在 Lisp 中调用函数时,实际上我们是写下了一个列表结构: 10 | 11 | ```clojure 12 | (+ 1 1) ; 函数调用 13 | ;=> 2 14 | (class ‘(+ 1 1)) ; 查看(+ 1 1)的类型 15 | ;=> clojure.lang.PersistentList 16 | ``` 17 | 18 | 可以看到,(+ 1 1)的类型实际上是一个 PersistentList。 19 | 20 | 因此,学习 Lisp 的语法其实就是在学习 Lisp 的数据表示。 21 | 22 | 同向性带来的好处是我们可以**像操作数据结构一样操作代码**,这为 lisp 系语言带来了其他语言不可比拟的元编程能力。 23 | 24 | ## 形式(Form) 25 | 26 | lisp 的语法单元被称为**形式**。形式指那些能够被读取器读入的代码片段。诸如数值、字符串、列表以及其他复合结构都是形式。相对的,一个没有闭合的列表不是一个形式。 27 | 28 | 在 Clojure 中,有一类**特殊形式**,比如用于定义变量的**def**,它们是语言的基础组成部分。 29 | 30 | ## 数值类型 31 | 32 | 和绝大多数语言一样,Clojure 中自然也有数值类型,对数值类型求值,当然也会返回这个数值本身(废话!) 33 | 34 | 除了常见的整型和浮点数外,Clojure 还支持任意精度实数和任意精度整数: 35 | 36 | ```clojure 37 | (class 0.0000000000001M) ; 任意精度实数 38 | ;=> java.math.BigDecimal ; 它的实际类型 39 | (class 999999999999999999N) ; 任意精度整数 40 | ;=> java.math.Bigint 41 | ``` 42 | 43 | 除此之外,Clojure 还支持“有理数”类型: 44 | 45 | ```clojure 46 | (/ 1 3) 47 | ;=> 1/3 ; 除法的结果以分数形式表示 48 | ``` 49 | 50 | 相较于浮点数,有理数类型不会损失精度,但会带来一定的性能损失。 51 | 52 | ## 数值计算 53 | 54 | Clojure 的数值计算与普通的函数调用并无不同: 55 | 56 | ```clojure 57 | (+ 1 2) ;=> 3 58 | (- 2 1) ;=> 1 59 | (* 2 2) ;=> 4 60 | (/ 4 2) ;=> 2 61 | (* (+ 1 2) 3) ;=> 9 62 | ``` 63 | 64 | 有趣的是,这些函数都是可以接受任意数量的参数的: 65 | 66 | ```clojure 67 | (+ 1 2 3 4) ;=> 10 68 | (apply * [1 2 3 4]) ;=> 24 69 | (- 1) ;=> -1 70 | (+) ;=> 0 71 | (*) ;=> 1 72 | ``` 73 | 74 | ## 数值比较 75 | 76 | 数值比较在 Clojure 中也是函数: 77 | 78 | ```clojure 79 | (> 2 1) ; => true 80 | (= 1 1) ;=> true 81 | ``` 82 | 83 | 这些函数也可以接受任意参数,因此,我们可以用>=函数判断一个数列是否为降序: 84 | 85 | ```clojure 86 | (defn desc? [nums] (apply >= nums)) ; 定义函数desc? 87 | ;=> #'user/desc? 88 | (desc? [4 3 2 1]) 89 | ;=> true 90 | ``` 91 | 92 | _注:在 Clojure 中有一个命名约定:返回值为布尔类型的函数应该以问号结尾。_ 93 | 94 | ## 布尔值与 nil 95 | 96 | 布尔值只包含 true 和 false 两个值。在 Clojure 中,nil 相当于 java 中的 null。Clojure 的真值判断遵循一个简单的规则:false、nil 为假,其余值都为真。 97 | 98 | **!注意:在 Clojure 中,空列表不为假** 99 | 100 | ## 条件判断 101 | 102 | Clojure 中自然也有分支跳转,需要用到一个叫 **if** 的特殊形式: 103 | 104 | ```clojure 105 | (if test then else?) 106 | ``` 107 | 108 | if 十分简单,当 test 为真时返回 then,为假时返回 else(如果有的话)。注意,当 test 为假时,else 是不会被求值的。 109 | 110 | ```clojure 111 | (if (= (+ 1 1) 2) 112 | "Math still works today!" 113 | (println "Never happens")) 114 | ;=> "Math still works today!" 115 | ``` 116 | 117 | 与 Java、C 语言的 if 不同,Clojure 的 if 是具有返回值的。因此其实它相当于其他语言的三元运算符。 118 | 119 | if 中的 then 块只能包含一个形式,如果需要执行多条语句可以使用 when: 120 | 121 | ```clojure 122 | (when (= (+ 1 1) 2) 123 | (println "print something") 124 | "Math still works today!") 125 | ;; print something 126 | ;=> "Math still works today!" 127 | ``` 128 | 129 | 注意:when 没有 else 块,如果判断条件不满足,when 会返回 nil。 130 | 131 | 许多语言都有 if-else if-else 的语言结构,在 Clojure 中可以使用**cond**实现同样的效果: 132 | 133 | ```clojure 134 | (defn speak [x] 135 | (cond 136 | (= x :dog) "Woof!Woof!Woof!" 137 | (= x :cat) "Mew~" 138 | (= x :repeater) *1 ; *1是REPL的特殊变量,表示上一个在REPL中求得的值 139 | :else nil)) ; :else会被求值为真,因此当上述条件都不满足时就会返回nil 140 | (speak :dog) ; => "Woof!Woof!Woof!" 141 | (speak :repeater) ; => "Woof!Woof!Woof!" 142 | (speak :cat) ; => “Mew~” 143 | (speak :repeater) ; => “Mew~” 144 | (speak :monkey) ; => nil 145 | ``` 146 | 147 | Clojure 中也有类似 switch 的结构**case**,上述例子可以用 case 进行改写: 148 | 149 | ```clojure 150 | (defn speak2 [x] 151 | (case x 152 | :dog "Woof! Woof! Woof!" 153 | :cat "Mew~" 154 | :repeater *1 155 | nil)) ; 最后一行表示默认情况 156 | ``` 157 | 158 | ## 符号与变量 159 | 160 | Clojure 中的符号类似其他语言中的标识符。除了能够使用字母、数字、下划线**(注:符号不能以数字开头)**以外,Clojure 符号还能够使用一些特殊符号,如+、-、\*、/、<、>、.等等。注意,除号(/)和句点(.)常被用于命名空间。 161 | 162 | Clojure 中可以使用特殊形式 def 声明一个变量: 163 | 164 | ```clojure 165 | (def myname "Khellendros") 166 | ;=> #'user/myname 167 | myname 168 | ;=> "Khellendros" 169 | ``` 170 | 171 | 变量的初始值不是必须的: 172 | 173 | ```clojure 174 | (def no-value) 175 | no-value 176 | #object[clojure.lang.Var$Unbound 0xcc0a548 "Unbound: #'user/no-value"] 177 | ``` 178 | 179 | 使用 def 定义的变量都是全局变量,使用特殊形式 let 可以定义局部量,这些局部量只能在 let 范围内使用: 180 | 181 | ```clojure 182 | (let [myage 22, mygender :male] 183 | (do (println "Name: " myname) 184 | (println "Age: " myage) 185 | (println "Gender: " mygender))) 186 | ;; Name: Khellendros 187 | ;; Age: 22 188 | ;; Gender: :male 189 | ;=> nil 190 | myage 191 | ;=> CompilerException java.lang.RuntimeException: Unable to resolve symbol: myage in this context, compiling:(null:0:0) 192 | ``` 193 | 194 | ## 关键字 195 | 196 | 关键字类似于符号,不同之处在于,符号通常都会引用其他事物(比如变量和函数名),而关键字仅仅代表它本身。关键字的命名规则与符号类似,但必须以冒号(:)开头,且数字可用紧跟着冒号。 197 | 198 | ```clojure 199 | :a-keywd 200 | ;=> :a-keywd 201 | :1+! 202 | ;=> :1+! 203 | ``` 204 | 205 | 关键字最常见的用法是充当关联结构(如 map 和 set)的**键值**。在 Python 和 JavaScript 中,我们通常会用字符串充当键值,然而,比较两个字符串是相当低效的做法,关键字相当于是单例对象,只需要比较它们的内存地址就可以了,效率显然要高很多。此外,关键字也可以用于表示**枚举**值。 206 | 207 | ## 字符与字符串 208 | 209 | 在 Clojure 中,用反斜杠后面紧跟着一个字符表示字符类型: 210 | 211 | ```clojure 212 | (class \a) 213 | ;=> java.lang.Character 214 | ``` 215 | 216 | 而字符串和 java 一样用双引号表示: 217 | 218 | ```clojure 219 | (class “Hello”) 220 | ;=> java.lang.String 221 | 字符串中间可以换行: 222 | “Hello 223 | world!” 224 | ;=> “Hello\nworld!” 225 | ``` 226 | 227 | str 函数用以将一个值转化为字符串: 228 | 229 | ```clojure 230 | (str 1) ;=> “1” 231 | ``` 232 | 233 | str 也可以接受多个参数,此时它会将参数转化为字符串后拼接起来,并且会跳过 nil 234 | 235 | ```clojure 236 | (str 1 2 nil 3) ;=> “123” 237 | ``` 238 | 239 | ## 正则表达式 240 | 241 | 在 Clojure 中可以使用井号(#)加字符串的方式定义一个正则表达式: 242 | 243 | ```clojure 244 | (def words #”\w+”) 245 | ``` 246 | 247 | 也可以使用 re-pattern 函数,不过注意要对正则表达式中的特殊字符用双反斜杠(\\)转义: 248 | 249 | ```clojure 250 | (def words2 (re-pattern \\w+)) 251 | ``` 252 | 253 | 使用 re-seq 可以找到字符串中所有的匹配项: 254 | 255 | ```clojure 256 | (re-seq words “aaa bbb|ccc,ddd”) 257 | ;=> (“aaa” “bbb” “ccc” “ddd”) 258 | ``` 259 | 260 | 我们可以在正则表达式中添加分组: 261 | 262 | ```clojure 263 | (def middle-part #"\w+\-(\w+)\-\w+") 264 | ;=> #'user/middle-part 265 | (re-seq middle-part "aaa-AAA-aaa bbb-BBB-bbb ccc-CCC-ccc") 266 | ;=> (["aaa-AAA-aaa" "AAA"] ["bbb-BBB-bbb" "BBB"] ["ccc-CCC-ccc" "CCC"]) 267 | ``` 268 | 269 | 此时 re-seq 将返回一个二维序列。 270 | 271 | ## 复合类型 272 | 273 | Clojure 提供了一组功能非常强大的容器,包括列表(list)、向量(vector)、映射表(map)、集合(set),由于篇幅有限这里仅对它们做一些简单的介绍。 274 | 275 | 所有这些 Clojure 容器都是**不可变**的,当我们使用诸如 replace 这样的函数改变容器内的元素时,我们将得到一个新的容器,而旧容器始终保持不变。 276 | 277 | 也许你会觉得这样的做法十分低效:“每次我要对容器进行更改时都需要把整个容器都复制一遍?”。其实恰恰相反,Clojure 容器是十分高效的。不可变也意味着更容易实现“共享”——新旧容器之间可以共享大部分内容,当我们对容器做出更改时,通常只会生成一个新的**根结点**而不用拷贝整个容器。 278 | 279 | **列表(list)**是 Clojure(以及各种 Lisp 系语言)中最常见的结构,不过在 Clojure 中它的主要作用不是作为容器而是用来表示函数(或宏)调用。列表以一对括号表示,列表中的元素以空格或逗号分隔。 280 | 281 | ```clojure 282 | (+ 1 1) 283 | ;=> 2 284 | ``` 285 | 286 | 在 Clojure 中,最常用的容器是**向量(vector)**。向量以一对中括号([ ])表示: 287 | 288 | ```clojure 289 | [1 2 3] 290 | ;=> [1 2 3] 291 | ``` 292 | 293 | 向量的一大特点是它支持高效的随机访问。虽然向量的底层不是数组(array),但它进行随机访问的效率和数组相差无几。 294 | 295 | ```clojure 296 | (nth [1 2 3] 1) ; => 2 297 | (get [1 2 3] 1) ; => 2 298 | ``` 299 | 300 | 向量本身也可以当做函数使用,效果等同于 nth: 301 | 302 | ```clojure 303 | ([1 2 3] 1) ;=> 2 304 | ``` 305 | 306 | **!但是注意,向量不能当成集合来使用,对向量使用 contains?函数将永远返回 true,哪怕该元素其实并不存在。** 307 | 308 | ```clojure 309 | (contains? [1 2 3] 0) ;=> true 310 | ``` 311 | 312 | **映射表(map)**常用来表示一些相互关联的键值对,它使用花括号({ })来定义。 313 | 314 | ```clojure 315 | (def me {:name "Khellendros", :age 22, :gender :male}) 316 | ;=> #’user/me 317 | ``` 318 | 319 | **!注意:映射表中不能出现重复的键** 320 | 321 | 使用 get 函数可以根据键查找值: 322 | 323 | ```clojure 324 | (get me :name) 325 | ;=> "Khellendros" 326 | ``` 327 | 328 | 映射表也可以直接当成函数使用,效果等同于调用 get 函数: 329 | 330 | ```clojure 331 | (me :gender) 332 | ;=> :male 333 | ``` 334 | 335 | 此外,关键字也能当成函数使用: 336 | 337 | ```clojure 338 | (:age me) 339 | ;=> 22 340 | ``` 341 | 342 | 使用关键字作为函数对映射表进行查询,是 Clojure 的一种惯用法。 343 | 344 | 映射表的键可以是任意类型,同时同一个映射表的键的类型也可以各不相同: 345 | 346 | ```clojure 347 | (def mess-map { 348 | :name "@#$$%", 349 | [1 2 3] "aaa", 350 | "?" 233 } ) 351 | ;=> #'user/mess-map 352 | (mess-map [1 2 3]) 353 | ;=> "aaa" 354 | (mess-map "?") 355 | ;=> 233 356 | ``` 357 | 358 | **集合(set)**相当于键和值相同的映射表。集合使用井号加花括号(#{ })表示,集合的内容不能重复。 359 | 360 | ```clojure 361 | (def img-exts #{"jpg" "gif" "png" "bmp"}) 362 | ;=> #'user/img-exts 363 | ``` 364 | 365 | 集合通常用来判断其是否包含某个元素: 366 | 367 | ```clojure 368 | (contains? img-exts "jpg") 369 | ;=> true 370 | ``` 371 | 372 | 集合自身也可以当做函数使用,效果等同于 get 373 | 374 | ```clojure 375 | (img-exts "jpg") 376 | ;=> "jpg" 377 | (img-exts "txt") 378 | ;=> nil 379 | ``` 380 | 381 | ## 解构 382 | 383 | 复合类型可以进行解构。复合类型的构造可以看做是将多个量聚合成一个,而解构则是构造的逆过程,可以将复合解构拆解成多个量。 384 | 385 | 解构可以在许多地方发生,这里先以上面提到过的 let 为例: 386 | 387 | ```clojure 388 | (def nums [1 2 3]) 389 | ;=> #’user/nums 390 | (let [[a b c] nums] ; 解构nums 391 | (+ a b c)) 392 | ;=> 6 393 | ``` 394 | 395 | 可以看到,向量 nums 的第 0、第 1、第 2 项分别被绑定到了变量 a、b、c 上。 396 | 397 | 我们可以只取列表的前 n 项而忽略余项: 398 | 399 | ```clojure 400 | (def natural-nums (iterate inc 0)) ; 表示全体自然数 401 | ;=> #'user/natural-nums 402 | (let [[a b c] natural-nums] (+ a b c)) 403 | ;=> 3 404 | ``` 405 | 406 | 在这里 natural-nums 表示全体自然数的总集,因此它是一个无限长的序列,但因为我们只取其前三项,因此不会出现无限循环。 407 | 408 | 如果要只区第 2 项,忽略第 0 和第 1 项可以这么写: 409 | 410 | ```clojure 411 | (let [[_ _ a] natural-nums] a) 412 | ;=> 2 413 | ``` 414 | 415 | 下划线(\_)是一个合法的符号名,使用下划线忽略某些我们不关心的值是一种惯用法。注意这里下划线被绑定了两次,因此它最终的值是 1 而不是 0。 416 | 417 | :as 命令可以将整个结构绑定到一个局部量上: 418 | 419 | ```clojure 420 | (let [[a b c :as all] nums] (str "Sum of: " all " = " (+ a b c))) 421 | ;=> "Sum of: [1 2 3] = 6" 422 | ``` 423 | 424 | 除了向量以外,映射表也可以进行绑定: 425 | 426 | ```clojure 427 | (let [{name :name, age :age} me] (str name " is " age " years old.")) 428 | :=> "Khellendros is 22 years old." 429 | ``` 430 | 431 | 这种要把键名打两遍的做法略显繁琐,我们可以使用:keys 命令进行简化: 432 | 433 | ```clojure 434 | (let [{:keys [name age]} me] (str name “ is “ age “ years old.”)) 435 | :=> "Khellendros is 22 years old." 436 | ``` 437 | 438 | 除了 let 以外,其他可以绑定局部量的位置都可以进行解构,包括但不限于函数参数, 439 | 440 | loop,for 等(见下文)。 441 | 442 | ## 函数 443 | 444 | Clojure 中有许多定义函数的方式,最常见的是使用宏 defn: 445 | 446 | ```clojure 447 | (defn hello ;定义函数 448 | "Say hello to someone." ;文档说明 449 | [name] ;函数参数 450 | (str "Hello, " name "!")) ;函数体 451 | ;=> #'user/hello 452 | (hello "World") 453 | ;=> "Hello, World!" 454 | ``` 455 | 456 | 函数可以有多个参数列表和函数体,此时各个参数列表的参数数量需要各不相同: 457 | 458 | ```clojure 459 | (defn vec-of 460 | ([a] [a]) 461 | ([a b] [a b])) 462 | ;=> #'user/vec-of 463 | (vec-of 1) 464 | ;=> [1] 465 | (vec-of 1 2) 466 | ;=> [1 2] 467 | ``` 468 | 469 | 在函数的参数声明处也可以对参数进行解构: 470 | 471 | ```clojure 472 | (defn third [[_ _ x]] x) 473 | ;=> #'user/first3 474 | (third natural-nums) 475 | ;=> 2 476 | ``` 477 | 478 | ## 代码块 479 | 480 | 函数体只能包含一个形式,如果我们要执行多个表达式怎么办呢?使用特殊形式 do 可以解决这个问题: 481 | 482 | ```clojure 483 | (do 484 | (println "first") 485 | (println "second") 486 | "not return" 487 | "return") 488 | ;;first 489 | ;;second 490 | ;=> "return" 491 | ``` 492 | 493 | block 会将最后一个形式的值当做返回值。 494 | 495 | ## 匿名函数 496 | 497 | 一些函数会使用一个回调函数作为参数,回调函数通常都只有一两行代码,如果我们懒得给它们起名字,可以使用匿名函数。匿名函数使用 fn 定义: 498 | 499 | ```clojure 500 | (def double-n (fn [n] (* n 2))) 501 | ;=> #’user/double-n 502 | (double-n 10) 503 | ;=> 20 504 | ``` 505 | 506 | 匿名函数还有一种简写形式,称作原位函数: 507 | 508 | ```clojure 509 | (def double-n-2 #(* % 2)) 510 | ;=> #’user/double-n-2 511 | (double-n-2 10) 512 | ;=> 20 513 | ``` 514 | 515 | 原位函数使用井号加括号(#( ))定义,其中%[n]表示第 n 个函数参数,%1 表示第一个参数,%2 表示第二个……以此类推。单独一个%等价于%1 516 | 517 | ## 递归 518 | 519 | Clojure 不提供类似 java 的 while 和 for 循环,需要进行迭代时可以使用递归。在 Clojure 中可以使用特殊形式 recur 进行**尾递归**: 520 | 521 | ```clojure 522 | (defn count-down [n] 523 | (when (pos? n) ; 如果n是正数就继续执行,否则返回nil 524 | (println n) ; 输出n 525 | (recur (dec n)))) ; 递减n,然后递归调用count-down 526 | ;=> #'user/count-down 527 | (count-down 10) 528 | ;;10 529 | ;;9 530 | ;;8 531 | ;;… 532 | ;;1 533 | ;=> nil 534 | ``` 535 | 536 | recur 类似于 C 语言的 goto 语句,“跳转点”默认为 recur 所在的函数开始处,我们也可以用特殊形式 loop 自定义跳转点: 537 | 538 | ```clojure 539 | (defn count [start end] 540 | (loop [n start, end end] 541 | (when (< n end) 542 | (println n) 543 | (recur (inc n) end)))) 544 | ;=> #'user/count 545 | (count 0 10) 546 | ;; 0 547 | ;; 1 548 | ;; 2 549 | ;; … 550 | ;; 9 551 | ;=> nil 552 | ``` 553 | 554 | 在 loop 中,我们绑定了两个局部量:n 和 end,当 recur 被调用时,他会将参数传递给 loop 绑定的变量。 555 | 556 | 注意:recur 只能放置在一个函数或 loop 的**出口处** 557 | 558 | ## 遍历 559 | 560 | 虽然没有传统意义上的 while 和 for 循环,但是 Clojure 提供了类似 java 的 foreach 循环的设施——**doseq**和**for**。为了方便理解,我们会通过对比 java 代码来对它们进行说明。 561 | 562 | doseq 主要用于产生副作用(比如输出到控制台)。我们通过一个实际的例子来讲解一下 doseq 的用法: 563 | 564 | ```clojure 565 | (doseq [n [1 2 3]] ; 将列表[1 2 3]中的每一个元素依次绑定到n 566 | (println n)) 567 | ;; 1 568 | ;; 2 569 | ;; 3 570 | ;=> nil 571 | ``` 572 | 573 | 下面是功能相同的 java 代码: 574 | 575 | ```clojure 576 | List nums = Arrays.asList(1, 2, 3); 577 | for (int num : nums) { 578 | System.out.println(num); 579 | } 580 | ``` 581 | 582 | doseq 还能对多个结构进行遍历: 583 | 584 | ```clojure 585 | (doseq [m [1 2], n [3 4]] 586 | (println 587 | (str m " + " n " = " (+ m n)))) 588 | ;;1 + 3 = 4 589 | ;;1 + 4 = 5 590 | ;;2 + 3 = 5 591 | ;;2 + 4 = 6 592 | ;=> nil 593 | ``` 594 | 595 | 上述例子清晰的展示了 doseq 是如何对两个向量进行遍历的。以下是等效的 java 代码: 596 | 597 | ```java 598 | List nums1 = Arrays.asList(1, 2); 599 | List nums2 = Arrays.asList(3, 4); 600 | for (int m : nums1) { 601 | for (int n : nums2) { 602 | String tmp = m + " + " + n + " = " + (m + n); 603 | System.out.println(tmp); 604 | } 605 | } 606 | ``` 607 | 608 | for 的功能比 doseq 更为强大,它拥有遍历、过滤、变换等多种功能。 609 | 610 | ```clojure 611 | ; 求笛卡尔积 612 | (for [m [1 2], n [\a \b]] [m n]) 613 | ;=> ([1 \a] [1 \b] [2 \a] [2 \b]) 614 | ; 变换 615 | (for [num [1 2 3 4]] (inc num)) 616 | ;=> (2 3 4 5) 617 | ;过滤出偶数 618 | (for [n (range 0 10) :when (even? n)] n) 619 | ;=> (0 2 4 6 8) 620 | ``` 621 | 622 | ## 遍历映射表 623 | 624 | 对映射表进行遍历时,会将映射表转化为二维序列: 625 | 626 | ```clojure 627 | (seq me) 628 | ([:name "Khellendros"] [:age 22] [:gender :male]) 629 | ``` 630 | 631 | 因此我们可以像操作普通二维序列一样遍历映射表。 632 | 633 | 需要注意的是,Clojure 映射表的默认实现是哈希表,因此元素的遍历顺序是无法预期的。 634 | 635 | ## 与 Java 互操作 636 | 637 | 在 Clojure 中可以使用已有的 java 类库,包括调用静态方法,构造类实例对象,调用方法,读取、设置字段值等。 638 | 639 | ## 调用静态方法/静态字段 640 | 641 | 可以用 _(类名/静态方法名 [方法参数…])_ 的形式调用静态方法和静态字段: 642 | 643 | ```clojure 644 | (Math/PI) 645 | ;=> 3.141592653589793 646 | (Math/abs -1) 647 | ;=> 1 648 | ``` 649 | 650 | ## 构造实例对象 651 | 652 | _(类名. [构造方法参数…])_ 或者 _(new 类名 [构造方法参数…])_ 可以构造实例对象: 653 | 654 | ```clojure 655 | (def nums (java.util.ArrayList.)) 656 | ;(def nums (new java.util.ArrayList)) 657 | ;=> #’user/nums 658 | nums 659 | ;=> [] 660 | ``` 661 | 662 | ## 方法调用 663 | 664 | 使用 _(.方法名 对象 [方法参数…])_ 调用方法: 665 | 666 | ```clojure 667 | (.add nums 1) 668 | ;=> true 669 | nums 670 | ;=> [1] 671 | ``` 672 | 673 | ## 读取、设置字段 674 | 675 | 读取字段的方法是 _(.-字段名 对象)_: 676 | 677 | ```clojure 678 | (def point (java.awt.Point. 0 1)) 679 | ;=> #’user/point 680 | (.-x point) 681 | ;=> 0 682 | ``` 683 | 684 | 使用 set!函数可以设置字段值。 685 | 686 | ```clojure 687 | (set! (.-x point) 2) 688 | ;=> 2 689 | (.-x point) 690 | ;=> 2 691 | ``` 692 | 693 | _注:在 Clojure 中,以!结尾的函数往往意味着其会带来副作用(比如 set!会改变对象的属性)。我们应该审慎的使用这些函数。_ 694 | 695 | ## 小试牛刀:index-of 函数 696 | 697 | 我们上面提到 contains?函数对向量无效,我们不妨自己实现一个功能类似的函数 index-of。 698 | 699 | index-of 函数使用起来应该是这样的: 700 | 701 | ```clojure 702 | (index-of [1 1 4 5 1 4] 4) 703 | ;=> (2 5) 704 | (index-of [1 1 4 5 1 4] 0) 705 | ;=> () 706 | ``` 707 | 708 | 如果序列内包含我们想要查找的目标,index-of 会返回由所有匹配项的下表组成的序列。如果没有找到则返回空序列。 709 | 710 | 首先,我们需要将列表项与其对应的下标关联在一起。还记得我们之前定义的 natural-nums 吗?把它和向量结合起来就可以了: 711 | 712 | ```clojure 713 | (defn indexed-vec [vec] 714 | (map vector natural-nums vec)) 715 | ;=> #’user/indexed-vec 716 | (indexed-vec [\a \b \c \d]) 717 | ;=> ([0 \a] [1 \b] [2 \c] [3 \d]) 718 | ``` 719 | 720 | map 函数用于将一个函数应用到一个序列的每一项上,例如: 721 | 722 | ```clojure 723 | (map inc [1 2 3]) 724 | ;=> (2 3 4) 725 | ``` 726 | 727 | 如果传递给 map 两个序列,那么它就可以通过一个二元函数将两个序列结合起来: 728 | 729 | ```clojure 730 | (map + [1 2 3] [3 2 1]) 731 | ;=> (4 4 4) 732 | ``` 733 | 734 | 同理,如果传入 3 个序列就要使用一个三元函数,以此类推。如果两个序列的长度不一致,较长的序列会被“截断”。 735 | 736 | 而 vector 函数的作用自然是构造一个向量,因此,(map vector natural-nums vec)就会把一个自然数序列(代表下标)和 vec 像拉链一样“拉”在一起。现在向量已经和下标关联起来了,我们就可以使用 for 简单的实现 index-of 了。 737 | 738 | ```clojure 739 | (defn index-of [vec item] 740 | (for [[index value] (indexed-vec vec) :when (= value item)] index) ) 741 | ;=> #’user/index-of 742 | (index-of [1 1 4 5 1 4] 4) 743 | ;=> (2 5) 744 | (index-of [1 1 4 5 1 4] 0) 745 | ;=> () 746 | ``` 747 | 748 | ## 更进一步? 749 | 750 | index-of 工作的很好,但还不够通用。如果对映射表、集合都能用通用的接口进行调用就好了。 751 | 752 | pos 函数(通用版本的 index-of)使用起来应该是这样的: 753 | 754 | ```clojure 755 | (pos [1 2 3 1] 1) ;=> (0 3) 756 | (pos {:a 1, :b 2, :c 1} 1) ;=>(:a :c) 757 | (pos #{1 2 3} 0) ;=> () 758 | ``` 759 | 760 | 要做到这一点,首先我们需要定义一个通用版本的 indexed: 761 | 762 | ```clojure 763 | (defn indexed [xs] 764 | (cond 765 | (map? xs) (seq xs) 766 | (set? xs) (seq xs) 767 | :else (indexed-vec xs))) 768 | ``` 769 | 770 | pos 相较于 index-of 只是把 indexed-vec 换成了 indexed: 771 | 772 | ```clojure 773 | (defn pos [xs item] 774 | (for [[index value] (indexed xs) :when (= item value)] index)) 775 | ``` 776 | 777 | 再进一步,我们完全可以把 item 参数换成一个判断函数。来看看这个最终版本的 pos-if 吧: 778 | 779 | ```clojure 780 | (defn pos-if [xs pred] 781 | (for [[index value] (indexed xs) :when (pred value)] index)) 782 | ;=> #’user/pos-if 783 | (pos-if [1 2 3 1] #(> %1 1)) 784 | ;=> (1 2) 785 | ``` 786 | --------------------------------------------------------------------------------