clojure - 宏观构成
问题描述
我正在玩一些 Clojure 宏,特别是尝试了 map 的宏版本,我想象中将其命名为“mmap”:
(defmacro mmap [f coll]
`(vector ~@(map #(list f %) coll)))
请不要深究我这样做的原因。这只是一个最小的可重现示例。
只要将集合传递给它,它就可以正常工作,但是当我尝试用另一个宏组合它时,mmap 用宏本身而不是它的扩展来切片和切块 sexp。
我希望“mmap”像这样工作:
(mmap inc [1 2 3 4]) => [2 3 4 5]
(mmap dec (mmap inc [1 2 3 4])) => [1 2 3 4] NOPE. See the edit.
所以问题是:有没有办法强制扩展作为参数传递给另一个宏的宏?在这种情况下,怎么做?
感谢您的任何见解!
编辑我只是注意到我的示例没有意义,传递 mmap 宏的结果就像调用:
(mmap inc (vector 1 2 3 4))
在这种特定情况下这没有任何意义,但我仍然想知道它是否可以以某种方式完成
EDIT2使用 [1 2 3 4] 数组而不是符号,例如“arg”是为了传达整个事情是在编译时完成的,而不是运行时。
解决方案
函数总是在调用函数之前计算它们的参数,导致表达式以熟悉的“由内而外”模式进行计算:
(/ (inc 3) (dec 3)) ; orig
(/ 4 2) ; args sent to `/` function
2 ; final result
宏的目的是避免在调用函数之前计算参数,从而导致表达式被“从外向内”计算。这允许人们编写宏,就好像每个宏都是一种新的语言功能:
(ns tst.demo.core
(:use demo.core tupelo.core tupelo.test))
(defmacro infix
[[l op r]]
`(~op ~l ~r))
(defmacro snoop
[& forms]
`(let [result# ~@forms]
(do
(println "snoop => " result#)
result#)))
(dotest
(spyx (infix (2 + 3)))
(spyx (infix (5 - 3)))
(newline)
(snoop (+ 1 2))
(snoop (infix (4 + 5)))
(newline)
(println :last
(infix ((snoop 4) + (snoop 5)))))
结果
(infix (2 + 3)) => 5
(infix (5 - 3)) => 2
snoop => 3
snoop => 9
snoop => 4
snoop => 5
:last 9
所以最后一个例子有一个中间阶段,看起来像:
(println :last
(+ (snoop 4) (snoop 5)))
在哪里可以看到在 2 次调用infix
之前已经评估过。snoop
这正是宏的强大之处,没有它它们将毫无用处。
您也许可以创建一个新的宏,其工作方式如下:
(defmacro mmap
[fns coll]
`(let [comp-fn# (comp ~@fns)]
(mapv comp-fn# ~coll)))
(mmap [inc inc inc] [1 2 3]) => [4 5 6]
虽然上述mmap
方法有效,但它确实是对宏系统的滥用,如果你给它提供编译时常量以外的东西,它可能会失败。
基本函数的更好替代方案是对普通函数的简单记忆:
(def do-stuff
(memoize ; will only execute on the first usage
(fn
[coll]
(mapv #(* 2 %) (mapv inc coll)))))
(do-stuff [1 2 3 4]) => [4 6 8 10]
如果您认为即使在运行时的单个调用也太多了,我建议在预编译步骤中手动运行“类宏”代码并将结果保存在新的“源文件”中,然后可以像往常一样编译(也许例如,您正在预先计算一堆素数)。
结果是宏系统仅用于操作代码以添加新的语言功能。使用不同的技术可以更好地实现任何其他目标。
关于mapv
我也是mapv
偶然发现的,而且是在几个月后才发现的。
请务必为Clojure CheatSheet添加书签,并始终保持浏览器选项卡对其打开。定期研究它,直到你能记住每个功能。:)
另请注意,那里没有列出一些更晦涩的项目!
更新:
宏的“杀手级功能”是添加新的语言功能。在 Java 等中,只能添加新库。例如,上面的 spyx 是一个与 snoop 示例非常相似的宏。有关其他示例,请参阅Tupelo 库中的with-exception-default、vals->map 和 it-> 。事实上,许多“核心”Clojure 特性,如 for 表达式或 and & 或逻辑运算符实际上是任何应用程序都可以编写(或修改!)的宏。
推荐阅读
- python - Pandas drop row santax 问题
- reactjs - 如何配置 Expo 推送令牌以通过单击同时向多个设备发送通知
- javascript - 如何在javascript中的[[],[],[]]数组中简单地查找匹配值
- visual-build-professional - 如何用正则表达式替换文本文件?
- sql - 使用联合优化 SQL 选择
- locking - 在这种情况下,如何处理 Cloud Storage 或 Minio Storage 中等待的读/写请求?
- c# - 如何将类序列化为 JSON,其中属性类型为 Windows.Data.Json.JsonObject?
- r - 在 ggplot 中,如何用正值和负值记录比例?
- javascript - VUE 3我如何检查元素何时在视口中?
- c# - 依赖其他项目的 DLL 时,如何在 APP Center 中执行 Xamarin UI 测试?