Clojure,你成功引起了我的注意
应该是在2012年,我还是个Haskell的粉丝,看过一些lisp的入门(因为使用emacs,得写些elisp配置文件),对lisp有这个语言括号很多而且性能很差的认知。当时在YouTube上看一个clojure的视频
视频里的人说:
如果你已经会编程了,这不是你的优势,而只会是障碍,你需要改掉一些坏毛病
我去...哥也是博通n种语言的人,c、c++、Java、python之类都是老伙计了。
我们会学习Clojure,一门lisp
语气神态都让我想起圣经上说“你不可有别的神”,记得一开始他写了个递归
(defn fact [x]
(if (zero? x)
1
(*' x (fact (dec x)))))
; 注意(clojure.core/*')是一个函数支持自动升级,integer溢出后自动升级为BigInteger
我心里说:”这玩意性能很差呀,而且可会栈溢出”,果然他接着用substitution model展开了这段执行代码
由于Clojure是基于immutable data structure的,可能执行起来是这样的:
(fact 5)
(* 5 (fact 4))
(* 5 (* 4 (fact 3)))
(* 5 (* 4 (* 3 (fact 2))))
(* 5 (* 4 (* 3 (* 2 (fact 1)))))
(* 5 (* 4 (* 3 (* 2 (* 1 (fact 0))))))
(* 5 (* 4 (* 3 (* 2 (* 1 1)))))
(* 5 (* 4 (* 3 (* 2 1))))
(* 5 (* 4 (* 3 2)))
(* 5 (* 4 6))
(* 5 24)
120
这个递归的过程,每一步都依赖下一步的结果,的确性能不高
这个就有点意思了,一般lisp由于递归都性能不高这个事实大家都略过不提,这个问题是可解决的?
clojure 可以用recur实现尾递归
(defn fact [x]
(loop [accum 1
n x]
(if (= n 1)
accum
(recur (*' accum n) (dec n)))))
所以它的执行是这样的:
(fact 5)
(iter 5 1)
(iter 4 5)
(iter 3 20)
(iter 2 60)
(iter 1 120)
(iter 0 120)
120
请大家注意,在这个过程中,我们并没有一堆等待处理的压栈,每一步计算得到的accm都是下一步的结果,直到第n步,n等于1的时候,我们就得到最终结果
这个其实挺自然的,除了‘没有压栈’这部分,不论是谁设计的这个语言,至少他/她把新能放在心上。
至此,虽然我还有疑问,这个语言成功的引起了我的注意,接下来,他介绍了函数式编程中高阶函数(参数是函数的函数)的概念,这个对我并不陌生,但是他的介绍角度很新颖。
有oo编程背景的人一般会有这样的疑问:你不能调用一个还没写的函数,如果写了,干嘛不去直接调用,把函数作为参数这件事情显得有点多余。
结下来他用微积分举了个例子
对于任意的函数的导数
$$D f(x) = f'(x) = \frac {f(x + d(x)) -f(x)} {dx}$$
我们可以做如下定义:
(def dx .0001)
(defn deriv [f]
(fn [x]
(/ (- (f (+ x dx)) (f x))
dx)))
(defn cube [x]
(* x x x ))
运行的结果:
user> ((deriv cube ) 10)
300.0030000089282
user> ((deriv cube ) 2)
12.000600010022566
user> ((deriv cube ) 3)
27.00090001006572
user> ((deriv cube ) 4)
48.00120000993502
大学的导数公式还没全忘的话,你会发现这个值很接近 $3x^2$, 哇!我可以对任意的数学公式进行求导了
事后我真的去进行了一番验证,没栈溢出。
- 性能?这个语言设计中对性能是有充分考量的
- 难懂?它能用四行代码表达微积分的精髓
Clojure,你成功引起了我的注意!