9

Clojure The Mini Book

 3 years ago
source link: https://blog.oyanglul.us/clojure/clojure-the-mini-book
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

Clojure The Mini Book

Table of Contents

我每天用括号当早饭

为什么要学习全是括号的语言

选择Clojure是因为

  • 专门为多线程并发编程设计
  • 跑在JVM上,使部署变得简单
  • lisp语法太简单了,函数,函数,都是函数
  • 动态类型,更灵活
  • 数据结构都是Immutable,mutable is evil
  • 与Java交互
  • 丰富的第三方库

lisp 是好东西

上世纪50年代的就有了lisp语言,都不能说它不是一门语言,因为他太多种方言了。虽然一直都不温不火,但是随着系统逻辑和计算越来越复杂,再加上分布式和并行计算。人们突然发现函数式是一个好东西,state is evil。目前比较流行的lisp方言是Clojure,Scheme。

函数式是好东西

OO并没有想象中的好,带状态和mutable的代码特别难推理,非常难读。需要特别多的上下文才能推理当前属于哪种状态,有哪些行为。如果再加上多线程,那就更难推理代码的行为了。

多态是好东西

OO的多态的概念倒是有趣的好东西。一个函数在不同类型的参数能有不同的行为,使得我们的能够更灵活的建立抽象。

多线程是好东西如果用的对

  • Immutablility 减少了很多多线程带来的问题
  • 加锁只会阻塞并使事情更复杂,Clojure用更妙的方式解决资源共享问题。

TODO 搭建环境

首先得有一个管理依赖的玩意,如Ruby的bundler,python的pip,js的npm。clojure用leiningen。

如果你用mac,简单的用brew安装leiningen

brew install leiningen

clojure的编辑器我推荐使用emacs,如果你觉得emacs学习曲线太陡峭,那么light table是个不错的选择。

来试试不一样的Clojure数据结构

Number

Cojure支持全面的数字类型,甚至包括分数。

1/2

String

字符串只能用双引号定义哦,字符串的连接不再是加号,而是str

(str "What's your name? " "I'm fine! " "thank you! " "and you?")

Vector

向量是indexed的集合,用方括号初始化

[1 2 3 4]
(vector 1 2 3 4)

由于动态类型,还支持向量内的元素可以是任何类型

(get [1 "2" {3 "4"}] 2)

和vector类似,但是却稍微不同

'(1 2 3 4)
(list 1 2 3 4)

但是取元素的时候就和vector有所不同了

