Clojure 对函数向量的迭代

Mr.*_*bot 1 clojure

我正在读一本关于 Clojure 的书,上面写着:

您可以使用映射做的另一件有趣的事情是向它传递函数集合。如果您想对不同的数字集合执行一组计算,则可以使用它,如下所示:

(def sum #(reduce + %))
(def avg #(/ (sum %) (count %)))
(defn stats
  [numbers]
  (map #(% numbers) [sum count avg]))

(stats [3 4 10])
; => (17 3 17/3)

(stats [80 1 44 13 6])
; => (144 5 144/5)
Run Code Online (Sandbox Code Playgroud)

在此示例中,stats 函数迭代函数向量,将每个函数应用于数字。

我觉得这很令人困惑,本书没有给出更多解释。

我知道%代表匿名函数中的参数,但我无法弄清楚它们在这个示例中代表什么值。是什么%

还有如何stats迭代countifcount嵌套在其中avg

Dav*_*fer 5

它有助于不要考虑“正在执行的代码”,而是考虑“正在减少的表达式树”。表达式树被重写,直到结果出现。当“实时函数”出现在列表的第一个位置时,符号将被替换为“它们代表什么”,并且函数将应用于其参数;如(some-function a b c). 这是从表达式树的顶部到叶子以自上而下的方式完成的,当quote遇到符号时停止。

\n\n

在下面的示例中,遗憾的是,我们无法标记已减少的内容和未减少的内容,因为不支持着色。请注意,缩减顺序不一定与 Clojure 编译器发出的编译代码实际执行的操作相对应。

\n\n

从...开始:

\n\n
(defn stats\n  [numbers]\n  (map #(% numbers) [sum count avg]))\n
Run Code Online (Sandbox Code Playgroud)\n\n

...我们会打电话stats

\n\n

第一个困难是stats可以将集合作为单个事物来调用:

\n\n
(stats [a0 a1 a2 ... an])\n
Run Code Online (Sandbox Code Playgroud)\n\n

或者可以使用一系列值来调用它:

\n\n
(stats a0 a1 a2 ... an)\n
Run Code Online (Sandbox Code Playgroud)\n\n

是哪一个?不幸的是,预期的调用风格只能通过查看函数定义来找到。在这种情况下,定义说

\n\n
(defn stats [numbers] ...\n
Run Code Online (Sandbox Code Playgroud)\n\n

这意味着stats需要一个名为numbers. 因此我们这样称呼它:

\n\n
(stats [3 4 10])\n
Run Code Online (Sandbox Code Playgroud)\n\n

现在减价开始了!作为参数的数字向量被归约到其自身,因为向量的每个元素都被归约并且数字被归约到其自身。该符号stats被简化为之前声明的函数。的定义stats实际上是:

\n\n
(fn [numbers] (map #(% numbers) [sum count avg]))\n
Run Code Online (Sandbox Code Playgroud)\n\n

...这有点被隐藏了defn简写隐藏了。因此

\n\n
(stats [3 4 10])\n
Run Code Online (Sandbox Code Playgroud)\n\n

变成

\n\n
((fn [numbers] (map #(% numbers) [sum count avg])) [3 4 10])\n
Run Code Online (Sandbox Code Playgroud)\n\n

接下来,减少fn表达式会产生一个参数的实时函数。让我们用 \xe2\x98\x85 标记实时函数,并使用数学箭头表示法:

\n\n
(\xe2\x98\x85(numbers \xe2\x9e\x9c (map #(% numbers) [sum count avg])) [3 4 10])\n
Run Code Online (Sandbox Code Playgroud)\n\n

实时函数位于列表的第一个位置,因此接下来将进行函数调用。函数调用包括numbers用参数替换出现的[3 4 10],并去掉整个表达式的外括号:

\n\n
(map #(% [3 4 10]) [sum count avg])\n
Run Code Online (Sandbox Code Playgroud)\n\n

符号map, sum, count,avg解析为已知的已定义函数,其中mapcount来自 Clojure 核心库,其余部分已在之前定义。再次,我们将它们标记为活动状态:

\n\n
(\xe2\x98\x85map #(% [3 4 10]) [\xe2\x98\x85sum \xe2\x98\x85count \xe2\x98\x85avg]))\n
Run Code Online (Sandbox Code Playgroud)\n\n

同样,该# %符号是函数接受一个参数并将其插入到%位置的简写,让我们明确这一点:

\n\n
(\xe2\x98\x85map (fn [x] (x [3 4 10])) [\xe2\x98\x85sum \xe2\x98\x85count \xe2\x98\x85avg]))\n
Run Code Online (Sandbox Code Playgroud)\n\n

减少fn表达式会产生一个参数的实时函数。再次,用 \xe2\x98\x85 进行标记,并使用数学箭头表示法:

\n\n
(\xe2\x98\x85map \xe2\x98\x85(x \xe2\x9e\x9c (x [3 4 10])) [\xe2\x98\x85sum \xe2\x98\x85count \xe2\x98\x85avg]))\n
Run Code Online (Sandbox Code Playgroud)\n\n

实时函数\xe2\x98\x85map位于头部位置,因此整个表达式根据以下规范进行简化map:将第一个参数(函数)应用于第二个参数(集合)的每个元素。我们可以假设首先创建集合,然后进一步评估集合成员,因此:

\n\n
[(\xe2\x98\x85(x \xe2\x9e\x9c (x [3 4 10])) \xe2\x98\x85sum)\n (\xe2\x98\x85(x \xe2\x9e\x9c (x [3 4 10])) \xe2\x98\x85count)\n (\xe2\x98\x85(x \xe2\x9e\x9c (x [3 4 10])) \xe2\x98\x85avg)]\n
Run Code Online (Sandbox Code Playgroud)\n\n

集合中的每个元素都可以进一步减少,因为每个元素都有一个包含 1 个位于头部位置的参数和一个可用参数的实时函数。因此,在每种情况下,x都被适当地替换为:

\n\n
[(\xe2\x98\x85sum [3 4 10])\n (\xe2\x98\x85count [3 4 10])\n (\xe2\x98\x85avg [3 4 10])]\n
Run Code Online (Sandbox Code Playgroud)\n\n

集合中的每个元素都可以进一步减少,因为每个元素在头位置都有一个包含 1 个参数的实时函数。练习继续:

\n\n
[ ((fn [x] (reduce + x)) [3 4 10])\n  (\xe2\x98\x85count [3 4 10])\n  ((fn [x] (/ (sum x) (count x))) [3 4 10])]\n
Run Code Online (Sandbox Code Playgroud)\n\n

然后

\n\n
[ (\xe2\x98\x85(x \xe2\x9e\x9c (reduce + x)) [3 4 10])\n  3\n  (\xe2\x98\x85(x \xe2\x9e\x9c (/ (sum x) (count x))) [3 4 10])]\n
Run Code Online (Sandbox Code Playgroud)\n\n

然后

\n\n
[ (reduce + [3 4 10])\n  3\n  (/ ((fn [x] (reduce + x)) [3 4 10]) (count [3 4 10]))]\n
Run Code Online (Sandbox Code Playgroud)\n\n

然后

\n\n
[ (\xe2\x98\x85reduce \xe2\x98\x85+ [3 4 10])\n  3\n  (/ (*(x \xe2\x9e\x9c (reduce + x)) [3 4 10]) (count [3 4 10]))]\n
Run Code Online (Sandbox Code Playgroud)\n\n

然后

\n\n
[ (\xe2\x98\x85+ (\xe2\x98\x85+ 3 4) 10)\n  3\n  (/ (reduce + [3 4 10]) (count [3 4 10]))]\n
Run Code Online (Sandbox Code Playgroud)\n\n

然后

\n\n
[ (\xe2\x98\x85+ 7 10)\n  3\n  (\xe2\x98\x85/ (\xe2\x98\x85reduce \xe2\x98\x85+ [3 4 10]) (\xe2\x98\x85count [3 4 10]))]\n
Run Code Online (Sandbox Code Playgroud)\n\n

然后

\n\n
[ 17\n  3\n  (\xe2\x98\x85/ 17 3)]\n
Run Code Online (Sandbox Code Playgroud)\n\n

最后

\n\n
[ 17\n  3\n  17/3]\n
Run Code Online (Sandbox Code Playgroud)\n\n

您还可以使用函数juxt。尝试(doc juxt)REPL:

\n\n
\n
clojure.core/juxt\n([f] [f g] [f g h] [f g h & fs])\n  Takes a set of functions and returns a fn that is the juxtaposition\n  of those fns.  The returned fn takes a variable number of args, and\n  returns a vector containing the result of applying each fn to the\n  args (left-to-right).\n  ((juxt a b c) x) => [(a x) (b x) (c x)]\n
Run Code Online (Sandbox Code Playgroud)\n
\n\n

让我们试试吧!

\n\n
(def sum #(reduce + %))\n(def avg #(/ (sum %) (count %)))\n((juxt sum count avg) [3 4 10])\n;=> [17 3 17/3]\n((juxt sum count avg) [80 1 44 13 6])\n;=> [144 5 144/5]\n
Run Code Online (Sandbox Code Playgroud)\n\n

因此我们可以定义stats

\n\n
(defn stats [numbers] ((juxt sum count avg) numbers))\n(stats [3 4 10])\n;=> [17 3 17/3]\n(stats [80 1 44 13 6])\n;=> [144 5 144/5]\n
Run Code Online (Sandbox Code Playgroud)\n\n

聚苯乙烯

\n\n

有时 Clojure 代码很难阅读,因为您不知道正在处理什么“东西”。标量、集合或函数没有特殊的语法标记,实际上集合可以显示为函数,或者标量可以是集合。与 Perl 相比,Perl 有表示法$scalar, @collection, %hashmapfunction但也$reference-to-stuff有 and$$scalarly-dereferenced-stuff@$collectionly-dereferenced-stuffand %$hashmapply-dereferenced-stuff)。

\n