在Anthony Aaby的“ 语义学编程语言简介”部分中,他做了以下观察:
编程语言的语义方面的许多工作是由尝试构建和理解命令式程序(带有赋值命令的程序)时遇到的问题所激发的。由于赋值命令会将值重新分配给变量,因此赋值可能在程序的远处产生意外的影响。
这让我印象深刻,承认允许副作用会激发语义工作的大部分。
编程语言中副作用的存在如何影响将程序映射到计算模型的能力?是否有管理状态的方法可以改善此过程,同时仍然允许副作用?
在Anthony Aaby的“ 语义学编程语言简介”部分中,他做了以下观察:
编程语言的语义方面的许多工作是由尝试构建和理解命令式程序(带有赋值命令的程序)时遇到的问题所激发的。由于赋值命令会将值重新分配给变量,因此赋值可能在程序的远处产生意外的影响。
这让我印象深刻,承认允许副作用会激发语义工作的大部分。
编程语言中副作用的存在如何影响将程序映射到计算模型的能力?是否有管理状态的方法可以改善此过程,同时仍然允许副作用?
Answers:
在查尔斯的答案的基础上,编程语言理论的主要困难在于,程序等效性的自然概念通常不是严格的相等性,无论是在您可以给出的最直接的数学语义上还是在底层的机器模型中。例如,考虑以下类似Java的代码:
Object x = new Object();
Object y = new Object();
... some more code ...
因此,该程序创建一个对象并将其命名为x,然后创建另一个名为y的对象,然后继续执行更多代码。现在,假设程序员决定翻转这两个对象的分配顺序:
Object y = new Object();
Object x = new Object();
... some more code ...
现在,问一个问题:重构会改变程序的行为吗?一方面,在底层计算机上,x和y将在程序的两次运行中分配在不同的位置。因此,从这个意义上说,程序的行为有所不同。
但是在类似Java的语言中,您只能测试引用是否相等,而不能测试顺序,因此这是“更多代码” 无法观察到的差异。结果,大多数程序员希望颠倒顺序不会对最终答案产生任何影响,并且大多数编译器作者希望能够在此基础上执行重新排序和优化。(在另一方面,在一个类似C语言,你可以比较指针排序,首先他们铸造整数,因此该重新排序也并非一定保留观察到的行为。)
语义学的中心问题之一是回答两个程序何时可观察到相等的问题。由于我们的观察概念取决于编程语言的功能,因此我们得出这样的定义:“当没有任何客户端程序可以基于接收这些程序作为输入来计算不同答案时,两个程序是等效的”。所有客户端程序的量化是使这个问题变得困难的原因-似乎您最终不得不对所有可能的客户端程序说一些话,对两个特定的代码说了些话。
指称语义的窍门是给出一种数学解释,以使您避免这种普遍的量化-您说一段代码的含义是一些数学值,然后通过检查它们是否在数学上相等或比较来对它们进行比较。不。这是局部的(即组成的),不涉及所有可能客户的量化。(当然,您确实需要证明指称语义意味着它在听起来上是对等的。当它完成时-当指称相等与上下文对等完全相同时,我们说语义是“完全抽象的”。)
但是,这意味着您需要确保指称语义验证这些等效性。因此,对于此示例,如果您想为这种类似于Java的语言提供一种指称语义,则不仅需要确保调用new会占用一个堆并使用新创建的对象还给您一个新的堆,还应确保其含义程序的输入在输入堆的所有排列下都是不变的。这可能涉及相当复杂的数学结构(例如,在这种情况下,以确保所有内容以适当的排列组为模的形式工作)。
当然,有一些方法可以处理(表示式)语义中的效果。例如,我们可以使用Eugenio Moggi的想法,即计算效果是单子(在Haskell的设计中也曾使用过这种想法)。问题之一是单子难以组合。戈登·普洛特金(Gordon Plotkin)和约翰·鲍尔(John Power)提出将莫吉的单子形式改进为Lawvere理论(也称为代数理论),其中包括代数效应(最常见的效应是代数,例如状态,I / O,非确定性,但延续是不)。有关全面的处理,请参阅Matija Pretnar的论文。
我还应该提到由弗兰克·奥莱斯(Frank Oles)和约翰·雷诺兹(John Reynolds)开发的局部状态的可能世界语义(抱歉,找不到更好的链接,这是1982年的文字),它早于Moggi的单子。他们使用预滑轮的类别来提供类似于algol的语言的语义,该语言正确地对局部状态的许多方面进行了建模(但并非所有模型都可以建模,但我认为该模型允许回弹,但也许我的记忆对我有误)。
马蒂亚斯·费莱森(Matthias Felleisen)在“控制与状态的句法理论”系列中提出了一种令人信服的语义学副作用解决方案。
该工作产生了CESK机器,这是一个简单的抽象机器框架,能够对功能,面向对象,命令式甚至逻辑语言进行简洁地建模。CESK框架不仅可以处理副作用,还可以处理“复杂”的控制结构,例如异常,连续性,惰性以及甚至线程。
大约二十年来,CESK机器以及更广泛的小步操作语义已经成为编程语言理论的事实上的标准。
简而言之,CESK机器是一台小步机器,具有四个组件来描述每个机器状态:控制字符串(程序计数器的概括),环境,存储(也称为堆)和当前延续。
环境将变量映射到地址。商店将地址映射到值。
这使对可变变量建模变得简单:只需更改其地址处的值即可。
它还使指针建模和动态分配变得容易:只需使存储地址成为一流的值即可。
以类似的方式,一流的延续来自于使它们成为可寻址的值。
编程语言中副作用的存在如何影响将程序映射到计算模型的能力?
它不一定使它变得困难,但是它确实限制了可以从较小的表达式构造较大表达式的语义的方式。例如,如果要为一种语言提供一种Scott风格的指称语义,以便允许将高阶函数分配给全局引用,它可能会与某些其他编程结构发生非常恶劣的交互。
造成问题的原因不只是状态之类的副作用。简单的命令式语言(例如Dijkstra的受保护的命令语言)具有这些副作用,并且具有良好的语义。即使在没有副作用的情况下,lambda微积分的扩展也会带来编程语言所期望的操作语义的麻烦:最早,Plotkin的PCF相对较早地获得了指称模型,但语义并不完全抽象,这意味着指称语义过于笼统,与其操作语义不完全对应。PCF最终在1980年代后期获得了带有游戏语义的完全抽象的指称语义,这与Scott的顺序理论语义完全不同。并发仍未得到充分充分的指称治疗。
许多人质疑这种语义的重要性。我们始终可以提供某种操作语义,即使“语义”只是程序源以及已经编译并运行该程序的某些计算机的名称:出于这个原因,Strachey谴责了操作语义。但是Plotkin的结构化操作语义学表明了如何将操作语义学与机器模型分开,而Pitt的工作表明了这种语义学如何能够支持关于程序语言和编程语言的类似推理。因此,操作语义是指代语义的可行替代方法,并且已成功应用于大量编程语言(例如Standard ML)。
是否有管理状态的方法可以改善此过程,同时仍然允许副作用?
在某种程度上,提供语义的困难与提供功能强大的编程语言以人们期望的方式表现的困难相对应。务实的设计决策(例如,通常通过消息传递并发避免使用全局状态和并发)使提供语义更加容易。