为了理解复杂的表达式和对它的操作,一个首要的前提就是理解”前缀表达式“。这可能会花费你一点时间来习惯它。不过我相信你会很快的爱上这种规则的。你想想,如果你要对多个值进行同一种运算,你只用写一个运算符在第一个值的最前面,而不是写多个运算符在中间。不信就看下面的例子:
普通: 1 + 2 + 3 + 4 + 5 + 6 + 8 + 9
前缀: + 1 2 3 4 5 6 7 8 9
抛开前缀表示法不说,一个复杂表达式可以看出是一个单独的操作,或者是一组操作。它们既可以接收参数,也能向外输出,在整个表达式计算完成的时候返回计算结果。一句话,复杂的表达式可以做任何你想让它做的事情。让我们看一个非常简单的例子:
(+ 1 2)
在对这个式子求值前,我们先去看一看clojure在求值期间会做哪些工作:
首先,clojure会遇到左括号,表明这是一个列表(表达式的形式)的开始。这个列表包含了它要处理的所有东西。clojure永远会把左括号的第一个元素当做是一个操作符(这也是采用前缀表达式的原因),这种方式使得lisp方言语法异常的简单。在我们的例子中,操作符指的就是一个函数。(函数其实可以看出是针对其参数需要做哪些操作的说明)。当clojure遇到一个右括号,表明这个列表的结束。这种解析是递归的,因为列表中的元素依然可能是列表。这种表达方式还有另一个名字:S表达式。(lisp 的含义就是 list processor 即列表处理)。
S表达式看起来可能很直观,但对它的理解是非常非常重要的。这是学习一切lisp方言的基础。Clojure在执行函数(例如+)之前,首先会对其所有的参数进行顺序(从左到右)求值并返回自己的结果。然后函数会针对这些参数的返回结果进行相应的求值运算。返回最终的结果。
上面例子中,”+"是函数,参数都是数字字面值。我们说过字面值求值后返回自己。所以整个操作就是将“+”运用到1和2之上。得到的最终结果就是3。再考虑一下下面这个例子
(+ 2 (- 8 3))
上面例子稍微复杂了一点,因为第二个参数不是单纯的字面值。这个求值也非常简单,clojure在对第二个参数(- 8 3)进行求值时采取的依然是之前的策略,得到结果5。最终 "+"会作用在2和5之上,最终结果返回7。所以我们说这种解析方式是递归的。S表达式规则虽然简单,但是真的是变化无穷啊。
再来看一个例子(检测对前缀表达式的理解):
=>(- 8 3 2 1 -6 34 12 4 2 6 4 -23 12 4)
-47
了解完上面,接下来做点啥呢?运行点例子也许是个好主意,但是还有更好的方法。让我们看一看内置函数的源码来了解一下。查看某个函数的源码很简单,还是调用函数来做。我们会使用一个名为“source”的函数:(source 【函数名】)
让我们查看函数'-'(减号)的源码
=>(source -) ;;依然是S表达式
(defn -
"If no ys are supplied, returns the negation of x, else subtracts
the ys from x and returns the result."
{:inline (fn [& args] `(. clojure.lang.Numbers (minus ~ @args)))
:inline-arities #{1 2}
:added "1.0"}
([x] (. clojure.lang.Numbers (minus x)))
([x y] (. clojure.lang.Numbers (minus x y)))
([x y & more]
(reduce - (- x y) more)))
nil
这个比预想的要多的多了,一个减号居然有这么多代码量。现在不用担心不理解上面的代码。因为这里面包含了很多未知的知识。我们只用知道一点是,我们的”-“可以操作任意个参数的能力,而这一切归功一个叫做”reduce“的函数。我们不用去调用”reduce“的源码,只用看看相关描述即可。我们可以使用”doc“函数:
=>(doc reduce)
-------------------------
clojure.core/reduce
([f coll] [f val coll])
f should be a function of 2 arguments. If val is not supplied,
returns the result of applying f to the first 2 items in coll, then
applying f to that result and the 3rd item, etc. If coll contains no
items, f must accept no arguments as well, and reduce returns the
result of calling f with no arguments. If coll has only 1 item, it
is returned and f is not called. If val is supplied, returns the
result of applying f to val and the first item in coll, then
applying f to that result and the 2nd item, etc. If coll contains no
items, returns val and f is not called.
nil
这里的reduce和python中的reduce函数含义是一样。