不要考虑“正在执行的代码”,而是考虑“减少表达式树”会有所帮助。表达式树被重写,直到结果出现。当“实时函数”出现在列表的第一个位置时,符号被“它们所代表的”替换,并且函数被应用于它们的参数;如(some-function a b c)。这是从表达式树的顶部到叶子以自上而下的方式完成的,当遇到quote 符号时停止。
在下面的示例中,很遗憾,由于不支持着色,我们无法标记已减少的内容和未减少的内容。请注意,减少的顺序不一定与 Clojure 编译器发出的编译代码实际执行的操作相对应。
开始于:
(defn stats
[numbers]
(map #(% numbers) [sum count avg]))
...我们会打电话给stats。
第一个困难是stats 可以用集合作为一个单一的东西来调用:
(stats [a0 a1 a2 ... an])
或者它可以用一系列值来调用:
(stats a0 a1 a2 ... an)
它是什么?不幸的是,预期的调用风格只能通过查看函数定义来找到。在这种情况下,定义说
(defn stats [numbers] ...
这意味着stats 需要一个名为numbers 的东西。因此我们这样称呼它:
(stats [3 4 10])
现在开始减少!作为参数的数字向量被归约为自身,因为向量的每个元素都被归约并且数字归约为自身。符号stats 简化为之前声明的函数。 stats的定义其实是:
(fn [numbers] (map #(% numbers) [sum count avg]))
...defn 简写有点隐藏。因此
(stats [3 4 10])
变成
((fn [numbers] (map #(% numbers) [sum count avg])) [3 4 10])
接下来,减少fn 表达式会产生一个带有一个参数的实时函数。让我们用 ★ 标记实时函数,然后使用数学箭头符号:
(★(numbers ➜ (map #(% numbers) [sum count avg])) [3 4 10])
live 函数位于列表的第一个位置,因此随后会调用函数。函数调用包括将 numbers 的出现替换为实时函数主体中的参数 [3 4 10] 并剥离整个表达式的外括号:
(map #(% [3 4 10]) [sum count avg])
符号 map、sum、count、avg 解析为已知的、已定义的函数,其中 map 和 count 来自 Clojure 核心库,其余部分已在前面定义。同样,我们将它们标记为实时:
(★map #(% [3 4 10]) [★sum ★count ★avg]))
同样,# % 表示法是一个函数的简写,它接受一个参数并将其插入到 % 位置,让我们明确这一点:
(★map (fn [x] (x [3 4 10])) [★sum ★count ★avg]))
减少fn 表达式会产生一个参数的实时函数。同样,用 ★ 标记,让我们使用数学箭头符号:
(★map ★(x ➜ (x [3 4 10])) [★sum ★count ★avg]))
一个活动函数★map 处于头部位置,因此整个表达式根据map 的规范进行了缩减:将第一个参数(一个函数)应用于第二个参数(一个集合)的每个元素。我们可以假设先创建集合,然后进一步评估集合成员,所以:
[(★(x ➜ (x [3 4 10])) ★sum)
(★(x ➜ (x [3 4 10])) ★count)
(★(x ➜ (x [3 4 10])) ★avg)]
集合的每个元素都可以进一步减少,因为每个元素都有一个在头部位置有 1 个参数和一个可用参数的实时函数。因此,在每种情况下,x 都会被适当地替换:
[(★sum [3 4 10])
(★count [3 4 10])
(★avg [3 4 10])]
集合的每个元素都可以进一步减少,因为每个元素在头部位置都有一个 1 参数的实时函数。练习继续:
[ ((fn [x] (reduce + x)) [3 4 10])
(★count [3 4 10])
((fn [x] (/ (sum x) (count x))) [3 4 10])]
然后
[ (★(x ➜ (reduce + x)) [3 4 10])
3
(★(x ➜ (/ (sum x) (count x))) [3 4 10])]
然后
[ (reduce + [3 4 10])
3
(/ ((fn [x] (reduce + x)) [3 4 10]) (count [3 4 10]))]
然后
[ (★reduce ★+ [3 4 10])
3
(/ (*(x ➜ (reduce + x)) [3 4 10]) (count [3 4 10]))]
然后
[ (★+ (★+ 3 4) 10)
3
(/ (reduce + [3 4 10]) (count [3 4 10]))]
然后
[ (★+ 7 10)
3
(★/ (★reduce ★+ [3 4 10]) (★count [3 4 10]))]
然后
[ 17
3
(★/ 17 3)]
终于
[ 17
3
17/3]
您也可以使用函数juxt。在 REPL 上尝试(doc juxt):
clojure.core/juxt
([f] [f g] [f g h] [f g h & fs])
Takes a set of functions and returns a fn that is the juxtaposition
of those fns. The returned fn takes a variable number of args, and
returns a vector containing the result of applying each fn to the
args (left-to-right).
((juxt a b c) x) => [(a x) (b x) (c x)]
让我们试试吧!
(def sum #(reduce + %))
(def avg #(/ (sum %) (count %)))
((juxt sum count avg) [3 4 10])
;=> [17 3 17/3]
((juxt sum count avg) [80 1 44 13 6])
;=> [144 5 144/5]
因此我们可以将stats 定义为
(defn stats [numbers] ((juxt sum count avg) numbers))
(stats [3 4 10])
;=> [17 3 17/3]
(stats [80 1 44 13 6])
;=> [144 5 144/5]
附言
有时 Clojure 代码很难阅读,因为您不知道自己在处理什么“东西”。标量、集合或函数没有特殊的句法标记,实际上集合可以作为函数出现,或者标量可以是集合。与 Perl 比较,它有符号 $scalar、@collection、%hashmap、function,还有 $reference-to-stuff 和 $$scalarly-dereferenced-stuff 和 @$collectionly-dereferenced-stuff 和 %$hashmapply-dereferenced-stuff)。