源代码生成是否是反模式?


118

如果可以生成某些东西,那么那是数据,而不是代码。

鉴于此,对源代码生成的整个想法不是一种误解吗?也就是说,如果有某个东西的代码生成器,那么为什么不将该东西变成一个可以接收所需参数并执行“将要生成的”代码将要执行的正确操作的适当函数呢?

如果这样做是出于性能原因,那么这听起来像是编译器的缺点。

如果要完成两种语言之间的桥梁,那么这听起来像缺少接口库。

我在这里想念什么吗?

我知道代码也是数据。我不明白的是,为什么要生成源代码?为什么不使其成为可以接受参数并对其执行操作的函数呢?


11
与代码生成相关的术语是元编程
UselesssCat

4
en.wikipedia.org/wiki/Code_as_data,Lisp语言,FP,脚本,元编程,冯·诺依曼/改进的哈佛结构等,这是被覆盖令人作呕。tl; dr区分“源代码”与“输出代码”,“代码”与“数据”等是为了简化事情。他们永远不要教条
vaxquis

9
@Utku,进行代码生成的更好原因通常与想要提供比您当前语言无法表达的更高级别的描述有关。编译器是否可以创建高效的代码实际上与它没有任何关系。考虑一下解析器生成器-由C语言生成的词法分析器flex或由C语言生成的语法分析器bison几乎肯定比C手写的等效语言更可预测,更正确并且通常更快地执行。并从少得多的代码构建(因此也减少了维护工作)。
Charles Duffy

1
也许您来自没有很多功能要素的语言,但是在许多语言中,功能是一流的-您可以传递它们,因此在这些类型的语言中,代码就是数据,您可以像这样对待它。
Restioson

1
功能语言代码中的@Restioson不是数据。一流的功能恰好意味着:功能就是数据。并不一定是特别好的数据:您不必对它们进行一点突变(例如,将函数中的所有加法突变为减法)。代码是同源语言的数据。(大多数谐音语言都具有一流的功能。但事实并非如此。)
Lyndon White

Answers:


149

源代码生成是反模式吗?

从技术上讲,如果我们生成代码,即使它是人类可以阅读的文本,它也不是代码。 源代码是由人类或其他真正的智慧生成的原始代码,未经机械翻译且不能从(真实)源(直接或间接)立即复制。

如果可以生成某种东西,那么那是数据,而不是代码。

我会说一切都是数据。甚至源代码。 特别是源代码! 源代码仅仅是一种旨在完成编程任务的语言中的数据。该数据将根据需要转换,解释,编译,生成为其他形式的数据,其中某些形式恰好是可执行的。

处理器执行内存不足的指令。用于数据的相同内存。在处理器执行指令之前,程序将作为数据加载到内存

因此,一切都是数据,甚至是代码

鉴于[生成的代码就是数据],整个代码生成的想法难道不是一个误解吗?

最好有多个编译步骤,其中一个步骤可以是将中间代码生成为文本。

也就是说,如果有某个东西的代码生成器,那么为什么不将该东西变成一个可以接收所需参数并执行“将要生成的”代码将要执行的正确操作的适当函数呢?

那是一种方法,但是还有其他方法。


代码生成的输出是文本,这是设计供人类使用的东西。

并非所有文本形式都供人食用。特别是,生成的代码(作为文本)通常旨在供编译器使用而不是人类使用。


源代码被认为是原始代码:大师-我们编辑和开发的内容;我们使用源代码控制归档的内容。即使是人类可读的文本,生成的代码通常也会从原始源代码中重新生成。一般来说,生成的代码不必受源代码控制,因为它是在构建过程中重新生成的。


1
评论不作进一步讨论;此对话已转移至聊天
maple_shaft

65

实用推理

好的,我知道代码也是数据。我不明白的是,为什么要生成源代码?

通过此编辑,我假设您是在相当实际的水平上提出问题,而不是理论上的计算机科学。

用Java之类的静态语言生成源代码的经典原因是,此类语言实际上并没有真正易于使用的语言工具来完成非常动态的工作。例如,在Java的形成时代,根本无法轻松地创建一个具有动态名称(与数据库中的表名匹配)和具有动态数据类型(与数据库中的表名匹配)的动态方法(与该表中的属性匹配)的类。所述属性的类型)。特别是由于Java在编译时能够捕获类型错误时非常重视,不保证。

因此,在这种情况下,程序员只能创建Java代码并手动编写很多行代码。通常,程序员会发现,每当表发生更改时,他都必须返回并更改代码以使其匹配。如果他忘记了那件事,就会发生坏事。因此,程序员将到达编写自己的工具的地步。因此,这条路开始了越来越智能的代码生成。

(是的,您可以动态生成字节码,但是用Java编程这样的事情并不是随机程序员在编写几行域代码之间所做的事情。)

将此与动态性很强的语言(例如Ruby)进行比较,我会在大多数方面考虑Java的对立面(请注意,我在说这时并不重视任何一种方法;它们只是有所不同)。在这里,在运行时动态生成类,方法等是100%正常和标准的,最重要的是,程序员可以在代码中轻松完成它,而无需进入“元”级别。是的,诸如Ruby on Rails之类的东西是随代码生成一起提供的,但是我们在工作中发现,基本上,我们将它用作新程序员的一种高级“教程模式”,但是过了一会儿它就变得多余了(因为代码太少了)在该生态系统中编写代码,当您知道自己在做什么时,手动编写它比清理生成的代码要快。

这些只是来自“现实世界”的两个实际示例。然后,您就有了像LISP这样的语言,其中的代码实际上就是数据。另一方面,在编译语言中(没有像Java或Ruby这样的运行时引擎),(或者曾经,我没有跟上现代C ++功能...)根本没有在运行时定义类或方法名称的概念,因此,代码生成构建过程是大多数事情的首选工具(其他更多C / C ++特定示例是诸如flex,yacc等之类)。


1
我认为这比投票率更高的答案更好。特别是,在Java和数据库编程中提到的示例在实际解决使用代码生成的原因并且是有效的工具方面做得更好。
Panzercrisis

这些天来,是否有可能用Java从数据库创建动态表?还是仅通过使用ORM?
Noumenon

“(或者曾经,我没有跟上最新的C ++功能...)”由于函数指针,这肯定已经在C ++中实现了二十多年了吗?我还没有测试过,但是我确定应该应该分配一个char数组,用机器代码填充它,然后将指向第一个元素的指针转换为函数指针,然后运行它?(假设目标平台没有采取任何安全措施来阻止您这样做,它很可能会这样做。)
Pharap

