Self
Self语言,是一种基于原型的面向对象的程序设计语言,于1986年由施乐帕洛阿尔托研究中心的David Ungar和Randy Smith给出了最初的设计。Self语言是在Smalltalk的基础上发展而来,沿用了Smalltalk中一切都是对象的风格。在实现Self系统的过程中,设计研究人员发展出了一种动态自适应编译技术。Self同Smalltalk一样既是一个编程语言,也是一个集成开发环境和运行环境。
面向对象 (基于原型) | |
設計者 | David Ungar, Randall Smith |
實作者 | David Ungar, Randall Smith, 斯坦福大学, Sun微系统 |
1987年 | |
穩定版本 | 2017.1[1]
(2017年5月24日
) |
型態系統 | 动态, 强类型 |
許可證 | 类BSD许可证 |
網站 | www |
主要實作產品 | |
Self | |
啟發語言 | |
Smalltalk | |
影響語言 | |
NewtonScript, JavaScript, Io, Agora, Squeak, Lisaac, Lua, Factor, REBOL |
简介
Self语言把概念上的精简作为设计原则。它取消了类的概念,只有对象的概念,同时把消息作为最基本的操作。把对象的属性理解为获取或更改属性这两种方法,从而把属性的概念简化为方法;取消了变量和赋值,并以通过消息来读槽和写槽的方式代之。在发展Self的过程中,Self的设计研究人员也探索了Self的程序设计。他们提出了特质的概念,用对象实现了名字(对象名或方法名)的管理,用动态绑定实现了委托。
尽管Self系统一次运行在一个进程中,但Self系统实际上可以分成两个部分:Self虚拟机和Self世界(Self world)。Self世界是一个Self对象库,Self程序就是Self世界里的对象,Self虚拟机用来执行这些Self程序。当Self程序从终端、文件或者图形用户界面输入到系统中来时,Self系统把源程序解析转化为Self对象。Self对象包括数据对象和方法对象,方法对象的代码部分是用一种指令非常简单的字节码(bytecode)表示的,字节码由虚拟机来解释。
动态自适应编译技术的采用提高了Self代码的执行效率。对经常执行的方法,虚拟机将进一步把字节码转化为本机代码。Self虚拟机还提供了一些可供调用的原语,用来实现算术运算、对象复制、输入输出等。
Self也拥有一个图形用户界面Morphic,Self的编程环境也是基于Morphic来实现的。Self在精简语言概念的同时也把大量的工作转交给环境来处理。如可见性,模块与代码的管理都是由环境来处理的,语言中的反射机制也同环境密切相关。
基于原型的编程语言
传统的基于类的面向对象语言,基于了根深蒂固的二元性:
例如,假设类Vehicle
(车辆)的对象有一个“名字”,和进行各种动作的能力,比如“开车上班”和“运送建材”。Bob's car
是类Vehicle
的特定对象(实例),它的“名字”是“Bob's car”。在理论上,你可以向Bob's car
发送消息,告诉它去“运送建材”。
这个例子展示了这种方式的一个问题:Bob's car
,恰巧是一个运动车,(在任何意义上)不能装载和运送建材,但这是建模Vehicle
所必须拥有的能力。通过使用子类来建立特殊化的Vehicle
,可产生一个更有用的模型;例如Sports Car
(运动车)和Flatbed Truck
(平板卡车)。只有类Flatbed Truck
的对象需要提供“运送建材”的机能;运动车,不适合这种工作,它只需要“快速行驶”。但是,这种深入建模在设计期间需要更多的洞察力,洞察那些可能只在引起了问题时才显现出的事情。
这个问题是在原型(prototype)这个概念背后的动机因素之一。除非你能必然性的预测出一组对象和类在遥远未来时所要有的品质,你不能恰当的设计好一个类的层级。程序最终需要增加行为实在是太频繁了,而系统的很多节段将需要重新设计(或重新构建)来以不同的方式迸发出对象。早期的面向对象语言如Smalltalk的实验,显示出这种问题反反复复的出现。系统趋向于增长到一定程度后就变得非常僵化,因为在编程者的代码下的深层的基本类,逐渐成为一个简单的“错误”。 没有变更起初的类的容易方式,严重的问题就会出现。
动态语言如Smalltalk,允许通过周知的按照类的方法进行这种变更;即通过改变类,基于它的对象就可以改变它们的行为。但是,进行这种变更必须非常小心,因为基于相同类的其他对象可能把它当作“错误行为”:“错误”经常是依赖于场景的。这是脆弱基类问题的一种形式。进一步的说,在语言如C++中,这里的子类可以从超类分别的编译,对超类的变更实际上可以破坏预编译的子类方法。这是脆弱基类问题的另一种形式,也是脆弱二进制接口问题的一种形式。
在Self和其他基于原型的编程语言中,消除了在类和对象之间的这种二元性。
不再有基于某种“类”的一个对象“实例”,在Self中,你可以复制一个现存的对象,并改变它。所以Bob's car
可以通过制作现存的Vehicle
对象的副本来建立,并增加“快速行驶”方法,建模它恰好是一辆保时捷911的事实。主要用来制作副本的基本对象叫做“原型”。这种技术被称为是一种非常简化的机制。如果一个现存的对象(或对象的集合)被证明是个不适当的模型,编程者可以简单的建立有正确行为的一个修改的对象,并转而使用它。使用现存对象的代码不会改变。
描述
下面简要描述Self的语法语义。
槽
Self对象是“槽”(slot)的集合(collection)。槽是返回值的访问器方法,它是名字-值对,包含到其他对象的引用。例如,一个叫做name
的槽:
myPerson name
返回在name
中的值。通过在“数据槽”的相同名字之后放置一个冒号来设置值,下例:
myPerson name:'foo'
设置它的值,这个槽叫做“赋值槽”。槽 <- 值
表示初始化读写变量,槽 = 值
指示一个只读槽。
注意在Self中在字段(field)和方法之间没有区分:所有东西都是槽。因为通过消息访问槽形成了Self语法的绝大部份,很多消息被发送给“self”,“self”可以去掉(语言因而得名)。
对象
对象文字用圆括号来界定。在圆括号内,对象描述构成自竖杠|
界定的一个槽的列表,随后是在这个对象被求值时要执行的代码。例如:
( | 槽1. 槽2 | ’这里是一些代码’ printLine )
一个真正的空对象指示为(| |)
或简单的()
,它根本不接收任何消息。
Self就像Smalltalk,使用“块”用于控制流程和其他义务。块对象的写法类似于其他对象,除了用方括号替代了圆括号之外。方法是除了槽(参数槽和局部槽)之外还包含代码的对象,并可以被放置入Self槽中如同任何其他对象一样,比如数。在其各种情况下语法都保持相同。
在表达式于提示符下键入的情况下,带领用户进入Self世界的这个对象叫做“大厅”(lobby)。它向用户提供三类对象:特质(trait)对象,封装共享行为的对象,典型的每个原型对象都有一个关联的同名特质对象来描述它的行为的共享部份;全局对象,即原型对象和独一无二对象(oddball);混入对象,即小的无父对象的行为束(bundle),设计用于混合入一些其他对象之中。
消息
访问槽的语法类似于Smalltalk。有三类消息可以获得:
- 一元
接收者 槽名字
- 二元
接收者 算符 参数
- 关键字
接收者 关键字1: 参数1 关键字2: 参数2
所有消息都返回结果,所以接收者(如果有的话)和参数自身可以是其他消息的结果。下列跟随着句号的消息,意味着Self将丢弃返回的值。例如:
'Hello, World!' print.
这是Self版本的hello world程序。'
语法指示文字串对象。其他文字(literal)对象包括数、块和一般对象。
组合可以通过使用圆括号来强制。在缺乏明确组合的情况下,一元消息被认为有最高优先级,其次是二元消息(组合是从左至右的),而关键字消息最低。赋值使用关键字,在表达式也有关键字消息的地方将导致一些额外的圆括号,为了避免如此Self要求关键字消息的第一部份开始于小写字母,而后续部份开始于大写字母。
valid: base bottom
between: ligature bottom + height
And: base top / scale factor.
可以被无歧义的分析,其含义同于:
valid: ((base bottom)
between: ((ligature bottom) + height)
And: ((base top) / (scale factor))).
在Smalltalk-80中,相同的表达式将写为:
valid := self base bottom
between: self ligature bottom + self height
and: self base top / self scale factor.
假定base
、ligature
、height
和scale
不是self
的实例变量,而事实上是方法。
制作新对象
考虑一个稍微复杂些的例子:
labelWidget copy label: 'Hello, World!'.
通过copy
消息制作labelWidget
对象的一个副本(这时没有快捷方式),接着向它发送一个消息,将Hello, World!
放入叫做label
的槽中。现在用它做点事情:
(desktop activeWindow) draw: (labelWidget copy label: 'Hello, World!').
在这个案例中(desktop activeWindow)
首先进行,从desktop
对象所知道的一个窗口列表中返回activeWindow
。接着(从内向外从左至右读)前面研究过的代码返回labelWidget
。最后这个组件被发送到活动窗口的draw
槽中。
委托
在理论上,所有Self对象都是独立实体。Self既没有类也没有元类。对任何特定对象的变更都不影响任何其他对象,但是在某些情况下却需要它们这样。一个对象正常的只可以理解对应于它的局部槽的消息,但拥有一个或更多的指示“父”对象的槽,对象可以将任何自身不理解的消息委托(delegate)给父对象。任何槽都可以通过增加星号后缀来制作父指针。采用这种方式Self处理在基于类的语言中使用继承来担负的责任。委托还可以用来实现一些特征比如名字空间和词法辖域。
例如,假定在一个简单的账簿应用中,定义了一个对象叫做“银行帐号”(bank account)。通常建立的这个对象,具有内部的方法,比如说“存款”(deposit)和“取款”(withdraw),和任何它所需要的数据槽,比如说“余额”(balance)。这只是一个原型,它只在使用方式上特殊,因为它恰好是一个全功能的银行帐号。
特质
为“Bob的账户”制作银行帐号对象的复本(clone)将建立一个新对象,它起步时完全同于原型。在这种情况下将复制包括方法和任何数据的槽。但更常用的解决方案是首先建立一个简单对象叫做特质对象,它包含通常与一个类有关的项目。
在这个例子中“银行账户”将没有存款和取款方法,而是将有一个父对象来做这些。采用这种方式可以制作银行帐号对象的很多副本,但是我们仍可以通过改变在根对象中的槽来改变全部它们的行为。
这与传统的类有任何不同吗?我们考虑如下例子的含义:
myObject parent: someOtherObject.
这个摘句通过改变与parent*
槽关联的值,星号是槽名字的一部份,但不是对应消息名字的一部份,它在运行时间改变myObject
的“类”。不同于继承或词法辖域,委托对象可以在运行时间修改。
增加槽
在Self中的对象可以通过包括新加的槽来修改。这可以通过使用图形编程环境来做,或者使用原语_AddSlots:
。原语与正常关键字消息有相同的语法,但他它的名字开始于下划线字符。_AddSlots
原语应当避免使用,因为它是早期实现的遗留物。但是我们仍在这个例子中展示它,因为它能使代码更短。
下面是关于重新构建一个叫做“车辆”的简单类的早期例子,用来使得在轿车和卡车之间有可区分的行为。在Self中,可以如下这样完成:
_AddSlots: (| vehicle <- (|parent* = traits clonable|) |).
因为_AddSlots:
原语未指定接收者,那就是发给“self”。给_AddSlots:
的参数是它的槽将被复制来到接收者的对象。在这个实例中它是就只有一个槽的文字对象。这个槽的名字是vehicle
而它的值是另一个文字对象。有第二个槽叫做vehicle:
,它可以用来改变第一个槽的值。这里的parent*
没有相应的parent:
。成为vehicle
对象初始值的文字对象包括一个单一的槽,所以它可以理解与复制关的消息。
vehicle _AddSlots: (| name <- 'automobile'|).
这里的接收者是前面的对象,它现在除了parent*
还将包括name
和name:
槽。
_AddSlots: (| sportsCar <- vehicle copy |).
sportsCar _AddSlots: (| driveToWork = (''some code, this is a method'') |).
尽管此前vehicle
和sportsCar
是完全类同的,现在后者包括了具有最初时没有的方法的一个新槽。方法只能包括在只读槽中。
_AddSlots: (| porsche911 <- sportsCar copy |).
porsche911 name:'Bobs Porsche'.
新的对象porsche911
起步时完全类同于sportsCar
,但是最后的消息改变了它的name
槽的值。注意二者仍有着完全相同的槽,尽管其中之一有着不同的值。
环境
Self的一个特征是它是基于了早期Smalltalk系统所用的某种虚拟机系统。就是说,程序不是像C语言中那样的独立实体,而是需要它们的整体内存环境来运行。这要求应用程序被装载入保存内存的大块(chunk)之中,这叫做“快照”或映像。这种方式的缺点是映像有时很大并且笨重;但是调试一个映像经常被调试一个传统程序要简单,因为运行时状态更容易检查和修改。(在基于源代码和基于映像的开发之间的不同是类似于在面向类的和面向原型的面向对象编程之间的区别。
此外,环境是为了让在系统之中的对象能快速和可持续的变更而定制的。重新构建一个“类”设计就像从现存的祖先拖动出来方法放入新造的之中一样容易。简单任务像测试方法可以通过制作副本来处理,拖动方法进入这个副本,接着变更它。不同于传统系统,只有变更了的对象有新代码,不需要重建任何东西来测试它。如果这个方法有效,可以简单的把它拖动回祖先之中。
垃圾收集
Self的垃圾收集器使用世代垃圾回收,它按年龄分离对象。通过使用内存管理系统记录页面写,可以维护一个写屏障。这个技术给出了卓越的性能,尽管在运行一些时间之后,出现完全的垃圾收集,要花相当可观的时间。
优化
运行系统选择性的扁平化调用结构。这给出适当的自身提速,但允许了对不同调用者类型的类型信息和多版本的代码的大量缓存。这去除了对做很多方法查找的需要并允许条件分支语句和硬编码调用被插入,这经常能给出类似C语言的性能而又不失去语言层面的通用性,但要建立在完全的垃圾收集系统之上[3]。
发展简史
Self语言的最初设计是由David Ungar和Randy Smith于1986年在施乐帕洛阿托研究中心提出的,并在1987年的OOPSLA'87的论文SELF: The Power of Simplicity上给出了描述。
1987年初Craig Chambers, Elgin Lee和Martin Rinard在Smalltalk上给出了Self的第一个实验性解释器。
1987年夏Self项目在Stanford大学正式开始,1988年夏给出了第一个有效率的实现,并发布了1.0和1.1两个版本。在第一个版本中包括内存管理系统和编译器。
1991年初Self项目移至Sun Microsystems Laboratories Inc.,并且在1992年发布了2.0版。在第二个版本中采用了新的编译技术,并引入了多重继承。
1993年1月Self 3.0版发布。在这个版本中包括了一个实验性的图形用户界面,简化了上个版本中多重继承的设计,引入了可见性概念,并采用了更新的编译技术。
1995年7月Self 4.0版发布。在这个版本中包括了一个全新的图形用户环境Morphic,提供了工具transporter用于保存对象,改进了虚拟机,改善了内存管理,在环境的层次上引入了模块的概念,取消了语言层次上的可见性概念。
1995年之后Self的发展基本已经停滞,但在发展Self过程中探索出的一些技术在别的系统中得到了应用。在Self的实现中采用的各种编译优化技术直接导致了Java Hotspot虚拟机的产生;在Smalltalk的一个实现Squeak中采用了Self图形用户界面Morphic的设计方案,放弃了标准Smalltalk中采用的MVC的方案。
引用
- . 24 May 2017 [24 May 2017]. (原始内容存档于2017-05-24).
- Agesen, Ole. . sun.com. March 1997 [2020-05-13]. (原始内容存档于2006-11-24).
- Whole-Program Optimization of Object-Oriented Languages.