Self
✍ dations ◷ 2025-08-29 06:17:14 #Self
Self语言,是一种基于原型的面向对象的程序设计语言,也是一个集成开发环境和运行环境,由David Ungar和Randy Smith,最初在1986年于施乐帕罗奥多研究中心设计。Self语言在Smalltalk的基础上,去除了变量,从而彻底体现了一切都是对象的风格。在实现Self系统的过程中,设计研究人员发展出了一种动态自适应编译技术。
Self语言把在概念上精简Smalltalk作为设计原则。它在把消息作为最基本的操作的同时,取消了类的概念,只有对象的概念。它把对象的特性,理解为获取或更改特性的这两种方法,从而把特性的概念简化为方法,即取消了变量及其赋值,并以通过消息来读槽和写槽的方式来取代之。Self提出了特质的概念,用动态绑定(英语:Late binding)实现了委托。
尽管Self系统一次运行在一个进程中,但实际上可以分成两个部分:Self虚拟机和Self世界(Self world)。Self世界是一个Self对象库,Self对象包括数据对象和方法对象,方法对象的代码部分,是用一种指令非常简单的字节码(bytecode)表示的,字节码由Self虚拟机来解释。当Self程序从终端、文件或者图形用户界面输入到系统之中时,Self系统把源程序解析转化为Self世界里的对象。
动态自适应编译技术的采用,提高了Self代码的执行效率。对经常执行的方法,虚拟机将进一步把字节码转化为本机代码。Self虚拟机还提供了一些可供调用的原语(英语:Language primitive),用来实现算术运算、对象复制、输入输出等。
Self也拥有一个图形用户界面Morphic,Self的编程环境,也是基于Morphic来实现的。Self在精简语言概念的同时,也把大量的工作转交给环境来处理,语言中的反射机制也同环境密切相关。
在1986年,David Ungar和Randy Smith在施乐帕罗奥多研究中心,提出了Self语言的最初设计,并在1987年的OOPSLA'87的论文《Self:简单性的能力》中给出了描述,此文在2006年被评为1986年到1996年间三个最有影响的OOPSLA论文之一。
1987年初,Craig Chambers、Elgin Lee和Martin Rinard,在Smalltalk上给出了Self的第一个实验性解释器。1987年夏,Self项目在斯坦福大学正式开始,1988年夏给出了第一个有效率的实现,并发布了1.0和1.1两个版本。1991年初,Self项目移至Sun微系统,并且在1992年发布了2.0版。1993年1月,Self 3.0版发布。
1995年7月,Self 4.0版发布。在这个版本中包括了一个全新的图形用户环境Morphic。在2016年发行了4.3版本并可运行在Mac OS X和Solaris上。在2010年发行了版本4.4,由最初团队的某些人和独立编程者形成的小组开发,它和所有后续版本可以运行在Mac OS X和Linux上。2014年1月发行了4.5版本。2017年5月发行了版本2017.1。
Self的发展基本已经停滞,但在发展Self过程中探索出的一些技术,在其他的系统中得到了应用。在Self的实现中采用的各种编译优化技术,直接导致了Java Hotspot虚拟机的产生;在Smalltalk的一个实现Squeak中,采用了Self图形用户界面Morphic的设计方案,放弃了Smalltalk-80中采用的MVC的方案。Self是对JavaScript编程语言设计有最主要影响者之一。
传统的基于类的面向对象语言,基于了根深蒂固的二元性:
例如,假设类Vehicle
(车辆)的对象有一个“名字”,和进行各种动作的能力,比如“开车上班”和“运送建材”。Bob's car
是类Vehicle
的特定对象(实例),它的“名字”是“Bob's car”。在理论上,你可以向Bob's car
发送消息,告诉它去“运送建材”。
这个例子展示了这种方式的一个问题:Bob的汽车,恰巧是一个跑车,在任何意义上都不能装载和运送建材,但这是建模Vehicle
所必须拥有的能力。通过从Vehicle
创建特殊化的子类,可产生一个更有用的模型;比如创建类SportsCar
(跑车)和类FlatbedTruck
(平板卡车)。只有FlatbedTruck
的实例需要提供“运送建材”的机能;SportsCar
的实例不适合这种工作,它只需要“快速行驶”。但是,这种深入建模在设计期间,需要更多的洞察力,洞察那些可能只在引起了问题时才显现出的事情。
这个问题是在原型(prototype)这个概念背后的动机因素之一。除非你能必然性的预测出一组对象和类,在遥远未来时所要有的质量,你不能恰当的设计好一个类的层级。程序最终需要增加行为,实在是太频繁了,而系统的很多节段将需要重新设计(或重新构建),来以不同的方式迸发出对象。早期的面向对象语言如Smalltalk的实验,显示出这种问题反反复复的出现。系统趋向于增长到一定程度后,就变得非常僵化,因为在编程者的代码下的深层的基本类,简直就像是逐渐变成了一个“错误”;没有变更原来的类的容易方式,就会出现严重的问题。
动态语言如Smalltalk,允许通过周知的按照类的方法进行这种变更;即通过改变类,基于它的对象就可以改变它们的行为。但是,进行这种变更必须非常小心,因为基于相同类的其他对象,可能把它当作“错误行为”:“错误”经常是依赖于场景的,这是脆弱基类(英语:Fragile base class)问题的一种形式。进一步的说,在静态语言如C++中,这里的子类可以从超类分别的编译,对超类的变更实际上可以破坏预编译的子类方法;这是脆弱基类问题的另一种形式,也是脆弱二进制接口问题(英语:Fragile binary interface problem)的一种形式。
在Self和其他基于原型的编程语言中,消除了在类和对象之间的这种二元性。不再有基于某种“类”的一个对象“实例”,在Self中,你可以复制一个现存的对象,并改变它。故而Bob's car
可以通过制作现存的Vehicle
对象的复本来创建,并增加“快速行驶”方法,建模它恰好是一辆保时捷911的事实。
主要用来制作复本的基本对象叫做“原型”。这种技术被称为是一种非常简化的机制。如果一个现存的对象或对象的集合,被证明是个不适当的模型,编程者可以简单的创建有正确行为的一个修改的对象,并转而使用它。使用现存对象的代码不会改变。
下面简要描述Self语言的语法和语义。
文字(英语:Literal (computer programming))(literal)包括:数、用'
包围起来的字符串对象,块和一般的对象。对象文字用圆括号来界定。在圆括号内,对象描述构成自竖杠|
界定的一个槽列表,随后是在这个对象被求值时要执行的代码。例如:
(| 槽1. 槽2 | 一些代码 )
槽(slot)是名字-值对,槽包含到其他对象的引用。槽列表由点号分隔的(可以为空的)一序列的槽描述符组成。在槽列表结束处的点号是可选的。槽描述符(descriptor)有两种:
在Self中没有单独的赋值运算。其他面向对象语言中的访问子方法对应数据槽,变异子方法对应赋值槽。假如myPerson
对象中有个叫做name
的数据槽,则通过myPerson name
,可返回在这个槽中的值;如果它有对应的赋值槽name:
,则通过myPerson name: 'foo'
,可设置数据槽name
的值为'foo'
。
任何槽都可以通过增加星号后缀,来制成父槽。星号不是槽名字的一部分,在将名字与消息进行匹配时候忽略它。例如一个初始化了的可变的点可以定义为:
(| parent* = traits point. x <- 3 + 4. y <- 5.|)
一个对象的代码是以点号分隔的一序列的表达式。尾随的点号是可选的。每个表达式有一系列的消息发送和文字组成。在一个对象的代码中最后的表达式,可以前导着指示返回的^
算符。
一个真正的空对象,指示为(| |)
或简单的()
,它根本不接收任何消息。
方法(method)是除了参数槽及或(英语:And/or)局部槽之外,还包含代码的对象。参数(argument)槽名字开始于一个冒号,它不是槽名字的一部分,在将名字与消息进行匹配时候忽略它。参数槽总是只读的,并且不能对它们指定初始化者。下面例子是计算平方的方法对象:
(| :arg | arg * arg )
一个普通方法(简称方法),是不嵌入到其他代码之中的方法,它只能存放在只读槽中。普通方法总是有一个叫做self
的隐含的父参数槽。Self的普通方法等价于Smalltalk的方法。
如果一个槽包含一个方法,在求值这个槽来响应发来的消息的时候,这个方法对象被浅层复制(英语:Object copying)(clone),从而新建它的一个活动(activation)对象,它包含这个方法的参数槽和局部槽;复制体的self
父槽,初始化为这个消息的接收者;复制体如果有参数槽,将它们初始化为实际参数;在这个新的活动对象的上下文中,执行这个方法的代码。
作为语法约定,参数名字可以直接写在槽名字之后(不带前缀冒号),从而隐含的声明参数槽。例如下面的方法定义:
(| ifTrue: False: = (| :trueBlock. :falseBlock | trueBlock value ).|)
可以等价的定义为:
(| ifTrue: trueBlock False: falseBlock = ( trueBlock value ).|)
返回算符^
的出现或缺席,不影响普通方法的行为,因为普通方法总是会返回它最终的表达式的值。
通过消息访问槽的语法,类似于Smalltalk,有三类消息可以获得:
所有消息都返回结果,所以显式指定的接收者和参数自身可以是其他消息的结果。下面是Self版本的hello world程序例子:
'Hello, World!' print.
组合可以通过使用圆括号来进行强制。在缺乏明确组合的情况下,一元消息具有最高优先级,其次是二元消息,而关键字消息最低。一元消息从左至右复合。二元消息对于同一个算符从左至右结合,例如3 + 4 + 7
被解释为(3 + 4) + 7
,而对于不同的算符没有结合性,例如3 + 4 * 7
是非法的,而必须显式的写为要么(3 + 4) * 7
要么3 + (4 * 7)
。
关键字消息的第一部分必须开始于小写字母,而后续部分都必须开始于大写字母。例如表达式:
5 min: 4 Max: 7
是一个单一的消息min:Max:
,它被发送给5
并具有参数4
和7
,而表达式:
5 min: 4 max: 7
涉及两个消息:第一个消息max:
被发送给4
并接受7
作为它的参数,而接下来消息min:
被发送给5
,并接受4 max: 7
的结果作为它的参数。关键字消息从右至左结合,例如:
5 min: 6 min: 7 Max: 8 Max: 9 min: 10 Max: 11
被解释为:
5 min: (6 min: 7 Max: 8 Max: (9 min: 10 Max: 11))
由于很多消息被发送给当前消息接收者self
,故而可以将self
作为隐含接收者而不需要显式的写出。例如计算点的加法的一个方法:
(| + arg = ( (clone x: x + arg x) y: y + arg y ) |)
可以被无歧义的分析,其含义同于:
(| + = (| :arg | (clone x: ((x + (arg x)))) y: ((y + (arg y))) ). |)
这里出现了三个隐含接收者一元消息clone
、x
和y
。
块是Self的闭包,Self就像Smalltalk,使用“块”用于控制流程和其他职责。块文字的写法,除了方括号替代了圆括号之外,类似于其他对象文字。例如嵌入在下列表达式中的块:
1 to: 5 * i By: 2 * j Do:
一个块文字定义两个对象:一个块数据对象,和它包围的一个块方法对象。
相应的,块求值分为如下两个阶段:
在块中,返回算符^
导致从包含这个块的普通方法中返回控制权,立即终止这个方法的活动,这个块的活动,和在其间的所有活动。这种返回叫做“非局部返回”,因为它可以穿越很多活动。普通方法求值的结果,是非局部返回所返回的值。
在理论上,所有Self对象都是独立实体,Self既没有类也没有元类。对任何特定对象的变更,都不影响任何其他对象,但是在某些情况下,却需要它们有关联。正常的一个对象,只能理解对应于它的局部槽的消息,但拥有一个或更多的指示父(parent)对象的槽,对象可以将任何自身不理解的消息,委托(delegate)给父对象。
Self采用这种方式,处理在基于类的语言中使用继承来担负的责任。委托还可以用来实现一些特征,比如名字空间和词法作用域。通过下面的例子展示委托与传统的类的不同之处:
myObject parent: someOtherObject.
这个句子通过改变与叫做parent
的父槽关联的值,在运行时间改变myObject
的“类”。不同于继承或词法作用域,委托对象可以在运行时间修改。
例如,假定在一个简单的账簿应用中,定义了一个对象叫做“银行帐号”(bank account)。通常创建的这个对象,具有内部的方法,比如说“存款”(deposit)和“取款”(withdraw),和任何它所需要的数据槽,比如说“余额”(balance)。这只是一个原型,它只在使用方式上特殊,因为它恰好是一个全功能的银行帐号。
为“Bob的账户”制作银行帐号对象的复制品(clone),将创建一个新对象,它在起初时完全同于原型。在这种情况下,将复制(copy)包括方法和任何数据的槽。但更常用的解决方案,是首先创建叫做它的特质(trait)对象的一个简单对象,它包含通常与一个类有关的项目。
在这个例子中,“银行账户”将没有存款和取款方法,而是委托给一个父对象来做这些。采用这种方式,可以制作银行帐号对象的很多复本,但是我们仍可以通过改变它所委托的特质对象中的槽,来改变它们全体的行为。
当处在于提示符下键入表达式的场景时,由叫做“大厅”(lobby)的一个对象,引领用户进入Self世界。当创建一个新对象的脚本被读入系统的时候,脚本中的表达式都在大厅的上下文中求值。就是说大厅是这个脚本中所有发送给self
的消息的接收者。
要引用在脚本中的某个现存的对象,必须通过发送一个消息到大厅才可以访问到它。大厅的traits
、globals
和mixins
槽,是从大厅可以访问的对象名字空间的根。大厅的lobby
槽允许大厅自身通过名字来提及。路径名字是一个一元选择子的序列,它描述从大厅到这个对象的路径。路径名字也是可以在大厅的上下文中求值的表达式,它产出这个对象。
例如,原型列表的完全路径名字是globals list
。因为globals
是父槽,它可以从路径名字表达式中省略,生成简短路径名字list
。大厅的traits
不是父槽,特质对象的名字必须开始于前缀traits
,因此列表的特质对象必须称呼为traits list
。
不是所有对象都有路径名字,只有那些从大厅可以到达的对象才有,这些对象称为“周知的”。大厅向用户提供三类对象:
在大厅的defaultBehavior
槽中,定义了系统中大多数对象所继承的缺省行为。
有两个消息与对象复制有关:
考虑一个图形用户界面有关的例子:
(desktop activeWindow) draw: (labelWidget copy label: 'Hello, World!').
首先进行的是desktop activeWindow
,它从desktop
(桌面)对象拥有的一个窗口列表中,返回activeWindow
(活动窗口)。按从内向外从左至右的次序,接着是labelWidget copy label: 'Hello, World!'
,通过copy
消息制作labelWidget
(标签组件)对象的一个复本,接着向它发送一个消息,将Hello, World!
放入它的叫做label
(标签)的槽中。最后将返回的这个组件,发送到这个活动窗口的draw
(绘制)槽中。
在Self中的对象,可以通过包括新加的槽来修改。这可以通过被推荐使用的图形编程环境来做,或者直接使用原语_AddSlots:
。原语与正常关键字消息有相同的语法,但是它的名字开始于下划线字符。给_AddSlots:
的参数是一个对象文字,它的槽将被复制进入接收者。例如,在大厅中新增叫做newObject
的对象,并初始化这里举出的它的叫做entries
的槽,采用如下这样的表达式:
_AddSlots: (| newObject = (| entries <- list copy ... |) |)
因为_AddSlots:
原语未指定接收者,这里的消息隐含接收者self
是大厅,由它来理解产生初始值的消息list copy
,list
是周知的原型对象。
在下面的例子中,将基于类语言中叫做Vehicle
(车辆)的一个简单类,重新构造为Self中的vehicle
对象,从而能够区分出在轿车和卡车之间共有的行为:
_AddSlots: (| vehicle <- (|parent* = traits clonable|) |).
在大厅中创建了一个叫做vehicle
的槽,它的值是一个对象文字,同时还创建了一个叫做vehicle:
的赋值槽。作为vehicle
槽初始值的对象文字,也就是新建的叫做vehicle
的对象,包括了一个单一的父槽parent
,没有相应的parent:
,它委托了traits clonable
对象,这个顶层的特质对象可以理解与复制有关的消息。
然后向这个新建对象继续增加name
槽和相应的name:
槽:
vehicle _AddSlots: (| name <- 'automobile'|).
在大厅中从vehicle
对象创建一个sportsCar
(跑车)对象,并接着向sportsCar
增加vehicle
没有的一个新的方法槽driveToWork
(开车上班):
_AddSlots: (| sportsCar <- vehicle copy |).sportsCar _AddSlots: (| driveToWork = ("这个方法的代码") |).
在大厅中从sportsCar
对象创建一个porsche911
(保时捷911)对象,接着向新建对象porsche911
发送一个消息改变它的name
槽的值:
_AddSlots: (| porsche911 <- sportsCar copy |).porsche911 name: 'Bobs Porsche'.
对象porsche911
与它的原型对象sportsCar
,仍有着完全相同的槽,但是其中的一个槽有着不同的值。
Self的一个特征,是它基于了早期Smalltalk系统所用的某种虚拟机系统。就是说,程序不是像C语言中那样的独立实体,而是需要它们的整体内存环境来运行。这要求应用程序被装加载保存内存的大块(英语:Chunk (information))(chunk)之中,这叫做“快照”或映像(英语:System image)。这种方式的缺点,是映像有时很大并且笨重;但是调试一个映像,经常被调试一个传统程序要简单,因为运行时状态更容易检查和修改。在基于源代码和基于映像的开发之间的不同,是类似于在面向类的和面向原型的面向对象编程之间的区别。
此外,环境是为了让在系统之中的对象能快速和可持续的变更而定制的。重新构建一个“类”设计,就像从现存的祖先拖动出来方法放入新造的之中一样容易。简单任务像测试方法,可以通过制作复本来处理,拖动方法进入这个复本,接着变更它。不同于传统系统,只有变更了的对象有新代码,不需要重建任何东西来测试它。如果这个方法有效,可以简单的把它拖动回祖先之中。
Self的VM实现的性能,在某些测试之中大约是优化的C程序速度的一半。这是通过即时编译技术达到的,它是在Self研究中首创并改进的,能够使高级语言表现得这么好。
Self的垃圾收集器使用分代垃圾回收,它按年龄分离对象。通过使用内存管理系统记录页面写,可以维护一个写屏障。这个技术给出了卓越的性能,尽管在运行一些时间之后,出现完全的垃圾收集,要花相当可观的时间。
运行系统选择性的扁平化调用结构。这给出适当的自身提速,但允许了对不同调用者类型的类型信息和多版本的代码的大量缓存。这去除了对做很多方法查找的需要,并允许条件分支语句和硬编码调用被插入,这经常能给出类似C语言的性能,而又不失去语言层面的通用性,但要创建在完全的垃圾收集系统之上。
相关
- 变种在植物分类学中,变种(拉丁文:varietas,简称写做 var.)为一种分类级别,位于种与亚种之下、变型(英语:Form (botany))之上;作为种下分类群,生物学名会采用三名法。有一种枕形仙人掌“Esco
- 加里哈斯凯尔·布鲁克·加里(英语:Haskell Brooks Curry /ˈhæskəl ˈkɜːri/,1900年9月12日-1982年9月1日),生于美国马萨诸塞州米里镇,数理逻辑学家,专长于组合子逻辑理论。尽管组合
- 费利克斯·霍夫曼费利克斯·霍夫曼(Felix Hoffmann,1868年1月21日-1946年2月8日)是一位德国化学家,是他首先将海洛因和阿司匹林合成成为药物。1868年1月21日霍夫曼生于路德维希堡,在慕尼黑学习化学
- 维特根斯坦路德维希‧约瑟夫‧约翰‧维特根斯坦(德语:Ludwig Josef Johann Wittgenstein,又译维特根施泰因、维特根斯坦;1889年4月26日-1951年4月29日)是一名奥地利哲学家。他生于奥地利,后入
- 本科本科教育是高等教育的一种类型,一般由高等学校(即大学)开展。本科生成绩合格并被准予毕业后,可获得学士学位。相比起研究生,本科是否被优秀的本科教育录取是判断学生素质是否优秀
- 远近孝一远近孝一(1971年10月20日-)是日本的男性声优,所属东京俳优生活协同组合。于大阪府出生并在山口县成长,血型为B型。代表作有《勇者指令》(大堂寺炎)、《侦探学园Q》(天草流)、《超能勇
- 大内隆雄大内隆雄(1907年4月8日-1980年2月10日)是日本的翻译家、文学家。原名山口慎一,笔名失间恒耀、徐晃阳、大内高子等。1907年4月8日生于日本福冈县柳河(现柳川)。小时,来到中国东北长
- 朝鲜册封使列表朝鲜册封使列表,每逢朝鲜王朝国王位发生更替之际,中国皇帝往往要向朝鲜派遣册封使。
- 前卫出版社前卫出版社是台湾一家以出版台湾文学、历史等台湾相关研究书籍为主的出版社,总部位于台北市农安街。前卫出版社由林文钦成立于1982年。林文钦在成立前卫出版社以前曾在三民书
- 木冈二叶木冈二叶(1965年11月22日-),日本足球运动员,日本国家女子足球队成员。在1981年6月7日,她代表日本国家女子足球队出赛,在对战中华台北的比赛中首次亮相。从1981年到1996年,他共为国家足球队出场75次,打进30球。她也曾代表日本参加1991年国际足联女子世界杯,1995年国际足联女子世界杯,1996年夏季奥林匹克运动会足球比赛。