Ruby的DSL根本不是DSL,而且我绝对不喜欢使用它们,因为它们的文档内容完全取决于它们的实际工作方式。让我们以ActiveRecord为例。它允许您“声明”模型之间的关联:
class Foo < ActiveRecord::Base
has_one :bar
has_one :baz
end
但是,这种“ DSL”的声明性(如Ruby class
语法本身的声明性)是一个可怕的谎言,任何了解Ruby“ DSLs”实际工作原理的人都可以揭露:
class Foo < ActiveRecord::Base
[:bar,:baz,:qux,:quux].each do |table|
has_one table if i_feel_like_it?(table)
end
puts "Just for shits and giggles, and to show"
puts "just how fucked up Ruby really is, we're gonna ask you"
puts "which SQL table you want the Foo model to have an"
puts "association with.\n"
puts "Type the name of a table here: "
has_one gets.chomp.to_sym
end
(只要尝试在Lisp 表单的主体内执行任何接近该动作的动作即可defclass
!)
一旦您在代码库中有了上述代码,项目中的每个开发人员都必须完全理解Ruby DSL的实际工作方式(而不仅仅是他们创建的错觉),然后才能维护代码。可用的文档将完全无用,因为它们仅记录了惯用用法,从而保留了声明性的幻觉。
RSpec甚至比上述还要差,因为RSpec具有奇怪的边缘情况,需要进行大量的逆向工程才能进行调试。(我花了整整一天的时间来弄清楚为什么我的一个测试用例被跳过了。事实证明,RSpec 在没有上下文的情况下执行所有具有上下文的测试用例,而不管它们在源中出现的顺序如何。 ,因为该context
方法会将您的块置于与通常使用的块不同的数据结构中。)
Lisp DSL由宏实现,这些宏是很少编译器。您可以通过这种方式创建的DSL不仅仅是滥用Lisp的现有语法。它们是实际的迷你语言,可以编写为完全无缝的,因为它们可以具有自己的语法。例如,Lisp的LOOP
宏比Ruby的each
方法强大得多。
(我知道您已经接受了答案,但并不是每个读此书的人都希望阅读On Lisp的全部内容。)