1
“分配一个char数组,用机器代码填充它,然后将指向第一个元素的指针转换为函数指针,然后运行它吗?” 除了具有不确定的行为外,它在C ++中等同于“动态生成字节码”。它属于“普通程序员不考虑”的类别
Caleth,

1
@Pharap,“可以肯定的是,在C ++中这已经有二十多年的历史了”……我不得不轻笑一下;自从我上一次编码C ++以来已经有大约二十年的历史了。:)但是我关于C ++的句子表达得很糟糕。我已经对其进行了一些更改,现在应该更清楚我的意思了。
AnoE

44

为什么生成代码?

因为使用打孔卡(或记事本中的alt代码)进行编程很麻烦。

如果这样做是出于性能原因,那么这听起来像是编译器的缺点。

真正。除非被迫,否则我不关心性能。

如果要完成两种语言之间的桥梁,那么这听起来像缺少接口库。

嗯,不知道你在说什么。

看起来像这样:生成并保留的源代码永远是永远的难题。它存在仅出于一个原因。有人想用一种语言工作,而其他人则坚持用另一种语言工作,任何人都不会费心找出如何在它们之间进行互操作,因此其中一个人想出了如何将自己喜欢的语言变成所强加的语言,以便他们可以做什么他们要。

在我必须维护它之前,这很好。在这一点上,你们都可以死。

是反模式吗?igh,不。如果我们不愿意告别以前的语言的缺点,那么许多语言甚至都不存在,而生成旧语言的代码就是多少种新语言的开始。

这是我忍受不了的一半转换科学怪人拼凑而成的代码库。生成的代码是不可修改的代码。我讨厌看无法触及的代码。但是人们一直在检查它。为什么?您可能还需要检入可执行文件。

好吧,我正在咆哮。我的观点是,我们都是“生成代码”。当您将生成的代码像源代码一样对待时,会让我发疯。只是因为看起来像源代码并没有使其成为源代码。


41
如果生成它,则不是源代码。这是中间代码。我现在要哭了。
candied_orange

65
!!看起来是什么都没关系!!!文本,二进制,DNA,如果不是源,则不是进行更改时应触摸的内容。如果我的编译过程中使用了42种中间语言,这无关紧要。别碰他们。停止检入它们。在源代码处进行更改。
candied_orange

24
XML是文本,显然不适合人类使用。:-)
尼克·基利

38
@utku:“如果不是人类要食用的东西,就不应该是文字”:我完全不同意。我想到了一些反例:HTTP协议,MIME编码,PEM文件-几乎任何在任何地方使用base64的东西。将数据编码为7位安全流的原因很多,即使没有人可以看到它也是如此。且不说事情的更大的空间通常日志文件,:一个人不应该有互动,但他们可能想偶尔/etc/文件在Unix等
丹尼尔Pryden

12
我认为“用打孔卡编程”并不意味着您认为的含义。我去过那里,做了那件事,是的,那痛苦。但它与“生成的代码”无关。一副打孔卡只是另一种文件,例如磁盘上的文件,磁带上的文件或SD卡上的文件。回到过去,我们将数据写入卡片组,并从中读取数据。因此,如果我们生成代码的原因是使用打孔卡进行编程很麻烦,那么这意味着使用任何类型的数据存储进行编程都是很痛苦的。
所罗门慢

41

为什么生成源代码

我在职业生涯中最常使用的代码生成器用例是生成器

  • 将某种数据模型或数据库模式作为输入(可能是关系模式或某种XML模式)进行了高级元描述。

  • 并生成了用于数据访问类的样板CRUD代码作为输出,并且可能还有其他内容,例如相应的SQL或文档。

这样做的好处是,从简短输入规范的一行开始,您将获得5到10行可调试,类型安全,无错误的代码(假定代码生成器的输出已经成熟),否则您必须手动实现和维护这些代码。您可以想象这将在多大程度上减少维护和开发工作量。

让我也回答您的最初问题

源代码生成是反模式吗

不,本身不是源代码生成,但是确实存在一些陷阱。如The Pragmatic Programmer中所述在生成难以理解的代码,应避免使用代码生成器。否则,增加的使用或调试此代码的工作量可能很容易超过不手动编写代码而节省的工作量。

我还想补充一点,通常是一个好主意,即以物理方式从手动编写的代码中分离出代码的生成部分,以便重新生成不会覆盖任何手动更改。但是,我也已经不止一次地处理过这种情况,即任务是将用旧语言X编写的一些代码迁移到另一种更现代的语言Y上,并打算随后使用语言Y进行维护。这是有效的用法一次性代码生成的情况。


我同意这个答案。使用类似Torque for Java的工具,我可以自动生成Java源文件,其字段与sql数据库匹配。这使操作更加容易。主要好处是类型安全,包括仅能够引用数据库中存在的字段(谢谢您自动完成)。
MTilsted

是的,对于静态类型的语言,这是重要的部分:您可以确保手写代码实际上适合所生成的代码。
圣保罗Ebermann

“迁移一些用旧语言编写的代码”-即便如此,一次性代码生成也可能是一个很大的麻烦。例如,在进行一些手动更改之后,您将检测到生成器中的错误,并且需要在修复后重做生成。幸运的是,git或类似的方法通常可以减轻疼痛。
maaartinus

13

为什么要生成源代码?

我遇到了两个生成(在构建时,从未签入)代码的用例:

  1. 通过用于指定特定内容的语言自动生成样板代码,例如getter / setter,toString,equals和hashCode。
  2. 根据某些接口规范(REST,SOAP等)自动生成DTO类型类,然后在主代码中使用。这类似于您的语言桥梁问题,但是最终变得更简洁,更简单,并且与尝试在没有生成类的情况下实现相同的事物相比,类型处理更好。

15
表达力强的语言中的高度重复的代码。例如,我不得不编写一些代码,这些代码在许多相似但不相同的数据结构上做同样的事情。这大概可以有像一个C ++模板做了(嘿嘿是不是代码生成?)。但是我使用的是C。代码生成使我不必编写很多几乎相同的代码。
尼克·基利

1
@NickKeighley也许您的工具链不允许您使用其他更合适的语言?
威尔逊

7
通常您不会选择您的实现语言。该项目使用C语言编写,这不是一个选择。
尼克·基利

1
@Wilson更具表现力的语言经常使用代码生成(例如lisp宏,rails上的ruby),但它们不需要同时将它们另存为文本。
皮特·柯坎

4
是的,代码生成本质上是元编程。诸如Ruby之类的语言允许您使用语言本身进行元编程,但是C不允许,因此您必须使用代码生成。
肖恩·伯顿

13

Sussmann在他的经典著作《计算机程序的结构和解释》中对这些事情有很多有趣的说法,主要是关于代码数据二元性的。

