“语法”和“语法糖”有什么区别


45

背景

语法糖上的Wikipedia页面指出:

在计算机科学中,语法糖是编程语言中的语法,旨在使事物更易于阅读或表达。它使人类可以使用这种语言“变甜”:可以更清楚,更简洁地表达事物,或者以某些人可能喜欢的替代方式表达事物。

我不太了解语法糖和语法之间的区别。

我赞赏含糖版本可以更清晰,更简洁,甚至可以简化一些观点。但是我觉得在某种程度上,所有语法实际上都是这样做的,以形成对代码进行编译的抽象。

在同一Wikipedia页面上:

语言处理器(包括编译器,静态分析器等)通常在处理之前将加糖的结构扩展为更基本的结构,此过程有时称为“反加糖”。

作为思想练习,如果我在此语句中将“经常”表示为“始终”:如果区别仅在于编译器在进入下一阶段之前是否“降低语法”,那么不了解内部条件的编码器又将如何?的编译器知道(或关心)什么是Sugar'd语法?

该网站上一个非常相关的问题“语法糖的严格定义?” 有一个开始的答案

恕我直言,我认为您无法对语法糖进行定义,因为该短语是BS,很可能被那些在“真实操作系统”上使用“真实工具”谈论“真实程序员”的人使用。

哪个可能向我表明,使用该语言的编码人员也许并没有太大的区别?也许差异仅是编译器编写者可以感知的?尽管在某些情况下使用该语言来了解语法糖背后的含义对编码人员有帮助。(但实际上,有关该主题的任何论述都倾向于使用“火焰诱饵”这个词?)

问题的核心

所以...问题的简短版本:

  • 语法和语法糖之间有真正的区别吗?
  • 对谁重要?

思考的额外食物

关于主题矛盾的奖励:

在Wikipedia页面上给出了一个示例:

例如,在C语言中,a[i]表示法是*(a + i)

而上述链接问题的另一个答案是关于同一示例的:

现在考虑a[i] == *(a + i)。考虑任何以任何实质性方式使用数组的C程序。

并总结为:

[]符号有助于这种抽象。这不是语法糖。

同一示例的相反结论!


9
在回答所提问题的过程中,我解释了语法糖另一种语法(对于该语言中已经存在的某些语法),它使表达一些常见的惯用法更容易,但没有引入新的语义。从这个意义上说,语法糖取决于上下文(取决于使用它的语言):相同的符号可以是一种语言中的正常语法,而另一种语言中的语法糖。这个说明对您有帮助吗?
Giorgio

7
一个问题是,有些人似乎认为“语法糖”是一个肮脏的词,而实际上并非如此。您添加到系统中的每个函数或类都可以被视为“语法糖”,因为它们使已经可以用该语言表达的想法变得更容易表达了。
Joris Timmermans

显然,这对您很重要。
SShaheen

11
最后,这仅仅是电能上的合成糖。
Anthony Pegram

Answers:


34

主要区别在于语法是用某种语言定义的语法,以使您可以使用某些功能。一旦可以使用该功能,任何其他可以使您执行相同操作的语法都被视为糖。当然,这会碰到关于两种语法中的哪一种是奇怪的情况,特别是因为并不总是很清楚先出现哪种语法。

在实践中,语法糖仅用于描述添加到语言中以促进易用性的语法,例如将infix lhs + rhs映射为lhs.Add(rhs)。我认为C的数组索引是语法糖。

这很重要,因为优雅的设计往往会限制重复的次数。某些人认为需要(或至少想要)语法糖是设计失败的标志。


“一旦可以使用该功能,任何其他可以使您执行相同操作的语法都被视为糖。”:这不足以拥有语法糖,因为那样的话,任何其他计算相同功能的程序都将成为语法糖。 。加糖程序和减糖程序必须具有相同的语义
Giorgio

3
@Giorgio“这两个程序计算相同的功能”与“这两个程序具有相同的语义”有何不同?除此之外,您将重点放在程序上,而不是语法元素上。整个程序不能是语法糖。运算符,一种陈述等可以是语法糖。

@ Giorgio-的确,我正在将具有不同语义的相似功能视为不同的功能。是的,可能会有跳跃的感觉,就像编写自己的具有相同语义的编译器并称其为“糖”一样。坦白说,对于这样一个非正式的概念,正式定义不会发生。
Telastyn

1
@Giorgio同样,我的牛肉与您的等效性并不相同,但是您尝试将语法糖的概念应用到程序中。该术语并不表示整个程序!根据您的定义,它要么只是原子构造块,要么只是简短的程序片段(第一个片段似乎覆盖了80%以上,但我不确定它是否涵盖了通常认为的语法糖)。

3
@Giorgio例如,整个程序;-)或任何足以使两个表述都包含几个不相关的语言关键字的东西。的东西,我从来没有被视为语法糖是if (a) return blah; ...result_type res; if (a) res = blah; else {...}; return res;。您需要假定使用特定的语言,并获得极高的语言水平(通常是小步操作语义),才能发现这些程序之间的任何差异。

10

语法是语言处理器用来理解语言结构含义的工具。被认为是语法糖的构造也必须由语言处理器解释,因此是语言语法的一部分。

将语法糖与语言的其余语法区分开的原因在于,有可能从语言中删除语法糖,而不会影响可以用该语言编写的程序。

为了给出更正式的定义,我会说

语法糖是语言语法的那些部分,其作用是根据语言中的其他语法结构定义的。

这绝不是要贬低语法糖或使用它的语言,因为语法糖的使用通常会导致其意图更易于理解的程序。


“没有明确的说法'这是语法糖,那就是语法',因为语法糖是语言语法的一部分。”:错。它是语言设计者,它为语言中已经存在并具有语法的某些结构引入了一些语法作为语法糖。语法糖通常是临时的(较不通用),但更易读/使用方便。
Giorgio

@乔治:我改写了我的第一段,因为这有点尴尬。但是我不同意语法糖通常是临时添加的。最广为人知的语法糖可能是C的->运算符,而且我认为该运算符的通用性不及其他任何运算符。
Bart van Ingen Schenau

1
@ Giorgio:在那种情况下,所有不直接映射到单个汇编指令的东西都应视为语法糖,因为它总是可以分解为更简单的部分(包括函数)。我认为您不会发现有很多人对此表示赞同。
Bart van Ingen Schenau

1
“临时”是指在特殊的受限情况下使用。该术语通常没有负面含义。临时解决方案一词确实具有否定含义,因为通常需要通用解决方案,而不是特定解决方案(以避免一次又一次地解决类似问题)。
Giorgio

1
由于“临时解决方案”通常很糟糕,因此似乎“临时”本身就意味着不好。但是还存在“即席多态” AKA函数重载(en.wikipedia.org/wiki/Ad_hoc_polymorphism),这还不错。至少在意大利语(我的母语)中,拉丁语“ ad-hoc”的使用本身并不是负面的。如果它确实有否定的英语含义,请原谅我的无知。
Giorgio

6

其他答案没有提到一个关键概念: 抽象语法;没有它,“语法糖”一词是没有任何意义的。

抽象语法定义了语言的元素和结构,以及如何将该语言的短语组合起来以构建更大的短语。抽象语法独立于具体语法。据我了解,术语“语法糖”是指具体语法。

通常,在设计语言时,您需要为抽象语法的每个术语创建具体的语法,以便人们可以使用纯文本以您的语言编写代码。

现在,假设您为foo创建了一个笨拙的具体语法。用户抱怨,您实现了一种新的具体语法来表示相同的抽象语法。结果是您的抽象语法和语义没有改变,但是对于同一个抽象语法术语,您现在有了两种具体的语法。

我相信,这就是人们所说的“语法糖”的意思-这些变化只会影响具体的语法,而不会影响抽象的语法或语义。

因此,“语法糖”和“具体语法”之间的区别现在很明显。对我来说。:)

这种解释还有助于解释Alan Perlis当他说“语法糖导致分号的癌症”时可能意味着什么:世界上所有具体的语法糖都不能解决弱抽象语法,并且您花费的所有努力都加上了糖您无需花费精力来处理真正的问题-抽象语法。


我还应该指出,这仅是我的看法;我只相信它,因为这是我能想到的唯一解释。


1
我还考虑过比较抽象语法和具体语法,但是我不确定这是否总是可以解释语法糖。例如,在C,给定一个指针p到含有字段的结构体x中,执行表达式(*p).xp->x具有相同的抽象语法?但是我认为,如果一切都归结为抽象语法和具体语法,那将是很棒的。
Giorgio

不幸的是,@ Giorgio,我对C的了解不足,无法自信地说明其中一个是否是另一个的语法糖,或者它们的抽象语法树是什么样子。甚至C的规范实际上定义了抽象语法。:(

1
可以将第一个表达式解析为以a .为根的树。根的左子节点是a *,其子节点是p。根的右子节点是x。对于第二个表达式,抽象语法树->的根有a ,根有两个孩子pand x。我试图找出以不同方式解析两个表达式以便它们具有相同的抽象语法树是否有意义,但是目前我不知道如何。
Giorgio

3
我不认为这个解析树,即使经常被描述为AST,也与Matt所说的抽象语法是一样的。这两个表达式具有相同的作用(将指针类型转换为引用类型,然后获取对成员的引用x
无用的

1
“两个表达式都具有相同的作用(将指针类型转换为引用类型,然后获取对成员x的引用)”:这是语义,而不是抽象语法。抽象语法是指省略无用的具体语法细节(例如(;),以生成反映程序实际结构的树(请参见en.wikipedia.org/wiki/Abstract_syntax_tree)。但是,在抽象语法中,关于程序的语义还没有说(尚未)。
Giorgio 2013年

4

语法糖是语言语法的子集。基本思想是说同一件事的方法不止一种。

是什么使得它很难说哪件是语法糖和作品是“纯粹的语法”是语句,如“这是很难说哪种形式来第一”或“这是很难知道的语言的作者的意图哪种方式”或“这是决定哪种形式更简单”。

在特定编译器或解释器的框架内提出问题的原因是,可以轻松决定哪些部分是纯糖还是含糖。纯语法是编译器直接转换为机器代码或解释器直接响应的内容。在这些直接的事情发生之前,糖是首先变成其他语法的事情。取决于实现方式,这可能与作者的意图甚至语言规范要求的相同或不同。

在实践中,这是确定问题的现实的方式。


此答案错误地将语法的实现与是否糖化了联系。我从未见过关于语言元素是否为“糖”的争论,而该论点本身与该元素的实现方式有关。纯粹是关于它是否在语法上复制一个不同的,丑陋的元素。
GreenAsJade

3

确实,您在Wikipedia上的第一句引述说,这一切“……使事情更易于阅读……”,“……人类更易使用的……”。

在书面形式中,诸如“不要”或“没有”之类的缩写形式可以被视为语法糖。


考虑(1)“我应该现在就去”与(2)“我应该现在就去”:(1)是(2)的语法糖吗?
Giorgio

1
@Giorgio我不会。
ozz 2013年

由于可读性(“应该”比“应该”不易读)或由于含义(“应该”和“应该”具有不同的含义)。
Giorgio

@ Giorgio足够公平:)
ozz 2013年

1
嗯,我明白了:)我猜想更像是一种直觉,我认为在这个例子中,没有一个比另一个更具可读性了
ozz 2013年

3

通常,语法糖是语言的一部分,可以由语言的现有部分(语法)表示,而不会失去一般性,但可能会失去清晰度。有时,编译器具有显式的减糖步骤,该步骤可转换由源代码创建的AST,并应用简单的步骤来删除与Sugar对应的节点。

例如,Haskell具有针对monad的语法糖,并递归应用以下规则

do { f }            ~> f
do { g; h }         ~> g >> do h
do { x <- f; h }    ~> f >>= \x -> do h
do { let x = f; h } ~> let x = f in do h

现在,它到底意味着什么无关紧要-但是,您可以看到LHS上的特殊语法可以转换为RHS上更基本的内容(即函数应用程序,lambda和lets)。此步骤可以使两全其美:

  1. LHS的语法对于程序员(语法糖)而言更容易以更清晰的方式表达现有思想
  2. 但是,由于RHS构造的编译器支持确实已存在于编译器中,因此它不需要在解析器和除杂项之外将其视为特殊内容(错误报告除外)。

类似地,在C中,您可以想象到废除重写规则(由于运算符重载等原因,对于C ++而言并非如此):

f->h ~> (*f).h
f[h] ~> *(f + h)

您可以想象现在不使用C ->[]使用C使用这种结构编写所有程序。但是,对于程序员来说,使用它会更困难,因为它提供了语法糖(我想在70年代,它也可能简化编译器的工作)。由于您可以从技术上添加以下完全有效的重写规则,因此可能不太清楚:

*f ~> f[0] -- * and [] have the same expressiveness 

语法糖不好吗?不一定-在没有理解更深层含义的情况下,有可能将其用作货物崇拜。例如,以下函数在Haskell中是等效的,但是许多初学者会在不了解语法过多使用的情况下编写第一种形式:

f1 = do x <- doSomething
        return x
f2 = doSomething

此外,语法糖可能会使语言过于复杂,或者过于狭窄而无法使用通用的惯用代码。这也可能意味着语言不够强大,无法轻松完成某些操作-可能是设计使然(不要为开发人员提供尖锐的工具或非常特殊的利基语言,而添加更强大的结构会损害其他目标)或被忽略-后者形式给语法糖一个坏名字。如果该语言功能强大到可以在不添加语法糖的情况下使用其他结构,则认为使用这些结构更为优雅。


3

我认为最明显的例子是C语言中的“ + =”语法。

i = i + 1;

 i +=  1;

做完全相同的事情,并编译成完全相同的机器指令集。第二种形式保存了几个字符输入,但是更重要的是,非常清楚地表明您正在基于其当前值修改值。

我将引用“ ++”后缀/前缀运算符作为规范示例,但意识到这不仅仅是语法糖。无法使用i = i + 1语法在单个表达式中表达++ i和i ++之间的差异。


+ =在类似的情况下可以派上用场a[f(x)] += 1
Florian F

1

首先,我将通过一个具体的例子解决其他一些答案。基于C ++ 11范围的for循环(非常类似于其他各种语言中的foreach循环)

for (auto value : container) {
    do_something_with(value);
}

完全等同于(即的加糖版本)

for (auto iterator = begin(container); iterator != end(container); ++iterator) {
    do_something_with(*iterator);
}

现在,尽管未在语言中添加任何新的抽象语法或语义,但它确实具有实用性。

第一个版本使意图(访问容器中的每个项目)明确。它还禁止异常行为,例如在遍历期间修改容器,或iterator在循环主体中进一步前进,或者使循环条件微不足道。这样可以避免可能的错误源,并且这样做可以减少阅读和推理代码的难度。

例如,第二个版本中的一个字符错误:

for (auto iterator = begin(container); iterator <= end(container); ++iterator) {
    do_something_with(*iterator);
}

给出了过去的错误和未定义的行为。

因此,加糖版本之所以有用是因为它的限制性更强,因此更易于信任和理解。


第二,对原始问题:

语法和语法糖之间有真正的区别吗?

不,“语法糖”是(具体的)语言语法,被认为是“糖”,因为它没有扩展语言的抽象语法或核心功能。我喜欢马特·芬威克对此的回答。

对谁重要?

与其他语法一样,它对语言的用户也很重要,因为它提供了糖来支持(在某种程度上说是祝福)特定的习惯用法。

最后,关于奖金问题

[]符号有助于这种抽象。

这听起来很像语法糖的定义:它使用指针作为数组来支持(并提供语言作者的祝福)。该p[i]表格是不是真的比更严格*(p+i),所以唯一的区别是意图明确的沟通(和轻微的可读性增益)。


首先,您说“ [基于循环的范围]完全等同于(即,其他版本的加糖版本)”。但是然后您说这是不同的,因为它禁止在遍历期间修改容器...并且还有其他差异,使其不仅仅只是您描述的简单语法转换(例如,“容器”是临时的时的行为)。这些差异意味着它不仅是语法糖,对吗?所以在我看来,这可能不是语法糖的好例子。
唐·哈奇

我不是说它等效于 某个通用循环,而是说它等效于给定的特定循环。在特定的循环不会改变容器,并且是一个非常常见的成语-但你仍然需要仔细阅读每一个普通环形告诉它无论是以下是常见的成语,或者一些不寻常的是潜伏在循环体。较新的循环是该惯用语的糖,它既不那么冗长,又更清楚地传达了该惯用语。它不是,而且我从未声称是,糖可以为循环的一般概念加糖。
没用的2016年

0

无论该短语的原始含义是什么,如今它主要是贬义词,几乎总是被表述为“ just”或“ only”语法糖。这只对喜欢以一种难以理解的方式做事并想要一种简洁的方式向其同事辩护的程序员很重要。从他们的角度来看,今天主要使用该术语的人的定义将是:

与其他更广泛使用的语法无关的语法,以便为不太了解该语言的程序员提供帮助。

这就是为什么对于同一句法元素您会得出两个相反的结论。关于数组表示法的第一个示例使用的是该术语的原始肯定含义,类似于Bart的答案。您的第二个示例是捍卫数组表示法,以免受贬义的语法糖的指责。换句话说,它认为语法是有用的抽象而不是拐杖。


我认为该术语的贬义含义是“语法糖不添加任何信息”这一事实。
Giorgio

1
它可能来自句法不添加任何信息的想法,但是这个想法不是事实。关于意图的信息(例如,使用众所周知的习语)仍然是信息。
没用的2013年

@Useless:通过信息,我的意思是程序在使用或不使用语法糖的情况下的行为相同。当然,使用语法糖可以使其更具可读性(它为读者增加了信息/文档),这是一个优势。
Giorgio

虚假。语法糖不是贬义词。任何值得他或她精打细算的程序员都将创建封装和简化更复杂任务的库,而不是到处复制和粘贴相同的代码。这导致行为更好,更可维护的代码。本质上,这就是语法糖通过将常见的但复杂的模式抽象为更简单的语法来实现的。
Craig 2013年

我是说人们通常把这个词当作侮辱,我不是在评论基本做法本身。说“白痴”这个词是普遍的,并不意味着所有人都是白痴。当人们想积极谈论这种做法时,他们通常使用诸如“抽象”或“封装”之类的术语。
Karl Bielefeldt
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.