翻译自 juliangamble - The Little Schemer in Clojure – Chapter 7 Shadows
这是 The Little Schemer to Clojure 的第七章
注,后文讲用 TLS 代替 The Little Schemer
这章开始我们主要处理数字计算。建立一个表达式,分解,然后求出他的值。
首先,需要一个函数用来检测输入的是否是一个数字。这个函数名为 numbered
。但是首先我们将扩展 Clojure 自带的 number?
1 2 3 4 5 6 7 8 9 10 11 12
| (def number_? (fn [a] (cond (null? a) false (number? a) true true false)))
(println (number_? 3))
(println (number_? 'a))
|
numbered
用来检验是否是数学表达式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| (def numbered? (fn [aexp] (cond (atom? aexp) (number_? aexp) (= (first (rest aexp)) '+) (and (numbered? (first aexp)) (numbered? (first (rest (rest aexp))))) (= (first (rest aexp)) '*) (and (numbered? (first aexp)) (numbered? (first (rest (rest aexp))))) (= (first (rest aexp)) 'exp) (and (numbered? (first aexp)) (numbered? (first (rest (rest aexp))))) true false)))
(println (numbered? '(a b c)))
(println (numbered? '(3 + (4 * 5))))
(println (numbered? '(3 a (4 * 5))))
|
简化 numbered?
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| (def numbered? (fn [aexp] (cond (atom? aexp) (number_? aexp) true (and (numbered? (first aexp)) (numbered? (first (rest (rest aexp))))))))
(println (numbered? '(a b c)))
(println (numbered? '(3 + (4 * 5))))
(println (numbered? '(3 a (4 * 5))))
|
注意,在这里最后一个表达式返回的是 true ,但是之前是false。 也就是说我们是基于知道输入是一个数学表达式而写的这个函数,是不正确的。
现在看第八戒律。 这里你将递归所有的子部分包括
所有list里面的 sublists
所有数学表达式里面的子表达式
我们将对数据和代码做区分。处理数据的函数递归数据。处理代码的函数递归代码。
现在你将想到一个名声狼藉的函数 eval
。我们将写 eval 的第一个版本的实现,用于解释数学表达式。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| (use 'clojure.math.numeric-tower)
(def value (fn [aexp] (cond (number_? aexp) aexp (= (first (rest aexp)) '+) (+ (value (first aexp)) (value (first (rest (rest aexp))))) (= (first (rest aexp)) '*) (* (value (first aexp)) (value (first (rest (rest aexp))))) (= (first (rest aexp)) 'exp) (expt (value (first aexp)) (value (first (rest (rest aexp))))))))
(println (value '(1 + 1)))
(println (value '(2 + 2)))
(println (value '(3 exp 3)))
(println (value '(4 b 4)))
|
现在将实现一个前缀符号的 value
实现
1 2 3 4 5 6 7
| (def value (fn [aexp] (cond (number_? aexp) aexp (= (first aexp) '+) (+ (value (rest aexp)) (value (rest (rest aexp)))) (= (first (rest aexp)) '*) (* (value (first aexp)) (value (first (rest (rest aexp))))) (= (first (rest aexp)) 'exp) (expt (value (first aexp)) (value (first (rest (rest aexp))))))))
|
现在我们将 提取数据
的函数和 提取operator
的函数提取出来。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| (def first-sub-exp (fn [aexp] (first (rest aexp))))
(def second-sub-exp (fn [aexp] (first (rest (rest aexp)))))
(def operator (fn [aexp] (first aexp)))
(def value (fn [aexp] (cond (number_? aexp) aexp (= (operator aexp) '+) (+ (value (first-sub-exp aexp)) (value (second-sub-exp aexp))) (= (operator aexp) '*) (* (value (first-sub-exp aexp)) (value (second-sub-exp aexp))) (= (operator aexp) 'exp) (expt (value (first-sub-exp aexp)) (value (second-sub-exp aexp))))))
(println (value '(+ 1 1)))
(println (value '(* 2 2)))
(println (value '(exp 3 3)))
(println (value '(b 4 4)))
|
现在来呈现以下 抽象的威力 - 回到中缀表达式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| (def first-sub-exp (fn [aexp] (first aexp)))
(def operator (fn [aexp] (first (rest aexp))))
(println (value '(1 + 1)))
(println (value '(2 * 2)))
(println (value '(3 exp 3)))
(println (value '(4 b 4)))
|
是时候将下一个戒律了 - 使用帮助函数抽象一些表达式
。 可以理解为提取方法。它有助于你为未来进行大范围的重构。
后文将重定义一个数学模型。
现在要写一个 null?
函数,用于测试,之所以重新定义,是为了让我们的api抽象到最简单。
1 2 3 4 5 6 7 8 9 10 11 12 13
| (def null? (fn [a] (or (nil? a) (= () a))))
(def null? (fn [s] (and (atom? s) (= s '()))))
|
我们将脱离原本的数学符建立自己的数学计算语法。
从 zero?
开始
1 2 3 4 5 6 7 8
| (def zero_? (fn [n] (null? n)))
(println (zero_? '()))
(println (zero_? 0))
|
也许没弄明白现在在干什么
举个例子。 现在 0 是 () , 1 是 (()), 2 是 (() ())
现在定义 add1
。
1 2 3 4 5 6 7 8
| (def add1 (fn [n] (cons '() n)))
(println (add1 '()))
(println (add1 '(())))
|
sub1
1 2 3 4 5 6 7 8 9 10
| (def sub1 (fn [n] (rest n)))
(println (sub1 '(()())))
(println (sub1 '(())))
(println (sub1 '()))
|
定义加法
1 2 3 4 5 6 7 8 9 10 11
| (def +_ (fn [n m] (cond (zero_? m) n true (add1 (+_ n (sub1 m))))))
(println (+_ '() '()))
(println (+_ '(()) '(())))
|
检验是否是数字
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| (def number_? (fn [n] (cond (null? n) true true (and (null? (first n)) (number_? (rest n))))))
(println (number_? '() ))
(println (number_? '(()) ))
(println (number_? '((())) ))
(println (number_? '(()()) ))
|
总结
为什么这章叫做shadows? 我也没弄明白 。。。(我觉得应该是后半部分的抽象吧,一样的接口不一样的实现)
这里我们以数学表达为基础,开始写简单的解释器了。我们相比之前仅仅增加了一个基础函数 expt
(我也没看懂expt是啥, 我理解应该就是次方的实现,但不懂为什么又造了个名字)