对我来说,即席代码生成的主要用途是利用可用的编译器将一些特定于领域的语言转换为可以链接到程序中的语言。想想BNF,想想ASN1(实际上,不要,这很丑陋),想想数据字典电子表格。

琐碎的领域特定语言可以节省大量时间,并且在创建这样的东西时,输出通过标准语言工具可以编译的东西是一种方法,而您宁愿编辑这种琐碎的手动分析器,无论您使用哪种母语写作,还是自动生成的BNF?

通过输出文本,然后将其输入到某些系统编译器,我无需进行任何考虑即可获得所有这些编译器的优化和特定于系统的配置。

我正在有效地将编译器输入语言用作另一种中间表示形式,这是什么问题?文本文件不是天生的源代码,它们可以是编译器的IR,并且如果它们恰好看起来像C或C ++或Java或其他内容,谁在乎?

现在,如果您很难想到可以编辑玩具语言解析器的输出,那么下次有人编辑输入语言文件并进行重建时,这显然会让您失望的,答案是不要将自动生成的IR提交给存储库由您的工具链生成的(并且避免在您的开发团队中拥有这样的人,他们通常在市场营销方面会更快乐)。

这并不是我们语言表达能力的失败,它表达了以下事实:有时您可以将规范的某些部分(或按摩)成一种可以自动转换为代码的形式,并且通常会少得多错误,并且易于维护。如果我可以给我们的测试和配置人员一个电子表格,他们可以对其进行调整,然后他们运行一个工具来获取数据,并在ECU上为闪存生成一个完整的十六进制文件,那么与由他人手动翻译相比,这将节省大量时间将最新的设置转换成一天中用语言表示的一组常量(用错别字完成)。

在Simulink中构建模型,然后用RTW生成C,然后使用任何有意义的工具进行编译,以达到目标,中间C是不可读的,那又如何呢?高级Matlab RTW东西只需要知道C的子集,C编译器负责平台的细节。只有当RTW脚本有bug时,人类才需要仔细检查生成的C,这种事情用名义上可读的IR进行调试比使用二进制分析树要容易得多。

您当然可以编写这样的东西来输出字节码甚至可执行代码,但是为什么要这样做呢?我们有将IR转换为这些东西的工具。


很好,但是我要补充一点,即在确定使用哪个IR时需要权衡:与例如x86汇编语言相比,将C用作IR会使某些事情变得容易,而其他事情则变得更加困难。例如,在Java语言代码和Java字节码之间进行选择时,选择就更为重要,因为还有许多操作只能以一种或另一种语言存在。
丹尼尔·普瑞登

2
但是,当针对ARM或PPC内核时,X86汇编语言的IR较差!万物都是工程学的权衡,这就是为什么他们称其为工程学。人们希望Java字节码的可能性是Java语言可能性的严格超集,并且通常情况是这样,因为无论工具链和注入IR的位置如何,您都更接近金属。
丹·米尔斯

哦,我完全同意:我的评论是对您的最后一段的回答,询问您为什么要输出字节码或一些较低级别的东西-有时您确实需要较低级别的东西。(特别是在Java中,使用字节码可以执行很多有用的事情,而这些代码在Java语言本身中是无法做到的。)
Daniel Pryden

2
我没有不同意,但是在金属上使用IR不仅要付出一定的代价,不仅降低通用性,而且事实上,您通常最终会对更多令人讨厌的低级优化负责。这些天我们通常在优化算法选择而不是实现方面进行思考,这反映了编译器已经走了多远,有时您在这些事情上必须非常接近金属,但是在丢弃编译器之前要三思而后行通过使用过低的IR进行优化的能力。
丹·米尔斯

1
“他们通常在市场营销方面更快乐”,凯蒂,但很有趣。
dmckee '17

13

务实的答案:代码生成是否必要且有用?它是否提供了真正非常有用的专有代码库所需要的东西,或者它似乎只是创造了另一种处理方式,从而为次优结果贡献了更多的智力开销?

好的,我知道代码也是数据。我不明白的是,为什么要生成代码?为什么不使其成为可以接受参数并对其执行操作的函数呢?

如果您必须提出这个问题,但没有明确的答案,那么代码生成可能是多余的,并且只会给代码库带来异国情调和大量的知识开销。

同时,如果您采用类似OpenShadingLanguage的方法:https : //github.com/imageworks/OpenShadingLanguage

……然后,无需提出此类问题,因为令人印象深刻的结果将立即回答这些问题。

OSL使用LLVM编译器框架将着色器网络即时转换为机器代码(及时或“ JIT”),并且在此过程中充分了解了着色器参数和其他运行时值,从而极大地优化了着色器和网络。从源代码编译着色器时已经知道。结果,我们看到我们的OSL着色网络的执行速度比用C手工制作的等效着色器快25%!(这就是我们的旧着色器在渲染器中的工作方式。)

在这种情况下,您无需质疑代码生成器的存在。如果您在这种VFX域中工作,那么您的直接反应通常是,“闭嘴,拿走我的钱!” 或者,“哇,我们还需要做这样的事情。”


将着色器网络转换成机器代码。这听起来像是编译器,而不是代码生成器,不是吗?
Utku

2
用户连接一个节点网络,并生成中间代码,该中间代码由LLVM编译为JIT。编译器和代码生成器之间的区别是模糊的。您是否在考虑使用诸如C ++中的模板或C预处理器之类的语言编写代码的功能?

我在想任何会输出源代码的生成器。
Utku

我认为,这里的产出仍供人类消费。OpenSL还生成中间源代码,但是它是低级代码,对于LLVM消耗来说,它接近于汇编。通常,不是要维护的代码(而是程序员维护用于生成代码的节点)。我大多数时候认为这些类型的代码生成器更可能被滥用,而不是足以证明其价值有用,尤其是在构建过程中必须不断重新生成代码的情况下。有时他们仍然可以解决缺点,但是仍然有一个真正的地方...

...用于特定域时可用的语言。QT的元对象编译器(MOC)就是其中之一。MOC减少了您通常需要在C ++中提供属性,反射,信号和插槽等所需的样板,但没有达到明显证明其存在的合理程度。我经常认为,如果没有MOC代码生成的繁重负担,QT可能会更好。

8

不,生成中间代码不是反模式。您的问题另一部分“为什么要这么做”的答案是一个非常广泛(和单独)的问题,尽管无论如何我都会给出一些原因。

从来没有中间可读代码的历史后果

让我们以C和C ++为例,因为它们是最著名的语言之一。

您应该注意,编译C代码的逻辑过程不是输出机器代码,而是输出人类可读的汇编代码。同样,旧的C ++编译器通常将C ++代码物理地编译为C代码。在那一系列事件中,您可以从人类可读代码1到人类可读代码2到人类可读代码3到机器代码进行编译。“为什么?” 为什么不?

如果从未生成中间的,人类可读的代码,那么我们甚至可能根本没有 C或C ++。这肯定是有可能的。人们走的道路对他们的目标的抵抗最小,如果其他一些语言由于C发展停滞而首先获得发展,那么C可能在它还很年轻的时候就死了。当然,您可以争论“但是也许我们会使用其他语言,也许会更好。” 也许,也许会更糟。也许我们都还是在汇编中。

为什么要使用人类可读的中间代码?

  1. 有时需要中间代码,以便您可以在下一步构建之前对其进行修改。我承认这一点是最薄弱的。
  2. 有时是因为原始工作根本不是用任何人类可读的语言完成的,而是在GUI建模工具中完成的。
  3. 有时您需要做一些非常重复的事情,而该语言不应该满足您的工作,因为它是一件小众事物或一件如此复杂的事情,以至于它无济于事,只会增加编程语言的复杂性或语法您。
  4. 有时,您需要做一些非常重复的事情,而无法以一种通用的方式将您想要的东西转化为语言。它不能由语言的语法表示或与语言的语法冲突。
  5. 计算机的目标之一是减少人工工作,有时不可能再次触及的代码(维护的可能性很小)可以编写元代码以十分之一的时间生成更长的代码。如果我可以在1天而不是2个星期内完成它,并且不太可能再维护一次,那么我最好生成它-并且从5年后的某个时候开始就烦恼某个人,因为他们实际上确实需要维护它,然后他们可以花2周完全写出来,如果他们想,或1周惹恼保持尴尬的代码(但我们仍为1提前一周在这一点),这就是如果保养需以全部完成。
  6. 我敢肯定,还有其他原因我会忽略。

我曾参与过一些项目,这些项目之前需要根据其他文档中的数据或信息生成代码。例如,一个项目的所有网络消息和常量数据都在电子表格中定义,并且该工具可以遍历电子表格并生成大量的C ++和Java代码,使我们可以处理这些消息。

我并不是说这是建立该项目的最佳方法(我不是启动项目的一部分),但这就是我们所拥有的,它是数百个(甚至可能是数千个,不确定)结构,对象和常量产生的;到那时,尝试以Rhapsody之类的方式重做可能为时已晚。但是即使重做了Rhapsody之类的东西,无论如何我们仍然有从Rhapsody生成的代码

同样,将所有数据包含在电子表格中也是一种不错的方式:它使我们能够以原始数据文件中没有的方式来表示数据。

例子2

在进行编译器构造工作时,我使用了Antlr工具进行词法分析。我指定了一种语言语法,然后使用该工具在C ++或Java中吐出了大量代码,然后将生成的代码与自己的代码一起使用,并将其包含在构建中。

那应该怎么做呢?也许您可以想出另一种方法;可能还有其他方法。但是对于这项工作,其他方式不会比我生成的lex / parse代码更好。


当两个系统不兼容但使用某种深奥的脚本语言编写的稳定api时,Ive将中间代码用作一种文件格式和调试跟踪。本来不是要手动读取的,但本来可以以相同的方式读取xml。但是,正如有人指出的那样,在所有网页都这样工作之后,这比您想的要普遍得多。
joojaa

7

您缺少的是重用

我们有一个了不起的工具,可以将源代码文本转换为二进制文件,称为编译器。它的输入定义明确(通常是!),并且它已经通过大量工作来完善其优化方式。如果您实际上要使用编译器执行某些操作,则要使用现有的编译器而不是自己编写。

许多人确实发明了新的编程语言并编写了自己的编译器。几乎没有例外,他们之所以这样做,是因为他们乐于接受挑战,而不是因为他们需要该语言提供的功能。他们所做的一切都可以用另一种语言完成;他们只是在创造一种新的语言,因为他们喜欢这些功能。但是,经过优化的,快速,高效,优化的编译器并不能使它们获得成功。可以肯定的是,它将为他们提供一些可以将文本转换为二进制的东西,但是它不如所有现有的编译器好

文本不仅仅是人类阅读和书写的东西。电脑也很适合在家中放置文字。实际上,像XML这样的格式(以及其他相关格式)是成功的,因为它们使用纯文本。二进制文件格式通常晦涩难懂,文档记录不清,读者无法轻易找到它们的工作方式。XML是相对自文档的,因此人们可以更轻松地编写使用XML格式文件的代码。并且所有编程语言均已设置为读取和写入文本文件。

因此,假设您想添加一些新的设施来简化您的生活。也许这是一个GUI布局工具。也许是Qt提供的信号和插槽接口。TI的Code Composer Studio也许就是这种方式,它使您可以配置正在使用的设备并将正确的库放入构建中。也许是要使用数据字典并自动生成typedef和全局变量定义(是的,这在嵌入式软件中还是很重要的)。不管是什么,利用现有编译器的最有效方法是创建一个工具,该工具将接受您所需要的配置并自动以您选择的语言生成代码。

它很容易开发和测试,因为您知道发生了什么,并且可以阅读它吐出的源代码。您不需要花很多年的时间来构建与GCC竞争的编译器。您不需要学习全新的语言,也不需要其他人来学习。您需要做的就是自动化这一小区域,其他一切保持不变。任务完成。


XML的基于文本的优势仍然在于,如果有必要,它可以由人类进行读写(通常,一旦工作,他们通常就不会打扰,但是在开发过程中肯定会这样做)。就性能和空间效率而言,二进制格式通常要好得多(尽管这通常并不重要,因为瓶颈在其他地方)。
大约

@leftaroundabout如果您需要那种性能和空间效率,请确定。如今,许多应用程序都采用基于XML的格式的原因是,性能和空间效率不再是它们曾经的最高标准,并且历史证明了二进制文件格式的维护程度很差。(旧的MS Word文档提供了一个经典示例!)重点仍然是-文本与计算机一样适合人类阅读。
格雷厄姆

当然,设计不当的二进制格式实际上可能比经过适当考虑的文本格式更糟糕,甚至一个体面的二进制格式通常也不会比带有某些通用压缩算法的XML更紧凑。IMO的两全其美是通过代数数据类型使用人类可读的规范,并根据这些类型的AST自动生成有效的二进制表示形式。参见例如平面库
大约

7

答案比较实用一些,集中在为什么而不是什么源代码上。请注意,在所有这些情况下,生成源代码都是构建过程的一部分-因此,生成的文件不应进入源代码控制。

互操作性/简单性

以Google的协议缓冲区为例:您编写了一个高级协议描述,然后可以用它来生成多种语言的实现-通常,系统的不同部分用不同的语言编写。

实施/技术原因

使用TypeScript-浏览器无法解释它,因此构建过程使用翻译(代码到代码翻译器)生成JavaScript。实际上,许多新的或深奥的编译语言都是在获得合适的编译器之前先将其编译为C。

使用方便

对于用C编写且仅使用单个二进制文件(RTOS或不使用OS)的嵌入式项目(例如IoT),很容易生成一个C数组,将要编译的数据就像普通的源代码一样,直接链接到它们作为资源。

编辑

扩展protobuf:代码生成使所生成的对象成为任何语言的一流类。在编译语言中,通用解析器必须返回键值结构-这意味着您需要大量样板代码,错过了一些编译时检查(尤其是键和值类型),导致性能下降,没有代码完成。想象一下用C语言编写的所有内容void*,或者std::variant用C ++语言编写的大量内容(如果您有C ++ 17),某些语言可能根本没有这种功能。


出于第一个原因,我认为OP的想法是在每种语言中都有一个通用的实现(采用协议缓冲区描述,然后解析/使用在线格式)。为什么这会比生成代码更糟糕?
圣保罗Ebermann

@PaŭloEbermann除了通常的性能参数外,这种通用解释将使得不可能将这些消息用作已编译(并且可能已解释)语言中的第一类对象-例如,在C ++中,此类解释器必然会返回键值结构。当然,您可以将kv放入您的类中,但是它可以变成很多样板代码。并且也有代码完成。并进行编译时间检查-编译器不会检查文字是否没有错字。
Jan Dorniak '17

我同意...您可以将其添加到答案中吗?
圣保罗Ebermann

@PaŭloEbermann完成
Jan Dorniak

6

源代码生成是反模式吗?

对于表达能力不足的编程语言,这是一种变通方法。无需使用包含适当的内置元编程的语言来生成代码。


3
这也是一种变通方法,必须为一个更具表现力的语言编写一个完整的,从本机到目标代码的编译器。生成C,让具有良好优化器的编译器负责其余工作。
Blrfl

不总是。有时,您有一个或多个数据库,其中包含一些定义,例如,总线上的信号。然后,您想将这些信息汇总在一起,也许要进行一些一致性检查,然后编写代码,以将总线信号与您希望在代码中包含的变量之间建立接口。如果您可以向我展示一种具有元编程的语言,从而可以轻松使用某些客户端提供的Excel工作表,数据库和其他数据源,并创建我需要的代码,并对数据的有效性和一致性进行一些必要的检查,则可以一切手段告诉我。
CodeMonkey '17

@CodeMonkey:像Ruby on Rails的ActiveRecord实现一样。无需在代码中复制数据库表架构。只需将类映射到表,然后使用列名作为属性编写业务逻辑即可。我无法想象代码生成器可能会产生的任何模式,而Ruby元编程也无法管理这种模式。C ++模板也非常强大,尽管有点不可思议。Lisp宏是另一个功能强大的语言内元编程系统。
凯文·克莱恩

@kevincline我的意思是基于数据库中某些数据的代码(可以从中构造),而不是数据库本身。即,我具有有关在Excel表A中接收哪些信号的信息。我具有包含有关这些信号的信息的数据库B,等等。现在,我想拥有一个访问这些信号的类。与运行该代码的计算机上的数据库或Excel工作表没有连接。使用真正复杂的C ++模板而不是简单的代码生成器在编译时生成此代码。我将选择codegen。
CodeMonkey

6

源代码生成并不总是一种反模式。例如,我目前正在编写一个框架,该框架根据给定的规范以两种不同的语言(Javascript和Java)生成代码。框架使用生成的Javascript记录用户的浏览器操作,并在框架处于重播模式时使用Selenium中的Java代码实际执行操作。如果不使用代码生成,则必须手动确保两者始终保持同步,这既麻烦又以某种方式进行逻辑复制。

但是,如果有人使用源代码生成来替换通用特性(如泛型),那么它就是反模式。


您当然可以在ECMAScript中编写一次代码,然后在JVM上的Nashorn或Rhino中运行它。或者,您可以在ECMAScript中编写JVM(或尝试使用Emscripten将Avian编译为WebAssembly)并在浏览器中运行Java代码。我并不是说这些想法很棒(嗯,它们可能是可怕的想法:-D),但是,如果不可行,至少它们是可能的。
约尔格W¯¯米塔格

从理论上讲,这是可能的,但这不是一个通用的解决方案。如果我无法在另一种语言中运行一种语言,该怎么办?例如,还有其他事情:我只是使用代码生成创建了一个简单的Netlogo模型,并具有系统的交互式文档,该文档始终与记录器和重放器同步。通常,创建需求然后生成代码可使语义上同步运行的事物保持同步。
赫里斯托(Hristo Vrigazov)

6

我在这里想念什么吗?

也许中间代码成为成功的一个很好的例子?我可以为您提供HTML。

我认为,对于HTML来说,简单和静态非常重要-它使浏览器的制造变得容易,允许早期启动移动浏览器等。随着进一步的实验(Java Applet,Flash)表明-更复杂,更强大的语言会导致更多问题。事实证明,用户实际上受到Java小程序的威胁,而访问此类网站就像尝试通过DC ++下载的游戏破解程序一样安全。另一方面,纯HTML足够无害,因此我们可以在合理相信设备安全性的情况下检出任何站点。

但是,如果不是由计算机生成的,HTML将远不及现在。在有人手动将其从数据库重写为HTML文件之前,我的答案甚至不会显示在此页面上。幸运的是,您可以使用几乎任何编程语言制作可用的HTML :)

也就是说,如果有某个东西的代码生成器,那么为什么不将该东西变成一个可以接收所需参数并执行“将要生成的”代码将要执行的正确操作的适当函数呢?

您能想象比将HTML用作生成的中间代码更好的方式向用户显示问题以及所有答案和注释吗?


是的,我可以想象一种更好的方法。HTML是Tim Berners-Lee决定允许快速创建纯文本Web浏览器的决定的遗产。当时完全可以,但是事后看来我们不会这样做。CSS使所有各种表示元素类型(DIV,SPAN,TABLE,UL等)都变得不必要。
凯文·克莱恩

@kevincline我并不是说HTML本身没有缺陷,我是在指出引入标记语言(可以由程序生成)在这种情况下效果很好。
Džuris

因此HTML + CSS比HTML更好。我什至为一些直接在HTML + CSS + MathJax中进行过的项目编写了内部文档。但是我访问的大多数网页似乎都是由代码生成器生成的。
David K

3

为什么要生成源代码?

因为它比手动编写代码更快,更容易(并且更不容易出错),特别是对于繁琐而重复的任务。在编写一行代码之前,您还可以使用高级工具来验证和验证您的设计。

常见用例:

  • 诸如Rose或Visual Paradigm的建模工具;
  • 水平的语言,如嵌入式SQL或必须预处理到的东西编译接口定义语言;
  • lexer和解析器生成器,例如flex / bison;

至于“为什么不仅仅使它成为一个函数并将参数直接传递给它”,请注意,以上都不是其自身的执行环境。无法将代码与它们链接。


2

有时,您的编程语言只是没有所需的功能,实际上使编写函数或宏来执行所需的操作实际上是不可能的。也许您可以做您想做的事,但是编写它的代码却很难看。然后,一个简单的Python脚本(或类似脚本)可以在构建过程中生成所需的代码,然后#include将其生成实际的源文件。

我怎么知道 因为这是我在使用各种不同的系统(最近是SourcePawn)时多次接触到的解决方案。当最终得到两打这样的代码行(创建我的所有cvar)时,一个简单的Python脚本可以解析一条简单的源代码并生成两到三行已生成的代码,这比手动制作已生成的代码要好得多。

演示/示例源代码,如果人们需要的话。


1

需要文本形式以便于人类容易使用。计算机也很容易以文本形式处理代码。因此,生成的代码应以最容易生成和最容易被计算机使用的形式生成,并且通常是可读文本。

而且,当您生成代码时,代码生成过程本身通常需要进行人工调试。如果生成的代码是人类可读的,那么它非常非常有用,这样人们就可以在代码生成过程中发现问题。毕竟,有人必须编写代码才能生成代码。这不是凭空发生的。


1

一次生成代码

并非所有源代码生成都是生成某些代码,然后再不对其进行处理的情况。然后在需要更新时从原始源重新生成它。

有时您只生成一次代码,然后丢弃原始源,然后继续维护新源。

将代码从一种语言移植到另一种语言时,有时会发生这种情况。特别是如果您不希望以后再移植原始文档中的新更改(例如,旧语言代码将不被保留,或者它实际上是完整的(例如,在具有某些数学功能的情况下))。

一种常见的情况是编写一个代码生成器来执行此操作,实际上可能只正确翻译了90%的代码。然后最后10%需要手工修复。这比手动翻译100%要快得多。

此类代码生成器通常与全语言翻译器(如Cython或f2c)生成的代码生成器类型非常不同。由于目标是使维护代码一次。通常将它们作为1 off来制作,以完全执行它们必须要做的事情。在许多方面,它是使用正则表达式/查找替换来移植代码的下一级别版本。您可以说“工具辅助的移植”。

仅从网站抓取一次生成代码。

密切相关的是,如果您是从某些源中生成代码,则不想再次访问。例如,如果生成代码所需的动作是不可重复的或不一致的,或者执行这些动作的成本很高。我现在正在研究两个项目: DataDeps.jlDataDepsGenerators.jl

DataDeps.jl帮助用户下载数据(如标准ML数据集)。为此,我们需要一个称为RegistrationBlock的东西。那是一些代码,指定一些元数据,例如从何处下载文件,校验和,以及一条消息,向用户解释任何条款/条件/数据的许可状态。

编写这些块可能很烦人。这些信息通常可以在托管数据的网站上(结构化或非结构化)中获得。因此,DataDepsGenerators.jl使用网络爬虫为承载大量数据的某些站点生成RegistrationBlockCode。

它可能无法正确生成它们。因此,使用生成的代码的开发人员可以并且应该对其进行检查和更正。奇怪的是,他们想确保它没有遗漏例如许可信息。

重要的是,使用DataDeps.jl的用户/开发人员无需安装或使用webscraper即可使用生成的RegistrationBlock代码。(不需要下载和安装网络爬虫可以节省大量时间。尤其是对于CI运行而言)

一次生成源代码不是反模式。它通常不能用元编程代替。


“报告”是一个英文单词,表示除“再次端口”外的其他含义。尝试“重新报告”以使该句子更清楚。(由于无法进行建议的编辑而
对它

好习惯@PeterCordes我改写了。
Lyndon White

更快,但潜在可维护性得多,具体取决于生成的代码的可怕程度。从Fortran到C的时代很早(使用C编译器的人越来越多,所以人们会使用f2c+ cc),但是生成的代码并不是C程序AFAIK的良好起点。
彼得·科德斯

1
可能,可能不会。某些代码生成器生成不可维护的代码并不是代码生成器概念中的错误。尤其是,不需要捕获所有情况的手工工具通常可以编写出完美的代码。例如,如果90%的代码只是数组常量的列表,那么将这些数组构造函数一次性生成就可以非常轻松且轻松地完成。(另一方面,Cython输出的C代码无法由人类维护。因为它不是故意的。就像您所说的f2c那样)
Lyndon White

1
大表只是最简单,最简化的参数。对于转换for循环或条件可以说类似。的确确实sed有很长的路要走,但有时需要更多的表现力。程序逻辑和数据之间的界线通常很好。有时,区分没有用。JSON是(/过去)只是javascript对象构造函数代码。在我的示例中,我还生成了对象构造函数代码(是数据吗?也许(也许不是,因为有时它具有函数调用)。将它更好地视为代码吗?是。)
Lyndon White

1

“源”代码的生成指示所生成的语言的缺陷。使用工具来克服这种反模式吗?绝对不是-让我解释一下。

通常使用代码生成,因为存在一个更高级别的定义,该定义可以描述生成的代码,而无需使用比更低级别的语言更详细的代码。因此,代码生成有助于提高效率和简洁性。

当我编写c ++时,之所以这样做,是因为与使用汇编程序或机器代码相比,它使我可以更高效地编写代码。机器代码仍然由编译器生成。刚开始,c ++只是生成C代码的预处理器。通用语言对于生成通用行为非常有用。

同样,通过使用DSL(特定领域语言),可以编写简洁的代码,但可能仅限于特定任务的代码。这将使生成正确的代码行为变得不那么复杂。记住,代码是到达终点的手段。开发人员正在寻找的是一种生成行为的有效方法。

理想情况下,生成器可以从易于操作和理解的输入中创建快速代码。如果满足此要求,则不使用生成器是反模式。这种反模式通常源于“纯”代码是“更清洁”的概念,这与木材工人或其他工匠可能会考虑使用电动工具或使用CNC来“生成”工件的方式非常相似(认为是金色的)。锤子)。

另一方面,如果生成的代码的源代码难以维护或生成的效率不够高,则用户将陷入使用错误工具的陷阱(有时是因为同样的金锤子)。


0

源代码生成绝对确实意味着生成的代码是数据。但这是一流的数据,程序其余部分可以操纵的数据。

据我所知,集成到源代码中的两种最常见的数据类型是有关窗口的图形信息(各种控件的数量和位置)和ORM。在这两种情况下,通过代码生成进行集成都使操作数据变得更加容易,因为您不必经过额外的“特殊”步骤即可使用它们。

在使用原始(1984)Mac时,对话框和窗口定义是使用resouce编辑器创建的,该编辑器将数据保留为二进制格式。与“二进制格式”是Pascal相比,在应用程序中使用这些资源更加困难。

因此,不,源代码生成不是反模式,它可以使数据成为应用程序的一部分,从而使使用变得更容易。


0

当代码产生的成本超过实现的成本时,它就是一种反模式。这种情况发生在从A到B的生成时,其中A与B几乎是相同的语言,但是有一些小的扩展,只需用A编码即可完成,而无需花费所有定制工具为A到B建立阶段。

这种权衡更不利于在没有元编程功能(结构宏)的语言中生成代码,因为通过外部文本处理来实现元编程的复杂性和不足之处。

权衡取舍还可能与使用量有关。语言A可能与语言B完全不同,但是整个项目及其自定义代码生成器仅在一个或两个小地方使用A,因此总的复杂程度(A的小部分加上A-> B代码生成器,加上周围的构建阶段)超过了用B完成的解决方案的复杂性。

基本上,如果我们致力于代码生成,我们可能应该“变大或变笨”:使它具有实质性的语义,并大量使用它,或者不要打扰。


为什么要删除“何时Bjarne Stroustrup首次实现C ++ ...”段落?我认为这很有趣。
Utku

@Utku其他答案从编译整个复杂的语言的角度来解决,该语言将项目的其余部分全部编写成该语言。我认为它不能代表大多数所谓的“代码生成”。
卡兹(Kaz)

0

我没有清楚地看到这句话(我确实看到它被一两个答案触及了,但似乎不太清楚)

生成代码(就像您所说的那样,就好像它是数据一样)不是问题,这是一种将编译器用于次要目的的方法。

编辑生成的代码是您将遇到的最阴险,邪恶,恐怖的反模式之一。不要这样做。

充其量,编辑生成的代码会将大量不良代码引入您的项目中(整个代码集现在是真正的源代码-不再是数据)。最糟糕的是,拉入程序的代码是高度冗余的,名称不正确的垃圾,几乎完全无法维护。

我想第三类是您使用过一次的代码(gui生成器?),然后进行编辑以帮助您入门/学习。这只是每个步骤的一小部分-可能是一个不错的开始,但是您的GUI生成器将针对使用“可生成的”代码,对于您作为程序员而言,这不是一个很好的开始-此外,您可能会试图再次将其用于第二个GUI,这意味着将多余的SOURCE代码提取到您的系统中。

如果您的工具足够聪明,可以禁止对生成的代码进行任何编辑,那就去吧。如果没有,我将其称为最糟糕的反模式之一。


0

代码和数据都是:信息。

数据就是您所需要的形式(和价值)的信息。代码也是信息,但采用间接或中间形式。本质上,代码也是数据的一种形式。

更具体地说,代码是信息,这些信息使机器可以自己摆脱人们对信息的处理。

最重要的动机是使人们摆脱信息处理的压力。中间步骤是可以接受的,只要它们使生活变得轻松即可。这就是存在中间信息映射工具的原因。像代码生成器,编译器,编译器等。

为什么要生成源代码?为什么不使其成为可以接受参数并对其执行操作的函数呢?

假设有人为您提供了这样的映射功能,但其实现对您来说却很模糊。只要函数按承诺工作,您是否会在内部生成源代码?


0

如果可以生成某些东西,那么那是数据,而不是代码。

由于您稍后在代码中规定数据是数据,因此您的主张简化为“如果可以生成某些内容,则该内容不是代码”。那么,您会说C编译器生成的汇编代码不是代码吗?如果碰巧恰好与我手工编写的汇编代码相符怎么办?如果您愿意的话,欢迎您去那里,但我不会和您一起去。

让我们从“代码”的定义开始。在不太技术的情况下,出于讨论目的,一个很好的定义是“用于执行计算的机器可操作指令”。

鉴于此,对源代码生成的整个想法不是一种误解吗?

好吧,是的,您的初始主张是无法生成代码,但我拒绝该主张。如果您接受我对“代码”的定义,那么代码生成一般就不会有概念上的问题。

也就是说,如果有某个东西的代码生成器,那么为什么不将该东西变成一个可以接收所需参数并执行“将要生成的”代码将要执行的正确操作的适当函数呢?

嗯,这是一个完全不同的问题,是关于采用代码生成的原因,而不是其本质。您正在提出一种替代方案,而不是编写或使用代码生成器,而是编写一种直接计算结果的函数。但是用什么语言呢?任何人都直接用机器代码编写的日子已经一去不复返了,如果您用任何其他语言编写代码,那么您将依靠编译器和/或汇编器形式的代码生成器来生成实际运行的程序。

那么,为什么您更喜欢用Java或C或Lisp或其他语言编写?甚至汇编程序?我断言,这至少部分是因为这些语言为数据和操作提供了抽象,从而使表达要执行的计算的细节变得更加容易。

大多数高级代码生成器也是如此。典型的案例可能是扫描器和解析器生成器,例如lexyacc。是的,您可以直接使用C或您选择的其他某种编程语言(甚至包括原始机器代码)编写扫描器和解析器,有时也可以。但是对于任何非常复杂的问题,使用高级的专用语言(例如lex或yacc)会使手写代码更易于编写,阅读和维护。通常也要小得多。

您还应该考虑“代码生成器”的确切含义。我认为C预处理和C ++模板的实例化是代码生成中的练习。你反对这些吗?如果没有,那么我认为您需要执行一些心理体操工作,以合理地接受这些内容,但拒绝其他形式的代码生成。

如果这样做是出于性能原因,那么这听起来像是编译器的缺点。

为什么?您基本上认为,应该有一个通用程序,用户向其提供数据,有些程序被归类为“指令”,而另一些程序被归类为“输入”,然后该程序将继续执行计算并发出更多的数据,我们称之为“输出”。(从某种角度来看,可以将这样的通用程序称为“操作系统”。)但是为什么您认为编译器在优化通用程序方面应该与在优化更专业的程序方面一样有效。程序?这两个程序具有不同的特性和不同的功能。

如果要完成两种语言之间的桥梁,那么这听起来像缺少接口库。

您说好像拥有通用度接口库一定是一件好事。也许可以,但是在许多情况下,这样的库很大,难以编写和维护,甚至可能很慢。如果实际上不存在这种野兽可以解决眼前的特定问题,那么当代码生成方法可以更快,更轻松地解决问题时,您是谁坚持要创建一个野兽呢?

我在这里想念什么吗?

我认为有几件事。

我知道代码也是数据。我不明白的是,为什么要生成源代码?为什么不使其成为可以接受参数并对其执行操作的函数呢?

代码生成器将用一种语言编写的代码转换为使用另一种通常是较低级语言的代码。然后,您要问的是,人们为什么要使用多种语言编写程序,尤其是为什么他们可能想要混合主观不同级别的语言。

但是我已经谈到了。一个人为特定任务选择一种语言,部分是基于该任务的清晰度和表现力。由于较小的代码平均具有较少的错误并且更易于维护,因此至少在大规模工作中也倾向于使用高级语言。但是,复杂的程序涉及许多任务,通常可以用一种语言更有效地解决其中的一些任务,而用另一种语言更有效或更简洁地解决其他任务。使用正确的工具完成工作有时意味着使用代码生成。


0

在您的评论范围内回答问题:

编译器的职责是采用以人类可读形式编写的代码,并将其转换为机器可读形式。因此,如果编译器无法创建有效的代码,则编译器将无法正确执行其工作。错了吗

编译器永远不会为您的任务而优化。这样做的原因很简单:它经过优化可以执行许多任务。这是许多人用于完成许多不同任务的通用工具。一旦知道您的任务是什么,就可以以特定于域的方式处理代码,从而权衡编译器无法做到的事情。

举例来说,我开发了可能需要分析师编写一些代码的软件。他们可以用C ++编写算法,并添加他们依赖的所有边界检查和提示技巧,但这需要对代码的内部工作有很多了解。他们宁愿写一些简单的东西,然后让我抛出一个算法来生成最终的C ++代码。然后,我可以做一些奇特的技巧来最大化性能,例如静态分析,这是我永远都不会期望我的分析师经历的。代码生成使他们能够以特定于域的方式进行编写,这使他们比任何通用工具都更容易将产品推向市场。

我也做了完全相反的事情。我还要完成另一项任务,即“不生成代码”。我们仍然希望使使用该软件的人员的工作变得轻松,因此我们使用了大量的模板元编程来使编译器即时生成代码。因此,我只需要通用的C ++语言即可完成工作。

但是,有一个陷阱。保证错误可读是非常困难的。如果您曾经使用过模板元编程代码,那么您会知道,一个无辜的错误会生成一个错误,该错误需要100行无法理解的类名和模板参数来了解出了什么问题。这种效果如此明显,以至于建议的语法错误调试过程是“滚动错误日志,直到看到自己的文件第一次出现错误为止。转到该行,斜视一下,直到意识到自己要做什么为止。做错了。”

如果我们使用代码生成功能,我们将拥有更强大的错误处理能力以及人类可读的错误。这就是生活。


0

有几种使用代码生成的不同方式。它们可以分为三大类:

  • 生成一种语言的代码,作为编译过程中某个步骤的输出。对于典型的编译器,这将是一种较低级的语言,但对于编译为JavaScript的语言,则可能是另一种高级语言。
  • 在编译过程中,以源代码语言生成或转换代码。这就是宏的作用。
  • 使用与常规编译过程分开的工具生成代码。此代码的输出是与常规源代码一起作为文件存储并与之一起编译的代码。例如,可能从数据库模式自动生成ORM的实体类,或者可能从接口规范(例如SOAP的WSDL文件)生成数据传输对象和服务接口。

我猜您正在谈论第三种生成的代码,因为这是最有争议的形式。在前两种形式中,生成的代码是一个中间步骤,它与源代码非常清晰地分开。但是在第三种形式中,源代码和生成的代码之间没有形式上的分隔,只是生成的代码可能带有注释,即“请勿编辑此代码”。仍然存在开发人员编辑生成的代码的风险,这确实很丑陋。从编译器的角度来看,生成的代码是源代码。

但是,这种形式的生成代码在静态类型的语言中可能确实有用。例如,当与ORM实体集成时,为数据库表使用强类型的包装器真的很有用。当然,您可以在运行时动态处理集成,但是会丢失类型安全性和工具支持(代码完成)。静态类型语言的一个主要优点是在编写类型时(而不只是在运行时)对类型系统的支持。(相反,这种类型的代码生成在动态类型的语言中不是很普遍,因为与运行时转换相比,在这种语言中它没有任何好处。)

也就是说,如果有某个东西的代码生成器,那么为什么不将该东西变成一个可以接收所需参数并执行“将要生成的”代码将要执行的正确操作的适当函数呢?

因为类型安全和代码完成是您在编译时(以及在IDE中编写代码时)想要的功能,但是常规函数仅在运行时执行。

但是,可能有一个中间立场:F#支持类型提供程序的概念,它基本上是在编译时以编程方式生成的强类型接口。这个概念可能会替代代码生成的许多用途,并提供更清晰的关注点分离。


0

处理器指令集从根本上是必须的,但是编程语言可以是声明性的。运行以声明性语言编写的程序不可避免地需要某种类型的代码生成。如本答案及其他中所述,以人类可读的语言生成源代码的主要原因是要利用编译器执行的复杂优化。


-3

如果可以生成某些东西,那么那是数据,而不是代码。

您弄错了方法。它应该读

如果可以将某些内容输入到可解释器的生成器中,那么该内容就是代码,而不是数据。

它是该编译阶段的源格式,接收器格式仍是代码。


1
源代码定义错误。源代码主要供正在使用它的人类使用(仅凭事实定义它,另请参阅FSF 是什么免费软件)。用生成的汇编代码gcc -fverbose-asm -O -S不是源代码(不是唯一的还是绝大部分不是数据),即使它是某种文本形式,也总是馈入GNU as并有时被人类读取。
巴西尔·斯塔林凯维奇

同样,许多语言实现都可以编译为C代码,但是生成的C并不是真正的源代码(例如,人类无法轻易使用)。
巴西尔·斯塔林凯维奇

最后,您的硬件(例如,AMD或Intel芯片或计算机主板)正在解释机器代码(显然不是源代码)。顺便说一句IBM1620具有键盘可打字(BCD)机器代码,但是事实并没有使其成为“源代码”。所有代码都不是源代码。
巴西尔·斯塔林凯维奇

@BasileStarynkevitch啊,你把我送到了那里。我不应该过多地压缩我的机智声明,否则它们会改变其含义。正确,代码应该是进入第一个编译阶段的最原始的代码。
Bergi

没有源代码是人类的代码。定义音乐(相对于声音)既困难又主观。尝试查找使用它的软件不是问题。
巴西尔·斯塔林凯维奇
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.