我读了:
我承认元编程/代码生成背后的目的有些混乱。
有没有人提供在哪里使用元编程/代码生成的具体示例?更好的解释是为什么它比其他方法更好。
编辑:将蓟视为元编程吗?
Answers:
想象一个制造汽车的家伙。说这与使用计算机是同一回事。
在某个时候,他意识到自己或多或少总是在做同一件事。
因此,他建立了工厂来制造汽车,这要好得多。他正在编程!
不过,在某个时候,他再次意识到自己在某种程度上一直在做同样的事情。
现在,他决定建造工厂,建造可以生产汽车的工厂。那是元编程。
元编程功能非常强大,但是系统中的一个小故障会使所有优势变成了巨大的困难。因此,掌握并使用它...或远离吧!
我认为元编程是“编写(或修改)其他程序的程序”。(另一个答案说“制造工厂的工厂”,比喻不错)。
人们为此找到了各种各样的用途:定制应用程序,生成样板代码,针对特殊情况优化程序,实现DSL,插入代码以处理正交设计问题(“方面”)...
引人注目的是,发明了许多不同的机制来完成此任务:文本模板,宏,预处理条件,泛型,C ++模板,方面,反射等。通常,这些机制中的某些机制已内置到某些语言中,并且其他机制转换成其他语言,并且大多数语言根本没有元编程支持。功能的这种分散分布意味着您可能可以用一种语言进行某种元编程,但有一定的限制,而不能用另一种语言进行这些元编程。令人不安的是:-}
我一直遵循的观点是,可以构建通用的元编程机制,以程序转换的形式使用任何语言 。一个程序转化为一个参数化的模式:“如果你看到这句法,通过替换它的是语法”。
通常,一个转换本身并不令人印象深刻,但是数十个或数百个可以对代码进行惊人的更改。因为(复杂的)程序转换实际上可以模拟图灵机,所以它们可以执行任意代码更改,包括您发现的所有点对点技术。
接受语言定义的工具。特定于语言的转换并生成另一个应用这些转换的方法是元元编程工具:一种编写“编写程序的程序”的程序。
价值在于您可以应用这种工具对任意代码进行各种各样的更改。而且,您不需要语言设计委员会就可以意识到您需要特定类型的元编程支持,并且赶紧提供它,以便您今天就可以继续工作。
一个有趣的教训是,此类机器需要强大的程序分析(符号表,控制和数据流分析等)支持,以帮助其专注于代码中的问题所在,以便元编程机器可以在此时进行某些操作(非常这种方面的一个较弱的例子是切入点方面的规范,即“在看起来像这样的地方进行更改”)。
OP要求提供元编程应用的特定示例。我们已经使用“元”元编程工具(DMS软件重新设计工具包)在大型代码库上自动执行以下活动:
跨多种语言,包括Java,C#,C ++,PHP等
OP还问:“为什么这比替代方案更好?” 答案与规模,时间和准确性有关。
对于大型应用程序,庞大的代码库意味着您没有资源或时间来手动进行此类分析或更改。
对于代码生成或优化任务,您可以手工完成,但是工具可以更快,更准确地完成。
本质上,这些工具可以做人类根本做不到的事情。
值得注意的是,这些工具没有创造力。您仍然需要人工确定要执行的操作,例如,确定任务是什么(请参见上面的示例),并确定如何定义分析/转换以实现效果。您仍然需要元编程器。但是,当元程序员使用正确的知识武装此类工具时,所产生的代码似乎是由难以置信的快速,富有创造力的专家编码器构建的。
我已经充分利用元编程在不同API之间进行桥接。
一个有效的示例是FireBreaths 1,它可以简化编写暴露给JavaScript的C ++类的工作。通过为要公开的功能提供注册功能,可以检查参数类型,并从编译时生成的适合代码中检查参数类型,这些合适代码从script-API类型转换为本地C ++类型,甚至可以直接支持,JSAPIAuto
map
vector
等
作为一个简单的示例,请考虑add(a, b)
使用某些脚本API类型的公开函数:
ScriptVariant add(const std::vector<ScriptVariant>& values) {
// have to check argument count
if(values.size() != 2)
throw script_error("wrong number of arguments");
try {
// have to convert from scripting-API types
long a = values[0].convert_cast<long>();
long b = values[0].convert_cast<long>();
return a+b; // potentially need to convert back too
} catch(ScriptVariant::bad_cast& e) {
// need to handle conversion failure
throw script_error("conversion failed :(");
}
}
埋藏在其中的实际逻辑只有一行,检查和转换很烦人且多余。使用前面提到的注册功能(例如,在构造函数中):
registerMethod("add", make_method(this, &MyClass::add));
现在可以简单地写成:
long add(long a, long b) {
return a+b;
}
...并且框架负责为您生成必要的代码。
1:虽然我会做一些实现...更清洁...如果我不得不再次开始
我最近(过去6个月)的具体代码生成示例:
我有一个SQL Plus脚本,该脚本可以生成然后执行其他SQL Plus脚本。生成脚本针对具有时间戳记字段的某些表运行查询,并且在设计脚本时,无法知道要选择哪个时间窗口。因此,主脚本执行其工作,并确定子脚本中需要包含什么时间范围。然后,通过将下标的代码写入文件来生成下标(并用占位符代替实际的开始时间和结束时间)。最后,它执行下标。现在,我已经在少数情况下使用了这个技巧(尽管通常比这要复杂得多),其中子步骤的结构取决于先前步骤的结果。
我曾经有一个电子表格,将元素从XSD映射到数据库中的表列。可以使用宏和VBA从电子表格中生成XSL代码段并完成查询。这些代码片段和查询被复制并粘贴(大多数情况下无需更改即可粘贴)到执行它们并处理结果的系统中。这不是一个很好的解决方案,但它确实使一项非常繁琐的工作变得不那么乏味,并且所产生的代码看上去比我花了一两个星期手工编写的代码看起来更加一致。
SO元编程示例列表:您在C ++中看到过的最酷的元编程示例是什么?
我可以举一个具体的例子:我正在开发ABSE,这是一种元编程方法。使用ABSE,您可以创建一个模型(实际上是一棵树),其中每个项目都是一个“原子”。该Atom表示一个“概念”,并包含其定义所需的元数据。
在ABSE中,概念的实现实际上是一个“小程序”。
然后,主持人建模师(AtomWeaver,沿着ABSE开发)主罚模式和“编织”发电机计划了其所有原子的。然后运行该程序,生成所需的工件(源代码,数据等)。
因此,ABSE工作流程为:
乍一看,这看起来像是很多多余的,复杂的工作,但是如果您掌握了这个概念,则实际上非常简单。
元编程的优势(并非ABSE独有)?:
元编程,代码生成,程序转换是IMHO软件开发中令人兴奋的新世界。但是,元编程需要一种新技能:元思维。
我们可以将元思考定义为“思考您如何看待自己的发展”。一种对自己的集体反思。在实践中,您必须找出自己的开发模式,将其隔离,使其具有通用性,然后使用您喜欢的技术将其转换为元程序,例如ABSE,DSL,DSM等。
flex
和bison
工具。与Spirit一样,它用于创建词法分析器和解析器。但是,它们具有自己的非C或C ++语言,必须作为单独的步骤运行,并且它们的输出必须由C或C ++编译器编译。这说明了代码生成器和元编程之间的根本区别:代码生成器将其工作作为一个单独的“预处理”步骤,然后对其输出进行编译,而元编程则只需一步就可以编译为目标代码,就像其余代码一样。
我将尝试解释使用元编程技术的具体示例。
我创建了一个程序工具,该工具将从任何MS Access数据输入表单生成ASP.NET网页源代码。我使用的技术是为每种类型的表单控件创建自己的ASP.NET文本模板。我只是简单地从MS Access表单对象元数据中插入了TOP,LEFT,HEIGHT,WIDTH,CONTROLSOURCE等值。例如,我的ASP.NET文本框模板如下所示:
<asp:TextBox ID="**ID**" runat="server" style="z-index: 1; left: **LL**px; top: **TOP**px; position: absolute" Text='<%# Bind("[**CTLSOURCE**]") %>' />
获取文本框控件元数据值后,我的程序为文本框生成代码
<asp:TextBox ID="txtCustomerID" runat="server" style="z-index: 1; left: 50px; top: 240px; position: absolute" Text='<%# Bind("[CustomerID]") %>' />
我的程序在2-3秒内为一个MS Access表单生成了整个网页的源代码。另一种方法是从头开始手工编写ASP.NET网页。一项可能耗时数小时甚至数天的任务。
想象一个具有24-35个表格的MS Access数据库。将每种形式的代码手动编写为ASP.NET网页源代码可能要花费数周甚至数月的时间。在这种情况下,使用具有元编程技术的转换工具可以将网页的开发时间从数周和数月缩短到数小时。
一个具体示例,说明它可能是一种有用的方法。
您有一组第三方类,您想要向它们添加一般行为-例如某种安全/访问控制,将对象映射为JSON等。
您可以为所有内容编写或生成子类,添加包装方法以添加访问控制并调用超类。通过元编程,您可以在运行时执行此操作,并且您的更改还将自动应用于任何其他/更改的第三方类。
对于JSON示例,通过使用类的自省,您应该能够生成代码以序列化对象,然后将其作为方法添加到类中。另一种极端情况是(在编译之前)预先生成或编写代码,并在每次类更改时产生影响,或者是每次想要映射它时,对每个对象使用内省的完全通用的方法。
根据所讨论的语言和运行时,元编程方法可能比完全通用/内省的方法要快,但由于减少了对代码的大量数据查找,因此元代码编程的速度较慢。
在元编程不是直接存在于某种语言中的地方,在我看来,它通常是通过框架(即像Spring这样的IoC样式容器)重新发明的。
我们经常使用元编程来在VBA中创建属性。我们有各种带有许多标题的Excel电子表格,并且我们希望为每个标题定义getter / setter属性,以允许我们操纵该标题下的单元格。手动执行此操作将是一场噩梦。
我们选择的元编程框架是Notepad ++及其查找/替换正则表达式的功能。这是我们对属性进行元编程的方式:
最后,我们有了一个混合了手动步骤,记录的宏和一个正则表达式的过程,每次需要图纸的属性时都可以重新应用它。我们做到了!效果很好。
这就是元编程的力量。何时使用,取决于经验/直觉。但是我建议回答这个问题:
如果能更快地直接将其编码,还是可以自动化部分/全部过程并加快过程?
这样一来,您就可以划清界限,超出该界限,元编程将不再有用。如果您可以更快地编写代码,即使它是10次重复,那就去做吧!仅当重复数以百计时,或者您希望将来重复使用多次,然后对它进行元编程。
另一点是这里有学位。我曾经写过一个Java程序来创建一堆文件,用于向检查编码项目中添加新的IntelliJ检查。这是相当大的开销:创建Java项目并进行编译等。另一方面,Notepad ++查找/替换只是您自己手动键入内容的一小步。这里的建议是手动开始做事,然后根据您的需要自动执行,直到合理为止。Notepad ++可以使用时不需要Java程序。手动键入时无需使用Notepad ++。