谈到不可变性,我们做个游戏。统计在座的一共有多少人数。我们都知道从某个人开始依次报数,最后得到的数字就是总人数,其实这就是一种不可变计算的游戏,为什么这么说呢?因为报数其实一个计算的过程,第一个人计算出1这个数,传递给第二个人。然后第二个人拿着前面的1进行加一操作,然后把结果2传递给后面的人做加法,以此类推。为了提高统计的效率,我也可以进行分组,然后每组自行报数,最后统计结果。但是如果我在白板上写个数字1,然后让大家来过来该这个数字,很大可能会出现错误,因为这个数字成为了竞态条件。在多并发的情况下,就得用读写锁来控制。所以不可变性特别利于并发。
好了,现在我们有个新的需求,设计一个不可变列表收集大家的名字。每个节点存储一个姓名的字符串,并且有个指针指向下一个节点。但是这也打破了列表的不可变性。怎么办?我们可以把新的节点指向旧有的列表,然后返回一个新的列表。这就是不可变列表实现的机制。随便一提,这也是区块链不可变特征的由来。

Clojure的创造者Rich Hickey扩展了Ideal Hash Tree数据结构,实现了Persistent Vector。由于此处的叶子节点可以扩展成32个,所以可以大量存储数据。利用Ideal Hash Tree的特点可以快速索引出数据,与此同时,数据的“增删改”也能做到近常数化的时间,并且总是产生新的数据结构替换原有的数据结构,即一种不可变的链式存储结构。
可计算很大问题就是得实现递归功能。
(defn reverse-seq [coll]
(when-let [elem (first coll)]
(concat (reverse-seq (rest coll)) [elem])))
(reverse-seq [1 2 3]) ; -> (3 2 1)
和循环无异的尾递归
(defn gcd [& nums]
(reduce #(if (zero? %2)
%
(recur %2 (mod % %2))) nums))
(gcd 8 16) ; -> 8
生成式测试会基于输入假设输出,并且生成许多可能的数据验证假设的正确性。
(defn add [a b]
(+ a b))
;; 任取两个整数,把a和b加起来的结果减去a总会得到b。
(def test-add
(prop/for-all [a (gen/int)
b (gen/int)]
(= (- (add a b) a) b)))
(tc/quick-check 100 test-add)
; -> {:result true, :num-tests 100, :seed 1515935038284}
测试结果表明,刚才运行了100组测试,并且都通过了。理论上,程序可以生成无数的测试数据来验证add方法的正确性。java输入字符串即便不能穷尽,我们也获得一组统计上的数字,而不仅仅是几个纯手工挑选的用例。
推荐:Scala的函数式编程
[Scala的函数式编程]
抽取共性,封装细节,忘记不重要的差异点。这样的好处是可以做到局部化影响和延迟决策。
命名就是一种抽象,重构中最重要的技法就是重命名和提取小函数
(* 3 3 3) (* x x x) (* y y y) -> (defn cube [x] (* x x x))
例如:我们定义数对 pair
本文来自电脑杂谈,转载请注明本文网址:
http://www.pc-fly.com/a/jisuanjixue/article-83096-4.html
#杨洋icon##杨洋2015金投赏#杨洋金投赏