Lisp / Clojure:编写函数生成宏是个好主意吗?

发布于 12-11 06:55 字数 368 浏览 2 评论 0原文

这个问题要求创建一个 Clojure 宏来生成多个函数。我们找到了一种方法来做到这一点,但仍被“这是一个好主意吗?”的问题所困扰。

我最初的反应是不完全,原因有两个:

  1. 您的代码中没有定义函数,这会使理解您的代码变得相当复杂! (想象一下,有人对您的某个函数有问题,查看源代码却找不到它)。
  2. 最好在函数或宏中分解出代码的共性。让你的计算机编写一堆非常相似的函数是一个糟糕的方法。

你怎么认为? 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

  1. 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).
  2. 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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

陈独秀2024-12-18 06:55:43

多年来,对代码复杂性的抱怨一直伴随着宏。每个抽象都隐藏了复杂性,无论是宏、函数还是其他什么。

函数因式分解的价值在于重用,因为函数比宏更具可重用性。不仅在能够使用“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.

国际总奸2024-12-18 06:55:43

TL;DR:这要看情况。

可以将共性或子集抽象为一个(或多个)函数,通过宏定义更动态的位吗?如果可能,最好尽可能限制宏部分的范围。

函数/宏的性质是什么?如果它们是记录良好的系统方面的一部分,那么它们来自哪里并不重要。

它们是否不太被理解,是否需要经常检查以理解或验证行为?如果是这样,那么将它们保留为实际函数可能更有意义。如果不是,并且或多或少是“库存”系统方面,请采取更干净的方式。

函数/宏是由每个人维护的,还是由更专注于底层系统实现的人维护的?如果它们大部分被消耗,那么它们如何/何地/何时实施就不那么重要了。

TL;DR: It depends.

Can the commonality, or a subset, be abstracted into a function (or functions), with only the more dynamic bits defined through macros? When possible, it's best to make the macro-y parts as limited in scope as possible.

What's the nature of the functions/macros? If they're part of a well-documented system aspect, it doesn't really matter where they come from.

Are they poorly-understood, and do they require frequent inspection to understand or verify behavior? If so, then leaving them as real functions may make more sense. If they're not, and are more or less "stock" system aspects, do whatever is cleaner.

Are the functions/macros maintained by everybody, or by someone more focused on an underlying system implementation? If they're mostly consumed, it matters less how/where/when they're implemented.

不必你懂2024-12-18 06:55:43

好的工具会解决第一个问题。在 Emacs/SLIME 中,只需按 M-。在符号上,它会将您带到定义它的任何地方。当然,如果您的宏非常复杂,这可能对您没有多大帮助,但这是另一个问题。

至于#2,这很大程度上是一个设计考虑。您可以完全不使用宏来“即时”编写函数,并且不会产生太大差异。

我个人主要担心宏,尤其是定义变量的宏,是它们的可组合性比我想要的要差。我想我同意 Dave Newton 的观点,即您应该限制任何特定宏的作用范围,并尝试将尽可能多的代码分解到公共函数(也许还有一些简单的宏)中。特别是如果你的宏创建了一些对象并将其分配给一个 var (即任何以 def... 开头的东西),你可能应该确保还有一种方法可以“匿名”创建该对象,并且如果你不需要宏为此,甚至更好。

Good tools will take care of concern #1. In Emacs/SLIME, just press M-. on a symbol and it takes you to wherever it was defined. Of course, if your macro is very complicated that may not help you much but that's another concern.

As for #2, that's very much a design consideration. You can write functions "on the fly" using no macros at all and it won't make much difference.

My personal main concern re macros, especially macros defining vars, is that they turn out to be less composable than I want them to be. I think I agree with Dave Newton here that you should limit the scope of what any particular macro does and try to factor out as much code into public functions (and maybe a few simple macros) as possible. Especially if your macros create some object and assign it to a var (i.e. anything that starts with def...) you probably should make sure there is also a way to create that object "anonymously" and if you don't need a macro for that, even better.

友谊不毕业2024-12-18 06:55:43

从根本上说,如果您可以用更少的思考做更多的事情 - 假设您的抽象是干净的,不需要不断调整并且命名良好 - 这是一个很好的解决方案。

我非常支持在最好的地方使用最好的功能,并尽一切努力编写更少的代码来完成更多的事情。

Fundamentally, if you can do more with less thinking - presuming your abstractions are clean, don't require tweaking constantly and well named - it is a good solution.

I am a big proponent of using the best feature in the best place, and using everything in your power to write less code to do more.

请你别敷衍2024-12-18 06:55:43

我发现使用这种方法时,您可以通过将宏生成 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)))

I've found that you can get back some readability when using this approach, by thinking of the macro-generating-lambda routine as a code generator, and then defining functions that take advantage of that generator, but are defined explicitly (using defun).

For example:

(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)))
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文