(nth '(1 2 3 4) 2)

集合也一样,元素类型可以随意

#{"1" 2 :3}
(set ["1" 2 :3 :3])

Keyword

慢着,刚刚的 :3 是个什么玩意

没错,如果你用过ruby,基本上时一个东西,但是可以是任何字母,数字,符号,甚至包括unicode,比如emoji

没有错了,那么我们其实是可以用中文和可爱的emoji编程的,虽然有点杀马特

:abc
:34
:>_<b
:你好
:😱

map 非常简单,就像将键值对写在list里,不过需要用花括号

跟其他语言不一样的是key可以是任何东西,甚至是list都可以作为key

{:smile 😀}
(get-in {:first-name "NiMa" :last-name "Wang" :属性 {:颜值 0 :吐槽能量 100 }} [:属性 :颜值])

get-in 通过一个path数组来找到深度的某个值。

lisp专用的 ' 引号

如果你觉得前面这些其实其他语言都有的话,那么你可能没有注意到在介绍list时有这样一个不起眼的玩意 '。

这是什么啊?具体是什么可能需要专门的篇幅来介绍,但是这里我可以解释它大概是神马。

如果在lisp里面见到单引号,那么你完全可以理解成literally后面那个东西,什么意思呢。

(let [男神 '(王尼玛 王大锤 张全蛋) 女神 '(孔连顺)]
  (first 男神);=> 王尼玛
  (first 女神);=> 孔连顺
  (first ['男神 '女神]);=>男神
  )

可以看到 男神 女神 都是list,但是如果在他们前面加个单引号后,他们就变成了字面的值,他们符号本身,而不会被eval成一个list。

所以由于lisp里面所有的 () 括号都是list,但是他们是会被eval的list,他们的会返回eval后的值,但是如果在前面加上单引号,他们返回他们本身,list,不会被eval。

反引号 `

Special Forms

def 创建一个全局的绑定

(def a-symbol 'init)

不管是在哪里(甚至是thread里)调用 def 都会创建成全局绑定

let关键字非常有意思,在其他语言如js里虽然没有这个关键字,但是功能大致可以翻译成

(function(男神,女神){
  男神[0]
  女神[0]
}).call(this, ['王尼玛','王大锤','张全蛋'],['孔连顺'])

但是js里面很少这么干,不是么。我们通常会直接。

var 男神=['王尼玛','王大锤','张全蛋'],女神=['孔连顺'];
男神[0];
女神[0];

var 有什么区别。当然就是scope不一样,前例中函数内部的 男神 女神 两个值的绑定不会受到函数外的影响,同样也不会对外界造成任何影响。

var 男神='葫芦娃';
  (function(男神,女神){
    男神[0]; //=> 王尼玛
  }).call(this, ['王尼玛','王大锤','张全蛋'],['孔连顺'])
男神; //=> 葫芦娃

所以 let 理解成一个函数, binding其实就是参数

clojure没有statement, 全是表达式, 有了do, 可以像statement一样按顺序 eval 表达式, 返回最后一个.

loop recur

clojure的数据结构都是immutable的,意味着你(如果不用macro的话)不能像其他语言一样写for循环,也不能像其他语言这样这样的…

var 男神=['王尼玛','王大锤','张全蛋']
男神[0]='葫芦娃'
男神 // => ['葫芦娃','王大锤','张全蛋']

后一种好解决,大不了创建一个新的 男神 但是for循环怎么搞?我又不能改变一个值.

var sum=0;
for(var i=0; i<10;i++)
  sum+=i

在函数式语言中,循环和遍历都必须要通过递归来实现呢。也就是我不能改变值,但是我能利用函数递归调用重新绑定参数

而在clojure中,写一个递归是如此的简单。

(do
  (defn sum-to-10 [sum i]
    (if (> i 10) 
      sum
      (recur (+ sum i) (inc i))))
  (sum-to-10 0 0))

还有更简单的, 不需要定义函数的递归, 更像for循环

(loop [sum 0 i 0]
  (if (> i 10)
    sum
    (recur (+ sum i) (inc i))))

recur总是会递归到离它最近的 loop 或者函数

完全可以吧 loop 理解成递归版本的 let 函数, 用起来跟 let 一模一样

code? data?

list 是数据, 但是他是可以eval的数据, eval的过程中第一个元素就变成了函数, 啊哈哈哈, 甚至是加减乘除. 比如 (+ 1 2), 你可能觉得读着别扭. 但是如果

(+ 1 2 3 4 5)

所以list是可以执行的, list 也是代码, 因此 lisp 叫做 list processing 语言.

因此在 lisp 语言里, 数据即代码, 代码也即数据. 而这样的 list 也就是著名的 s-expression

是不是感觉到头晕了, 来看看 clojure 到底是怎么做到的.

  1. expand macro
  2. eval list 中的每一个元素
  3. 用第一个元素作为函数, 后边所有元素作为参数

    ((or nil +) 1 2 (+ 3 4))
    ; => (+ 1 2 7)
    

第一部 expand macro 我们到后面macro的时候讨论

Reader

还记得搭建环境是提到的 REPL 吗? 也就是 Read Eval Print Loop

正常的Clojure程序的运行只经过前两个步骤, Read 和 Eval. 因此我们可以理解

  1. 有一个Reader去读取list
  2. 生成对应的clojure数据结构
  3. 扔给Evaluator
  4. Evaluator对其求值

Reader的工作有些像JavaScript的 JSON.parse, 读取json, 转换成JavaScript对象.

Macro

有了Reader, 在eval之前clojure还可以再作一些工作 – macro

macro 可以扩展一个 form 成另一种 form, 比如 when macro

(macroexpand '(when (> 1 2) (println "you suck")))

TODO Functional Programming

TODO Collection

TODO Concurrency

TODO 多态


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK