元编程的用途是什么?


71

我读了:

我承认元编程/代码生成背后的目的有些混乱。

有没有人提供在哪里使用元编程/代码生成的具体示例?更好的解释是为什么它比其他方法更好。

编辑:将视为元编程吗?


1
您的第二个链接看起来很清楚,您对这两个概念的理解是什么,以查看您是否存在一些基本的困惑,否则您的问题就太宽泛且主观了。
詹姆斯·布莱克

1
我的问题是实用性问题-为什么元编程比编写一些参数化SQL查询并根据特定条件将它们粘合在一起更好?或者那个元编程?(我不这么认为,但这就是为什么我要问这个问题-它是否与众不同,为什么它更好?)。
韦恩·维尔纳

1
因为有时您甚至都不知道要查询的表或要提前返回的列(也许它取决于用户输入的组合,所以很难提前计算所有可能性),所以您使用动态SQL(可以将其视为元编程的一种形式)。
FrustratedWithFormsDesigner

Answers:


114

想象一个制造汽车的家伙。说这与使用计算机是同一回事。
在某个时候,他意识到自己或多或少总是在做同一件事。
因此,他建立了工厂来制造汽车,这要好得多。他正在编程!
不过,在某个时候,他再次意识到自己在某种程度上一直在做同样的事情。
现在,他决定建造工厂,建造可以生产汽车的工厂。那是元编程。

元编程功能非常强大,但是系统中的一个小故障会使所有优势变成了巨大的困难。因此,掌握并使用它...或远离吧!


6
这是一个很好的答案,但是它显示了我对元编程最讨厌的事情。什么时候结束?是什么阻止我们让工厂建厂,工厂建厂,绵羊建厂,工厂建车?
XQLRSZ 2015年

5
@XQLRSZ您知道何时停止编写任何代码的方式相同:何时停止编写使您的生活变得更轻松。除非您一遍又一遍地做某事,否则没有理由将其提取为一段代码。
安德鲁

2
在某些环境中,它永远不会结束。参见Nada Amin的演讲“软件应自食其力”。youtube.com/watch?v=SrKj4hYic5A
Joan Charmant

完全同意第一段。不同意第二部分。以我的经验,元编程比编程本身允许更多的错误。如果有一个错误,谁在乎,我们可以修复它,因为它只分发给开发人员。但是,如果我们的实际代码中存在错误,那么该错误将被释放,并且很难解决。所以我的建议是自由和随意地进行元编程。不必担心TDD或完美。只需使其工作即可。
Colm Bhandal

@XQLRSZ通常以图灵完成元编程(即元编程的第一级)结束。这是因为元程序通常可以访问和修改自己代码(因此您的元元程序只是您的元程序)。
真正的

20

我认为元编程是“编写(或修改)其他程序的程序”。(另一个答案说“制造工厂的工厂”,比喻不错)。

人们为此找到了各种各样的用途:定制应用程序,生成样板代码,针对特殊情况优化程序,实现DSL,插入代码以处理正交设计问题(“方面”)...

引人注目的是,发明了许多不同的机制来完成此任务:文本模板,宏,预处理条件,泛型,C ++模板,方面,反射等。通常,这些机制中的某些机制已内置到某些语言中,并且其他机制转换成其他语言,并且大多数语言根本没有元编程支持。功能的这种分散分布意味着您可能可以用一种语言进行某种元编程,但有一定的限制,而不能用另一种语言进行这些元编程。令人不安的是:-}

我一直遵循的观点是,可以构建通用的元编程机制,以程序转换的形式使用任何语言 。一个程序转化为一个参数化的模式:“如果你看到句法,通过替换它的是语法”。

通常,一个转换本身并不令人印象深刻,但是数十个或数百个可以对代码进行惊人的更改。因为(复杂的)程序转换实际上可以模拟图灵机,所以它们可以执行任意代码更改,包括您发现的所有点对点技术。

接受语言定义的工具。特定于语言的转换并生成另一个应用这些转换的方法是元元编程工具:一种编写“编写程序的程序”的程序。

价值在于您可以应用这种工具对任意代码进行各种各样的更改。而且,您不需要语言设计委员会就可以意识到您需要特定类型的元编程支持,并且赶紧提供它,以便您今天就可以继续工作。

一个有趣的教训是,此类机器需要强大的程序分析(符号表,控制和数据流分析等)支持,以帮助其专注于代码中的问题所在,以便元编程机器可以在此时进行某些操作(非常这种方面的一个较弱的例子是切入点方面的规范,即“在看起来像这样的地方进行更改”)。

OP要求提供元编程应用的特定示例。我们已经使用“元”元编程工具(DMS软件重新设计工具包)在大型代码库上自动执行以下活动:

  • 语言迁移
  • 实施测试覆盖率和探查器
  • 实施克隆检测
  • 大规模架构再造
  • 用于工厂控制的代码生成
  • 嵌入式网络控制器的SOA化
  • 大型机软件的架构提取
  • 通过数组计算生成矢量SIMD指令
  • 将代码反向工程回概念

跨多种语言,包括Java,C#,C ++,PHP等

OP还问:“为什么这比替代方案更好?” 答案与规模,时间和准确性有关。

对于大型应用程序,庞大的代码库意味着您没有资源或时间来手动进行此类分析或更改。

对于代码生成或优化任务,您可以手工完成,但是工具可以更快,更准确地完成。

本质上,这些工具可以做人类根本做不到的事情。

值得注意的是,这些工具没有创造力。您仍然需要人工确定要执行的操作,例如,确定任务是什么(请参见上面的示例),并确定如何定义分析/转换以实现效果。您仍然需要编程器。但是,当元程序员使用正确的知识武装此类工具时,所产生的代码似乎是由难以置信的快速,富有创造力的专家编码器构建的。


5

我已经充分利用元编程在不同API之间进行桥接。

一个有效的示例是FireBreaths 1,它可以简化编写暴露给JavaScript的C ++类的工作。通过为要公开的功能提供注册功能,可以检查参数类型,并从编译时生成的适合代码中检查参数类型,这些合适代码从script-API类型转换为本地C ++类型,甚至可以直接支持,JSAPIAutomapvector

作为一个简单的示例,请考虑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:虽然我会做一些实现...更清洁...如果我不得不再次开始


3

我最近(过去6个月)的具体代码生成示例:

  1. 我有一个SQL Plus脚本,该脚本可以生成然后执行其他SQL Plus脚本。生成脚本针对具有时间戳记字段的某些表运行查询,并且在设计脚本时,无法知道要选择哪个时间窗口。因此,主脚本执行其工作,并确定子脚本中需要包含什么时间范围。然后,通过将下标的代码写入文件来生成下标(并用占位符代替实际的开始时间和结束时间)。最后,它执行下标。现在,我已经在少数情况下使用了这个技巧(尽管通常比这要复杂得多),其中子步骤的结构取决于先前步骤的结果。

  2. 我曾经有一个电子表格,将元素从XSD映射到数据库中的表列。可以使用宏和VBA从电子表格中生成XSL代码段并完成查询。这些代码片段和查询被复制并粘贴(大多数情况下无需更改即可粘贴)到执行它们并处理结果的系统中。这不是一个很好的解决方案,但它确实使一项非常繁琐的工作变得不那么乏味,并且所产生的代码看上去比我花了一两个星期手工编写的代码看起来更加一致。

SO元编程示例列表:您在C ++中看到过的最酷的元编程示例是什么?


3

我可以举一个具体的例子:我正在开发ABSE,这是一种元编程方法。使用ABSE,您可以创建一个模型(实际上是一棵树),其中每个项目都是一个“原子”。该Atom表示一个“概念”,并包含其定义所需的元数据。

在ABSE中,概念的实现实际上是一个“小程序”。

然后,主持人建模师(AtomWeaver,沿着ABSE开发)主罚模式和“编织”发电机计划了其所有原子的。然后运行该程序,生成所需的工件(源代码,数据等)。

因此,ABSE工作流程为:

  1. 创建一个离散概念(元元程序的一部分)
  2. 在模型中重用该概念(有效地构建元程序)
  3. 宿主建模器编织并运行元程序
  4. 元程序生成您的最终程序

乍一看,这看起来像是很多多余的,复杂的工作,但是如果您掌握了这个概念,则实际上非常简单。

元编程的优势(并非ABSE独有)?:

  • 更改模型并重新生成完整的系统(想象一下重构功能,而不是源代码行)。
  • 更改模型中的一些定义会导致程序不同(软件产品系列)。
  • 通过重用模板,您可以更改模板的代码,重新生成代码,并在数十个数百个地方更改您的代码。
  • 真的很多

元编程,代码生成,程序转换是IMHO软件开发中令人兴奋的新世界。但是,元编程需要一种新技能:元思维。

我们可以将元思考定义为“思考您如何看待自己的发展”。一种对自己的集体反思。在实践中,您必须找出自己的开发模式,将其隔离,使其具有通用性,然后使用您喜欢的技术将其转换为元程序,例如ABSE,DSL,DSM等。


我不是一个精通技术的人,但是让我问你一个问题,你只是说你已经创建了一个4级系统,就像它们是构建块一样,可以根据编程的需求进行将来的重新配置,因此您可以编织修补程序,更改或建立旧的或新的网络吗?
Nader Belal '19

2

基于元编程的库/代码有助于直接编写显式且简单的代码,这些代码将根据所使用的参数为您生成实现详细信息代码。

Boost充满(C ++)库,这些库演示了元编程可以实现的功能。有一些很好的(也许很难理解)的例子是允许执行的DSL精神,让写一个编译器使用EBNF直接在代码中的语法等诸多吹自扫门前雪库。


3
相应的代码生成示例是GNUflexbison工具。与Spirit一样,它用于创建词法分析器和解析器。但是,它们具有自己的非C或C ++语言,必须作为单独的步骤运行,并且它们的输出必须由C或C ++编译器编译。这说明了代码生成器和元编程之间的根本区别:代码生成器将其工作作为一个单独的“预处理”步骤,然后对其输出进行编译,而元编程则只需一步就可以编译为目标代码,就像其余代码一样。
Mike DeSimone 2010年

我不认为“元编程”与“代码生成”是“源代码到目标代码编译器是这样做的”还是“是在我的主要编程语言中完成的”。重要的是,我手动编写的代码被转换为可以运行的代码。“编译器”可以在早期,中期,后期的各个阶段进行元编程(遵循我编写的元编程指令)。重要的是,元编程是关于使用我编写的其他代码来操纵我编写的代码,以生成最终执行的代码。是的,其中包括代码生成器恕我直言。
Ira Baxter

2

我将尝试解释使用元编程技术的具体示例。

我创建了一个程序工具,该工具将从任何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网页源代码可能要花费数周甚至数月的时间。在这种情况下,使用具有元编程技术的转换工具可以将网页的开发时间从数周和数月缩短到数小时。


1

一个具体示例,说明它可能是一种有用的方法。

您有一组第三方类,您想要向它们添加一般行为-例如某种安全/访问控制,将对象映射为JSON等。

您可以为所有内容编写或生成子类,添加包装方法以添加访问控制并调用超类。通过元编程,您可以在运行时执行此操作,并且您的更改还将自动应用于任何其他/更改的第三方类。

对于JSON示例,通过使用类的自省,您应该能够生成代码以序列化对象,然后将其作为方法添加到类中。另一种极端情况是(在编译之前)预先生成或编写代码,并在每次类更改时产生影响,或者是每次想要映射它时,对每个对象使用内省的完全通用的方法。

根据所讨论的语言和运行时,元编程方法可能比完全通用/内省的方法要快,但由于减少了对代码的大量数据查找,因此元代码编程的速度较慢。

在元编程不是直接存在于某种语言中的地方,在我看来,它通常是通过框架(即像Spring这样的IoC样式容器)重新发明的。


1

启动Visual Studio(Eclipse,Netbeans等)。创建一个新项目。惊喜-通过模板创建项目,您刚刚使用了一些元编程。不实用吗?


0

您可以查看Common Lisp的宏或C ++的模板,并查看其用法。两者都是您正在使用的元编程。您会发现两者在很多代码中都被大量使用。

Lisp宏通常用于重新定义语言。例如,Paul Graham的On Lisp的最后一章为Common Lisp创建了一个有效的面向对象的扩展。另一个例子是现已解散的石榴石

较旧的C ++标准模板库(大部分已包含在标准库中)是一种引入大量容器和算法的方法,至少在集成和效率方面(不是语法上),它们就像是内置在语言中一样工作。


0

我们经常使用元编程来在VBA中创建属性。我们有各种带有许多标题的Excel电子表格,并且我们希望为每个标题定义getter / setter属性,以允许我们操纵该标题下的单元格。手动执行此操作将是一场噩梦。

我们选择的元编程框架是Notepad ++及其查找/替换正则表达式的功能。这是我们对属性进行元编程的方式:

  • 将标头列表从Excel复制到Notepad ++
  • 记录一个Notepad ++宏以清理数据(删除空格和特殊字符)。最后,我们列出了用换行符分隔的字符串。
  • 手动将列表复制到另一个.CSV文件,然后使用Excel生成行号列表。然后复制回Notepad ++。
  • 编写一个正则表达式以将属性名称转换为属性定义,并添加所有空格,关键字等。在我们的属性定义中使用行号作为列号。

最后,我们有了一个混合了手动步骤,记录的宏和一个正则表达式的过程,每次需要图纸的属性时都可以重新应用它。我们做到了!效果很好。

这就是元编程的力量。何时使用,取决于经验/直觉。但是我建议回答这个问题:

如果能更快地直接将其编码,还是可以自动化部分/全部过程并加快过程?

这样一来,您就可以划清界限,超出该界限,元编程将不再有用。如果您可以更快地编写代码,即使它是10次重复,那就去做吧!仅当重复数以百计时,或者您希望将来重复使用多次,然后对它进行元编程。

另一点是这里有学位。我曾经写过一个Java程序来创建一堆文件,用于向检查编码项目中添加新的IntelliJ检查。这是相当大的开销:创建Java项目并进行编译等。另一方面,Notepad ++查找/替换只是您自己手动键入内容的一小步。这里的建议是手动开始做事,然后根据您的需要自动执行,直到合理为止。Notepad ++可以使用时不需要Java程序。手动键入时无需使用Notepad ++。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.