Lisp / Clojure:编写函数生成宏是个好主意吗?
这个问题要求创建一个 Clojure 宏来生成多个函数。我们找到了一种方法来做到这一点,但仍被“这是一个好主意吗?”的问题所困扰。
我最初的反应是不完全,原因有两个:
- 您的代码中没有定义函数,这会使理解您的代码变得相当复杂! (想象一下,有人对您的某个函数有问题,查看源代码却找不到它)。
- 最好在函数或宏中分解出代码的共性。让你的计算机编写一堆非常相似的函数是一个糟糕的方法。
你怎么认为? Lisp 中的生成函数什么时候有意义?它应该是“即时”的还是您更愿意将其保存在某个文件中?
This question asks to create a Clojure macro to generate several functions. We figured out a way to do this but were stuck with the question of "Is this a good idea?".
My initial reaction is not really, for two reasons
- You then have functions that are not defined in your code, and this can complicate understanding your code quite a bit! (Imagine somebody has a problem with one of your functions and looks at the source code only to not find it anywhere).
- It is better to factor out the commonality of the code in a function or macro. Letting your computer write a bunch of functions that are very alike is a poor approach to that.
What do you think? When does generating functions in a Lisp make sense? Should it ever be 'on the fly' or would you prefer to have it in a file somewhere?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
发布评论
评论(5)
好的工具会解决第一个问题。在 Emacs/SLIME 中,只需按 M-。在符号上,它会将您带到定义它的任何地方。当然,如果您的宏非常复杂,这可能对您没有多大帮助,但这是另一个问题。
至于#2,这很大程度上是一个设计考虑。您可以完全不使用宏来“即时”编写函数,并且不会产生太大差异。
我个人主要担心宏,尤其是定义变量的宏,是它们的可组合性比我想要的要差。我想我同意 Dave Newton 的观点,即您应该限制任何特定宏的作用范围,并尝试将尽可能多的代码分解到公共函数(也许还有一些简单的宏)中。特别是如果你的宏创建了一些对象并将其分配给一个 var (即任何以 def... 开头的东西),你可能应该确保还有一种方法可以“匿名”创建该对象,并且如果你不需要宏为此,甚至更好。
我发现使用这种方法时,您可以通过将宏生成 lambda 例程视为代码生成器,然后定义利用该生成器但显式定义的函数(使用 defun)来恢复一些可读性。
例如:
(defmacro code-generator (&body body)
`(lambda (x y)
;do some stuff that's duplicated by a lot of functions you want to write
,@body))
(defun fun-a (x y)
(funcall (code-generator
(do-this on-some-var-in-code-generator-environment))
x y))
(defun fun-b (x y)
(funcall (code-generator
(do-that on-some-var-in-code-generator-environment))
x y))
(let ((closure-val 'some-val))
(defun fun-c (x y)
(funcall (code-generator
(combine closure-val with-some-var-in-code-generator-env-etc))
x y)))
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
多年来,对代码复杂性的抱怨一直伴随着宏。每个抽象都隐藏了复杂性,无论是宏、函数还是其他什么。
函数因式分解的价值在于重用,因为函数比宏更具可重用性。不仅在能够使用“apply”的情况下,而且在共享代码的字面情况下也是如此。共享函数只是指向函数实现的指针。共享和重用的宏会产生多个函数或代码或其他内容的副本,虽然存在抽象,但代码在系统内根本不共享。
现在,您可以创建一个非常聪明的宏,在扩展时检查函数定义,如果没有找到它,那么它可以动态创建该函数,或者做一些其他聪明的事情。
但即使将这些功能分解出来,它们表面上仍然对用户隐藏,因为这是宏背后的基本前提。将这些辅助函数放入某个隐藏包中并不会使消费者更加可见,除非他们知道查看源代码(假设他们甚至拥有源代码)。
理想情况下,开发人员对这些功能不感兴趣,因为它们“内部没有用户可维修的部件”。如果确实如此,那么宏首先就不足以支持这些函数或提供文档。
The complaint about code complexity have been following macros around for years. Every abstraction is there to hide complexity, whether it's a macro, or function, or whatever.
The value of factoring to a function is reuse, as functions are more reusable than macros. Not just in the case of being able to use "apply", but in the literal case of shared code. A shared function is simply a pointer to implementation of the function. A shared and reused macro results in several COPIES of functions or code or whatever, and while the abstraction is there, the code is not shared at all within the system.
Now, you could make a really clever macro that checks for a function definition at expansion, and if it doesn't find it, then it can create the function on the fly, or do some other clever thing.
But even with the functions factored out, they're still going to be ostensibly hidden from the user since that the basic premise behind the macro in the first place. Putting those helper functions in to some hidden package doesn't make then any more visible to the consumer unless they know to even look within the source code (assuming they even have the source code).
Ideally, the functions would be uninteresting to the developer, as they have "no user serviceable parts inside". If they do, then the macro is not sufficient support or documentation for these function in the first place.