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

Macros Lisp/Clojure:编写函数生成宏是个好主意吗?,macros,clojure,lisp,metaprogramming,Macros,Clojure,Lisp,Metaprogramming,要求创建Clojure宏以生成多个函数。我们想出了一个办法,但却被“这是个好主意吗?” 我最初的反应不是很强烈,原因有二 然后,您就拥有了代码中未定义的函数,这会使理解代码变得相当复杂!(假设有人对您的一个函数有问题,但查看源代码却发现它不在任何地方) 最好在函数或宏中考虑代码的通用性。让您的计算机编写一组非常相似的函数是一种糟糕的方法 你觉得怎么样?在Lisp中生成函数什么时候有意义?它应该是“在运行中”,还是您更愿意将其保存在某个文件中?关于代码复杂性的抱怨多年来一直伴随着宏的出现。每个抽象

要求创建Clojure宏以生成多个函数。我们想出了一个办法,但却被“这是个好主意吗?”

我最初的反应不是很强烈,原因有二

  • 然后,您就拥有了代码中未定义的函数,这会使理解代码变得相当复杂!(假设有人对您的一个函数有问题,但查看源代码却发现它不在任何地方)
  • 最好在函数或宏中考虑代码的通用性。让您的计算机编写一组非常相似的函数是一种糟糕的方法

  • 你觉得怎么样?在Lisp中生成函数什么时候有意义?它应该是“在运行中”,还是您更愿意将其保存在某个文件中?

    关于代码复杂性的抱怨多年来一直伴随着宏的出现。每个抽象都是为了隐藏复杂性,不管是宏、函数还是其他什么

    分解函数的价值在于重用,因为函数比宏更易于重用。不仅仅是在能够使用“apply”的情况下,而是在共享代码的文本情况下。共享函数只是指向函数实现的指针。一个共享和重用的宏会产生函数、代码或其他东西的多个副本,虽然存在抽象,但代码在系统中根本不共享

    现在,您可以制作一个非常聪明的宏,在展开时检查函数定义,如果找不到,那么它可以动态创建函数,或者做一些其他聪明的事情

    但是,即使将函数考虑在内,它们仍然表面上对用户隐藏,因为这是宏背后的基本前提。将这些helper函数放入某个隐藏的包中并不会使使用者更容易看到这些函数,除非他们知道要查看源代码(假设他们甚至拥有源代码)

    理想情况下,这些功能对开发人员来说是无趣的,因为它们“内部没有用户可维护的部件”。如果他们这样做了,那么宏首先就没有足够的支持或文档来支持这些功能。

    TL;医生:这要看情况

    公共性或子集是否可以抽象为一个函数(或多个函数),而只通过宏定义更动态的位?如果可能,最好使宏y部件的范围尽可能有限

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

    他们是否理解不足,是否需要经常检查以了解或验证行为?如果是这样的话,那么将它们作为实际函数可能更有意义。如果它们不是,并且或多或少是“库存”系统方面,那么做任何更干净的事情


    功能/宏是由每个人维护的,还是由更关注底层系统实现的人维护的?如果它们大部分都被消耗掉了,那么如何/在哪里/何时实现它们就不那么重要了。

    从根本上说,如果你能用更少的思考做更多的事情——假设你的抽象是干净的,不需要不断地调整和命名——这是一个很好的解决方案


    我大力提倡在最好的地方使用最好的功能,尽你所能编写更少的代码来完成更多的工作。

    好的工具会解决问题。在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)))
    

    那么…你是说“这取决于”?关于代码的复杂性/可理解性:假设其他人(同一团队的开发人员、开源库消费者等)可能需要理解你的代码。对我来说,在clojure.core中查看大多数东西的工作原理是一个巨大的好处。如果函数不是在文件中定义的,因为它是在宏展开时由宏生成的,这就变得更困难了。@Paul,但它是在所使用的文件的宏中定义的(并且您总是可以手动展开宏)。有什么区别?它与引用任何非文件本地的外部工件有何区别?@WillHartung如果您在源代码中查找它,您将无法找到它,并且可能必须仔细查看每个不同的宏以查看它的生成位置。@Paul您为什么不查找宏的源代码,因为这是生成它的代码。或者查看扩展以查看生成了什么。或者你为什么不诅咒开发人员,因为他们做了一些让你不得不在源代码中搜索这些信息的事情呢?我不相信#1是真的(我认为emacs找不到在宏中创建的函数)。完全同意这一点