【问题标题】:Lisp / Clojure: Is it a good idea to write function generating macros?Lisp / Clojure:编写函数生成宏是个好主意吗?
【发布时间】:2011-12-12 19:59:06
【问题描述】:

This question 要求创建一个 Clojure 宏来生成多个函数。我们想出了一种方法来做到这一点,但遇到了“这是个好主意吗?”的问题。

我最初的反应是不是真的,有两个原因

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

你怎么看? Lisp 中的生成函数什么时候有意义?它应该是“即时”还是您更愿意将它放在某个文件中?

【问题讨论】:

    标签: macros clojure lisp metaprogramming


    【解决方案1】:

    多年来,关于代码复杂性的抱怨一直伴随着宏。每个抽象都是为了隐藏复杂性,无论是宏、函数还是其他任何东西。

    因式分解对函数的价值在于重用,因为函数比宏更易于重用。不仅在能够使用“应用”的情况下,而且在共享代码的字面情况下。共享函数只是指向函数实现的指针。共享和重用的宏会导致函数或代码或其他内容的多个副本,虽然存在抽象,但代码在系统内根本不共享。

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

    但即使排除了这些功能,它们仍然会在表面上对用户隐藏,因为这是宏背后的基本前提。将这些辅助函数放入某个隐藏的包中并不会让消费者更加可见,除非他们知道甚至可以查看源代码(假设他们甚至有源代码)。

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

    【讨论】:

    • 所以...你是说“这取决于”吗?
    • 关于代码复杂性/可理解性:假设其他人(同一团队的开发人员、开源库消费者等)可能需要理解您的代码。例如。对我来说,我可以在 clojure.core 中查看大多数事情是如何工作的,这是一个巨大的好处。如果一个函数没有在文件中定义,因为它是在宏扩展时由宏生成的,那将变得更加困难。
    • @Paul,但它是在使用的文件的宏中定义的(您始终可以手动扩展宏)。有什么不同?它与引用任何非本地文件的外部工件有什么不同?
    • @WillHartung 如果您在源代码中查找它,您将找不到它,并且可能必须挖掘每个不同的宏以查看它的生成位置。
    • @Paul 你为什么不查看宏的源代码,因为那是生成它的代码。或者查看扩展以查看生成的内容。或者你为什么不诅咒开发人员做了一些让你必须首先搜索这些信息的源代码的事情?
    【解决方案2】:

    TL;DR:这取决于。

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

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

    是否对它们了解甚少,是否需要经常检查以了解或验证行为?如果是这样,那么将它们保留为真正的功能可能更有意义。如果它们不是,并且或多或少是“库存”系统方面,那么做任何更清洁的事情。

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

    【讨论】:

      【解决方案3】:

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

      至于 #2,这在很大程度上是一个设计考虑因素。您可以完全不使用宏来“即时”编写函数,这不会有太大区别。

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

      【讨论】:

      • 我不相信 #1 是真的(我认为 emacs 不会找到在宏中创建的函数)。完全同意宏的可组合性问题。可以在函数中完成的所有事情都应该在函数中完成。
      • 试试看。它确实在 SLIME 中有效。它之所以有效,是因为 slime 只是询问 clojure 运行时 var 是在哪里创建的,并且只要定义了 var,就会自动添加元数据。
      • 好的,那我拿回去。非常好,它的工作原理。我想这是一个较小的问题。
      【解决方案4】:

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

      我非常支持在最好的地方使用最好的功能,并尽你所能编写更少的代码来做更多的事情。

      【讨论】:

        【解决方案5】:

        我发现使用这种方法时,您可以恢复一些可读性,方法是将宏生成 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)))
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2011-12-12
          • 1970-01-01
          • 2011-05-14
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多