热门问题
时间线
聊天
视角

Smalltalk

来自维基百科,自由的百科全书

Smalltalk
Remove ads

Smalltalk是一种动态类型反射式面向对象编程语言。Smalltalk由艾伦·凯、Dan Ingalls、Ted Kaehler、Adele Goldberg等于1970年代在施乐帕罗奥多研究中心开始开发。

事实速览 编程范型, 设计者 ...
Remove ads

Smalltalk对其它众多的程序设计语言的产生起到了极大的推动作用,特别是Objective-CCLOSPythonRuby等。1990年代涌现的许多软件开发思想都得益于Smalltalk,例如设计模式敏捷编程代码重构[6]等。

Remove ads

概述

Smalltalk和许多程序设计语言不同,它不仅仅是一门语言。下面从几个不同的角度来解释Smalltalk。

  • 一种面向对象的程序设计语言:它是一种面向对象的语言,包含语言的语法和语义。一些编译器可以透过Smalltalk源程序产生可执行文件。这些编译器通常产生一种能在虚拟机上运行的二进制代码。Smalltalk语言本身非常精炼。
  • 一种程序设计环境:这里指的是一种提供许多对象的系统,而不是某种特殊的开发环境。和许多语言不同(包括C++),Smalltalk附带有一个巨大的、相当标准的类库。这些使得开发Smalltalk程序的效率非常高。在其它语言(例如AdaCPascal)中,通常被作为语言的一部分的功能(例如条件判断,循环等),在Smalltalk由特定的类提供。
  • 一个应用开发环境(ADE):由于Smalltalk的历史原因,它具有一个非常优秀的高度集成、开放的应用开发环境。由于开发环境中的浏览器、监视器以及调试器,都由同样的源程序派生出来的,不同的版本之间也具有相当好的兼容性。此外,这些工具的源程序都可以在ADE直接访问。
Remove ads

历史

Thumb
Smalltalk-76
Thumb
VisualWorks英语VisualWorks,派生于Smalltalk-80 v2的商业实现
Thumb
Pharo,从Squeak v3.9分叉出的派生于Smalltalk-80 v1的开源实现

最早的Smalltalk原型由艾伦·凯于1970年代初提出。(来自Simula 67)、海龟绘图(来自LOGO)以及图形用户界面(来自Sketchpad等先驱系统)等概念的有机组合,构成了Smalltalk的最初的蓝图[5]

在1971年到1975年之间,艾伦·凯在Xerox PARC的小组,在Xerox Alto计算机上,设计并实现了第一个真正的Smalltalk语言系统,编译器由Dan Ingalls负责主要实现。这个系统被称为Smalltalk-71与Smalltalk-72,具有以下几个技术创新:

  • 语言完全基于消息交换Simula 67的类的概念。
  • 语言没有固定的语法,语法分析由类本身完成。

开发环境的革新相当迅速。虽然当时的位图显示器十分昂贵,但是艾伦·凯却说服了PARC,让他使用这些位图显示器,这使得艾伦·凯和他的小组,能够实现不同大小和字体的文字,使用多窗口环境,以及一些对图像处理的高端支持。Smalltalk-72影响了演员模型的发展[7],它的语法和执行模型,与现代的Smalltalk变体有着显著的差异。

在1975到1976年间,艾伦·凯小组认识到应当对执行效率和规模进行优化。于是他们在许多重要方面重新设计了Smalltalk系统,被称为Smalltalk-76,它在语言上:

  • 引入了继承和子类的概念[8]
  • 确定了语言的语法,这使得编译器能够产生高效、可执行、精炼的二进制代码。
  • 拉里·泰斯勒设计了类浏览器,这极大地提高了Smalltalk程序员的编程效率。

前述的所有Smalltalk系统,都是在特殊的硬件上实现的,直到1977年至1978年,Bruce Horn和Ted Kaehler把Smalltalk-76移植到Xerox NoteTaker英语Xerox NoteTaker上,它是由Intel 8086处理器和自定显示器所组成的硬件环境。虽然这种硬件环境只生产了10台,但是它证明了在通常的处理器上实现Smalltalk的可能性。

在1979至1980年,部分受NoteTaker项目的影响,Smalltalk小组的注意力转移到Smalltalk的销售可行性上。小组设计并实现了新一代的Smalltalk系统,这次修改的目标着重于在标准硬件上的移植性等方面,被称为Smalltalk-80,它包括:

  • 采取ASCII码字符集,摒弃了原先在Smalltalk-72和Smalltalk-76中使用的特殊字符。
  • 取消了原始方法直接访问内存的能力。取而代之的是引入一系列的原始方法提供相应的功能。
  • 引入了元类的概念[9]
  • 引入MVC(模型-视图-控制器)系统以方便交互式应用软件的开发。

Smalltalk-80是在PARC之外能获得到的第一个语言变体,最初作为Smalltalk-80版本1,给与了少数公司(惠普苹果公司泰克DEC)和大学(UC Berkeley),用于同行评审和在它们自己的平台上实现。后来在1983年普遍可获得的实现,叫做Smalltalk-80版本2,发行为虚拟机规定和映像(具有对象定义的独立于平台的文件)[10]

1988年Xerox PARC为了将Smalltalk推向市场而成立了分拆公司ParcPlace Systems。ANSI Smalltalk自从1998年来是标准的语言参考[11]

两个当前流行的Smalltalk实现变体,是这些最初Smalltalk-80映像的后代。Squeak开源实现,它经由Apple Smalltalk[12],派生自Smalltalk-80版本1.03[13]VisualWorks英语VisualWorks经由Smalltalk-80 2.5和ObjectWorks(二者都是ParcPlace Systems的产品),派生自Smalltalk-80版本2[10]

Remove ads

面向对象编程

Thumb
Smalltalk-80例子代码在Squeak下的类层级和与之并行的元类层级的示意图。其中的rProtoObjectcClassmcMetaclass。蓝色连接表示实例联系,绿色连接表示继承联系。

如同其他面向对象语言,Smalltalk-80(而非Smalltalk-72)的中心概念是“对象” 。一个对象总是一个“”的一个“实例”。类是描述它们的实例的属性和行为的“蓝图”。例如,一个GUI窗口类,可以声明窗口拥有的属性,比如标签、位置和窗口是否可见。这个类还可以声明其实例支持的操作,比如打开、关闭、移动和隐藏。每个特定窗口对象,对这些属性都有自己的值,它们每个都能进行它的类定义的操作。

Smalltalk对象确切的可以做三件事:

  1. 持有状态(引用到其他对象)。
  2. 接收消息自本身或其他对象。
  3. 在处理一个消息的过程中,发送消息至本身或其他对象。

一个对象持有的状态总是私有于这个对象。其他对象只能通过发动请求(消息)至这个对象,来让它做出查询或变更这个状态。任何消息可以发送给任何对象:当接收到一个消息的时候,接收者确定这个消息是否合适。Alan Kay评论说,尽管关注于对象,消息才是Smalltalk中最重要的概念:“最大的想法是消息传递,它是Smalltalk/Squeak核心的全部意义所在(它是我们在Xerox PARC阶段从未真正完成的某种东西)。”[14]

不同于多数其他语言,Smalltalk对象可以在系统运行的同时进行修改。现场编码和飞速应用补丁,是Smalltalk的主导编程方法论,并且是它高效的主要原因。

Smalltalk是“纯”面向对象编程语言,这意味着,不像C++Java,在作为对象的值和作为原始类型的值之间没有区别。在Smalltalk中,原始值比如整数、布尔值和字符,也是对象,这么说的意义在于它们也是相应类的实例,而且要发送消息来调用在它们上的运算。编程者可以通过子类,改变或扩展实现原始值的类,使得可以向它们的实例定义新行为,例如实现一个新的控制结构,甚至使得它们现有行为得以改变。这个事实被总结成常听到的一句短语:“在Smalltalk中,所有东西都是对象”,它可以更精确的表达为:“所有的值都是对象”,因为变量不是。

因为所有的值都是对象,也是对象。每个类都是这个类的元类的一个实例。元类都是Metaclass(元类类)的实例,它也是对象,并且是Metaclass class(元类元类)的实例。代码块是Smalltalk表达匿名函数的方式,它也是对象[15]

Hello, World!例子

Hello, World!程序,实质上被所有计算机语言的课本用作要学习的第一个程序,它展示了这个语言的最基本语法和环境。对于Smalltalk,这个程序可极其简单的书写。下列代码中,消息show:被发送给对象Transcript,具有字符串文字'Hello, World!'作为它的实际参数。调用show:方法,导致它的实际参数,即字符串文字'Hello, World!',显示在叫做“副本”(Transcript)的终端窗口:

Transcript show: 'Hello, World!'.

注意需要打开Transcript窗口,来看到这个例子的结果。

语法

Smalltalk-80语法是相当极简主义的,只基于了一小把的声明和保留字。事实上,Smalltalk中只保留了六个“关键字”:truefalsenilselfsuperthisContext。它们的准确术语是“伪变量”,是服从变量标识符规则的标识符,但指示了编程者所不能变更的绑定。truefalsenil伪变量是单例实例。selfsuper,在响应一个消息而激活的方法中,指称这个消息的接收者;但是发送给super的消息,在这个方法的定义类的超类中查找方法,而非这个接收者的类中,这允许子类中的方法调用在超类中的同名方法。thisContext指称当前的活动记录。

内置的语言构造只有消息发送、赋值、方法返回和某些对象的文字语法。从它最初作为给所有年龄儿童的语言开始,标准的Smalltalk语法以更像英语,而非主流编码语言的方式使用标点符号。语言余下部分,包括用于条件求值和迭代的控制结构,都由标准Smalltalk类库实现在内置构造之上。出于性能上的原因,实现可以识别并特殊处理某些这种消息,但这只是优化而并未硬性规定入语言语法。

谚语“Smalltalk语法适合一张明信片”,所提及的是Ralph Johnson英语Ralph Johnson (computer scientist)的一个代码片段,展示了一个方法的所有基本标准语法元素[16]

exampleWithNumber: x
  | y |
  true & false not & (nil isNil) ifFalse: [self halt].
  y := self size + super size.
  #($a #a 'a' 1 1.0)
    do: [ :each |
      Transcript 
        show: (each class name);
        show: (each printString);
        show: ' ' ].
  ^x < y
Remove ads

文字

下列例子诠释了最常用的对象,可以在Smalltalk-80方法中被写为文字英语Literal (computer programming)值。

数和字符

下列是数的某些可能例子:

42
-42
123.45
1.2345e2
2r10010010
16rA000

最后两个项目分别是二进制和十六进制数。在r前的数是底数或基数。基数不必须是二的幂;例如36rSMALLTALK是一个有效的数值,等价于十进制的80738163270632

字符书写时带有前导的美元符:

$A

字符串

字符串是包围在单引号内的字符序列:

'Hello, world!'

要在一个字符串中包括一个引号,使用另一个引号来转义

'I said, ''Hello, world!'' to them.'

双引号不需要转义,因为单引号界定字符串:

'I said, "Hello, world!" to them.'

两个相等的字符串(字符串相等,如果它们包含完全相同的字符)可以是驻留在内存不同位置中的不同对象。

符号

除了字符串,Smalltalk有一类叫做符号英语Symbol (programming)Symbol)的字符序列对象。符号保证是唯一的,没有作为不同对象的两个相等的符号。因此,符号非常易于比较,并经常用于语言构造中,比如用作消息选择子。

符号被写为#跟随着字符串文字英语string literal。比如:

#'foo'

如果一个序列不包含空白或标点字符,还可以写为:

#foo

数组

例如定义了四个整数的一个数组:

#(1 2 3 4)

很多实现支持下列字节数组(ByteArray)的文字语法,例如定义了四个整数的字节数组:

#[1 2 3 4]

其他

最后却重要的是块(匿名函数文字):

[... 一些smalltalk代码 ...]

很多Smalltalk方言为其他对象实现了额外的语法,但是上述的是所有方言都本质上支持的。

变量声明

在各种Smalltalk中共同使用的有两种变量:实例变量和临时变量。其他变量和有关术语依赖于特定实现,例如VisualWorks英语VisualWorks有类共享变量和命名空间共享变量,而Squeak和很多其他实现,有类变量、池变量和全局变量。

在Smalltalk中临时变量声明是在方法(见后)内声明的变量。它们声明在方法的顶部,作为由竖杠包围的空格分隔的名字。例如:

| index |

声明一个临时变量名叫index,可以包含初始值nil

多个变量可以在一组竖杠内声明:

| index vowels |

声明了两个变量:indexvowels。所有变量都要初始化。字符串的索引变量,初始化为null字符或初始为0ByteArray,此外的所有变量初始化为nil

按命名约定,实例变量、临时变量、方法或块的参数,应当以小写字母开头,指示它们具有私有作用域,它们合称为局部变量。而全局变量、类变量、池字典、类名字,应当以大写字母开头,它们合称为共享变量。

赋值

变量通过:=语法来指定一个值。比如:

vowels := 'aeiou'

指定字符串'aeiou'至前面声明的vowels变量。这个字符串是个对象(在单引号之间的字符序列是文字字符串的语法),在编译时间由编译器创建。

在最初的Parc Place映像中,现在下划线(_)的字形,在那时是左向箭头()字形(就像1963年版本ASCII代码中那样)。Smalltalk最初接受左向箭头,作为唯一的赋值算符。一些现代代码仍然包含充当赋值的下划线,会让人想起这种最初的用法。多数现代的Smalltalk实现接受要么下划线,要么冒号等号语法。

消息

消息是Smalltalk中最基础的语言构造。所有控制结构都实现为消息发送。Smalltalk缺省的采用动态分派单一分派策略,这是相对于其他一些面向对象语言使用的多分派而言的。

一元消息

下列例子是发送消息factorial至数值42

42 factorial

在这种情况下,42叫做这个消息的“接收者”,而factorial是消息的选择子。接收者通过返回一个值来相应这个消息(这个情形中是42的阶乘)。同其他事物一样,消息的结果可以赋值给一个变量:

aRatherBigNumber := 42 factorial

上面的factorial是“一元”消息,因为只涉及了一个对象,即接收者。

关键字消息

消息可以承载额外的对象作为实际参数,比如:

2 raisedTo: 4

在这个表达式中,涉及了两个变量:2作为接收者而4作为消息的实际参数。消息结果,或用Smalltalk的说法,回答被认定为16。这种消息叫做“关键字”消息。消息可以有多个实际参数,使用如下语法:

'hello world' indexOf: $o startingAt: 6

它的回答是在接收者字符串中字符o的索引,从索引6开始查找。这个消息的选择子是indexOf:startingAt:,构成自两个部分或关键字。

这种关键字和实际参数的交织意图改进代码的可读性,因为实际参数由前导于它们的关键字来解释。例如,要建立一个矩形的表达式使用C++或Java类语法可以写为:

new Rectangle(100, 200);

不清楚这些实际参数分别是什么。与之相反,在Smalltalk中,这个代码可以写为:

Rectangle width: 100 height: 200

这个情况下接收者是Rectangle类,回答是这个类的具有指定宽度和高度的一个实例。

二元消息

最后,多数特殊(非字母)字符可以被用作所谓的“二元消息”。这些允许了数学和逻辑算符以传统形式书写:

3 + 4

它发送消息+给接收者3,具有4作为实际参数传递(回答将是7)。类似的:

3 > 4

将消息>发送给3具有实际参数4(回答将是false)。

注意,Smalltalk-80语言自身,不包含着这些算符的含义。上述的结果,都只是这些消息的接收者(这里是数值实例),为了响应消息+>而定义并返回的。这个机制的副作用是运算符重载,消息>可以被其他对象所理解,允许使用形如a > b的表达式来比较它们。

表达式

一元消息可以一个接一个的写成方法链式调用

3 factorial factorial log

它发送factorial3,接着发送factorial到前面的结果6,接着发送log到前面的结果720,产生最终的结果2.85733

一个表达式可以包括多次消息发送。在这个情况下,表达式依据一个简单的优先级次序来分析。一元消息有最高的优先级,随后是二元消息,最后是关键字消息。例如:

3 factorial + 4 factorial between: 10 and: 100

被求值如下:

  1. 3接收消息factorial并回答6
  2. 4接收消息factorial并回答24
  3. 6接收消息+具有24作为实际参数并回答30
  4. 30接收消息between:and:具有10100作为实际参数并回答true

最后的消息发送的回答,是整个表达式的结果。

组合

在需要的时候使用圆括号可以改变求值的次序。例如:

(3 factorial + 4) factorial between: 10 and: 100

将改变表达式含义,首先计算3 factorial + 4产生10。接着10接收第二个factorial消息,产生36288003628800接着接收between:and:,回答false

注意由于二元消息的含义,不是硬性规定入Smalltalk-80语法的,它们全部都被认为有相等的优先级,并简单的从左至右来求值。因此,使用二元消息的Smalltalk表达式的含义,可能不同于传统释义:

3 + 4 * 5

被求值为(3 + 4) * 5,产生35。要得到预期回答23,必须使用圆括号来显式的定义运算次序:

3 + (4 * 5)

复合

以点号分隔的表示式按顺序执行。注意在变量定义和随后的表达式之间没有点号。一个表达式序列的值,是最后的表达式的值。除了最后的表达式之外,所有的表达式的值都被忽略。注意点号是分隔符而并非终结符,因此最终的点号是可选的。

下列(假想)例子中,书写了一序列的表达式,每个都用点号分隔。这个例子首先建立类Window的一个新实例,存储它在一个变量中,接着向它发送两个消息:

| window |
window := Window new.
window label: 'Hello'.
window open

级联

如果像上述例子这样,将一序列消息都发送给相同的接收者,它们也可以写为方法级联调用,具有用分号分隔的单独消息:

Window new
  label: 'Hello';
  open

这种将前面例子的重新为一个单一表达式,避免了对将新窗口存储在临时变量的需要。依据平常的优先级规则,首先向Window类发送一元消息new,接着向new回答的那个对象,发送label:open

可以使用yourself消息来返回一个级联消息的接收者。

代码块

头等对象。代码块即匿名函数,可以被表达一个文字值(它是一个对象,因为所有值都是对象)。这是通过方括号达成的:

[ :params | <消息表达式> ]

这里的:params是代码可以接受的形式参数的列表。结果的块对象可以形成一个闭包:它可以在任何时间访问它外围的词法作用域内的变量。这意味着下列Smalltalk代码:

[:x | x + 1]

可以理解为:,或用λ演算表达为: :

块可以通过发送给它们value消息来执行。块有一个参数用value:,有2个参数使用value:value:,以此类推直到4个参数,对多于4个参数使用valueWithArguments:并将参数作为数组传递。例如下面的表达式:

[:x | x + 1] value: 3

可以被求值为:,或用λ演算表达为:

块返回(常称为回答)其主体的最后一个表达式的值,除非有一个由显式的^指示的返回,这时返回这个返回表达式的值。在块内部的返回,充当了一种逃出(escape)机制。在一个嵌套的块表达中的返回表达式,将终止在字面上包围的方法。

块的文字表示是一种创新,它一方面允许特定代码有更重大的可读性;它允许涉及迭代的算法一更清晰和简洁的方式编码。典型的在某些语言中使用循环写成的代码,可以在Smalltalk中使用块简洁的书写,有时在单一一行之内。更加重要的,块允许使用消息和多态来表达控制结构,因为块推延了计算,而多态可以用来选择交替者(alternative)。所以在Smalltalk 80中,if…then…else被书写和实现为:

expr ifTrue: [ expr为真时求值的语句 ] ifFalse: [ expr为假时求值的语句 ]

再举一例,向一个搜集发送消息select:

positiveAmounts := allAmounts select: [:anAmount | anAmount isPositive]

注意这与函数式编程有关,这里的计算模式被抽象成了高阶函数select:等价于在一个适当的函子上的高阶函数filter[17]

控制结构

在Smalltalk中控制结构没有特殊的语法。它们转而实现为发送到对象上的消息。以条件执行为例,布尔类Boolean定义了ifTrue:ifFalse:ifTrue:ifFalse:ifFalse:ifTrue:方法。比如向一个布尔对象,发送ifTrue:消息,并传递一个代码块作为实际参数,这个块被执行当且仅当布尔接收者为真。下面用一个例子来展示:

result := a > b
  ifTrue: [ 'greater' ]
  ifFalse: [ 'less or equal' ]

块也被用来实现,用户定义控制结构、枚举器访问者异常处理、可插拔的行为和很多其他模式。

迭代

下面例子,从一个字符串中过滤出其中所含有的元音字符:

| aString vowels |
aString := 'This is a string'.
vowels := aString select: [:aCharacter | aCharacter isVowel].

在最后一行,向字符串对象aString发送一个select:消息,它具有一个代码块[:aCharacter | aCharacter isVowel]作为实际参数。这个代码块,表示一个测试,代码块文字将被用作一个谓词函数,它回答true,当且仅当这个字符串的一个元素aCharacter,应当被包括在满足这个测试的字符搜集之中。

字符串类String响应select:消息,要调用的select:方法,定义并实现在搜集类Collection[18];它将给select:的实际参数选择块,传送给形式参数aBlock;然后将绑定了选择块的aBlock嵌入到迭代块的代码之中,再把这个迭代块作为向字符串自身发送的do:消息的实际参数,从而将这个字符串所包含的每个字符,都作为实际参数传送给这个迭代块,而各做一次求值。在求值迭代块的时候,通过value:消息,将迭代元素传送给aBlock所绑定的选择块,它回答一个布尔值;接着向它发送ifTrue:消息,如果这个布尔值是对象true,则将这个字符增加到要返回的字符串中。

字符串类String响应do:消息,要调用的do:方法,定义在可迭代类Iterable[19],而实现在可序列化搜集类SequenceableCollection[20],这个类是Iterable类的子类和String类的超类。

异常处理

Smalltalk的异常处理机制,Exception类及其子类比如Error类,类似于CLOS的异常处理样式,使用块作为处理器:

[ 一些运算. 
  Error signal: 'an error occurred'.
  另一些运算
] on: Error do: [ :ex | 
  处理器代码. 
  ex return ]

异常处理器的ex实际参数,提供对挂起运算的状态的访问,比如它的栈帧、行号、接收者和实际参数等,并且通过发送ex proceedex rejectex restartex return之一,还可用来控制计算怎样继续。

类通过实例变量定义它的实例的结构,通过方法定义它的实例的行为。每个方法都有叫做选择子的一个名字,它在这个类之内是唯一性的。

定义

下面是个平凡的类定义[21]

Object subclass: #MessagePublisher
  instanceVariableNames: ''
  classVariableNames: ''
  poolDictionaries: ''
  category: 'Smalltalk Examples'

多数这种定义经常由编程环境来填充。这里的类定义是给Object类的一个消息,用来建立它叫做MessagePublisher的一个子类。

在Smalltalk中类是头等对象,它可以就像任何其他对象一样接收消息,并可以在执行时间动态的创建。Object类在收到这个subclass:instanceVariableNames:classVariableNames:poolDictionaries:category:消息之时,原则上首先在其元类Object class中查找对应方法,未果而上溯继承链在其超类Class类中找到对应方法实现。

在Smalltalk中,实例变量英语Instance variable是这个实例的私有变量,它可以在定义它们的类的任何实例方法中,还有在它的子类中定义的方法中,通过名字来访问。在需要于一个类的所有实例、这个类本身和它的子类之间,共享某个数据的时候,要使用类变量英语Class variable,它是由这个类和它的所有实例共享的私有变量。池变量是在可以没有继承关联的多个类之间共享的变量。池变量最初存储在池字典中,现在它们应当被定义为专门的类(SharedPool的子类)的类变量。

category指示有关的类的“群组”,在现代Smalltalk版本如Pharo中被替代为package,包系统是利用简单的命名约定,组织SqueakPharo源代码的简单而轻量级的方式。

在一个类所对应的元类中定义的实例变量叫做类实例变量,每个类都有自己私有的类实例变量,子类将继承这些类实例变量,但是子类会拥有这些变量的它们自己的私有复本,类和它们的子类不分享类实例变量。例如,可以定义一个类实例变量叫做count来跟踪一个给定的类有多少实例。

类不能直接访问它的实例的实例变量,而实例不能访问它们的类的类实例变量。如果需要的话必须定义变异子与访问子

方法

当一个对象接收到一个消息的时候,调用匹配这个消息名字的一个方法。所有方法都是公开的和虚拟的(也就是动态查找的)。方法被分组入指示它们意图的协议(protocol)之中。

为一个类增加方法涉及到Behavior类,其中的compile:方法,编译一个方法源代码并返回一个CompiledMethod类的实例;addSelector:withMethod:方法,将给定的一个编译过的方法增加到方法字典中。在Behavior类的子类ClassDescription类中,compile:classified:方法,编译一个方法的源代码,并为这个方法指派上给定的归类(对于方法称为协议)。

对象负责在运行时间动态的确定,执行哪个方法来响应一个消息,尽管在很多语言中,这可能是(有时或总是)在编译时间静态确定的。下列代码定义一个方法publish,并且这个定义将在这个对象收到publish消息的时候发生。

publish
  Transcript show: 'Hello World!'

下列名字为#quadMultiply:and:的方法,演示了接收多个实际参数并返回一个值:

quadMultiply: i1 and: i2
  "这个方法将给定的两个数相乘并对结果乘以4."
  | mul |
  mul := i1 * i2.
  ^mul * 4

执行任何前导了^脱字符)的表达式,都导致这个方法于这一点退出,并返回这个表达式的值。终止而没有显式返回某个表达式的一个方法,将隐含的返回自身。

实例化

为一个类新建一个实例,要用到new方法,它定义在Behavior类中。下列例子代码:

MessagePublisher new

建立并返回MessagePublisher类的一个新实例。它典型的会被赋值到一个变量:

publisher := MessagePublisher new

但是也可以向一个临时的匿名对象发送一个消息:

MessagePublisher new publish

类方法

类方法就是在一个类的元类中定义的方法。

比如搜集类Collection的类方法中,实例创建方法有with:with:with:[22]、一直到with:with:with:with:with:with:方法。在下面的例子中,将with:with:用于有序搜集类OrderedCollection,它是SequenceableCollection类的子类,故而能最终上溯至超类搜集类:

| rectangles aPoint collisions |
rectangles := OrderedCollection
  with: (Rectangle left: 0 right: 10 top: 100 bottom: 200)
  with: (Rectangle left: 10 right: 10 top: 110 bottom: 210).
aPoint := Point x: 20 y: 20.
collisions := rectangles select: [:aRect | aRect containsPoint: aPoint].

反射

反射是一个计算机科学术语,适用于有能力检查它们自己的结构的软件程序,例如检查它们的分析树或输入和输出参数的数据类型。反射是动态、交互式语言比如Smalltalk和Lisp的一个特征。具有反射的交互式程序(要么解释的要么编译的)维护所有内存内对象的状态,包括代码对象自身,这是在解析/编译期间生成的,并且是在编程上可访问和修改的。

反射也是Smalltalk这种有元模型的语言的一个特征。元模型是描述这个语言的模型,开发者可以使用元模型来做事,比如游历、检查和修改一个对象的分析树,或找到特定种类的结构的所有实例(例如在元模型中Method类的所有实例)。

Smalltalk-80是完全的反射式系统,用Smalltalk-80语言实现。Smalltalk-80提供了结构性和计算性反射二者。Smalltalk是结构性反射式系统,其结构是由Smalltalk-80对象定义的。定义这个系统的类和方法也是对象,并且完全是它们所有助力定义的系统的一部分。Smalltalk编译器将文本源代码编译成方法对象,典型是的CompiledMethod的实例。通过把它们存储入一个类的方法字典,而增加到这个类。类层级的定义类的那部分,可以向系统增加新类。这个系统是通过运行建立或定义类和方法的Smalltalk-80代码来扩展的。Smalltalk-80系统是个现场(living)系统,承载着在运行时间扩展自身的能力。

因为类是对象,可以向它们提问比如:“你实现了哪些方法?”或“你定义了什么字段/槽/实例变量?”。所以通过能应用于系统中的任何对象的普通的代码,对象可以轻易的检查、复制、(去)序列化,诸如此类[23]

Smalltalk-80还提供计算性反射,有能力观察系统的计算状态。在派生自最初Smalltalk-80的语言中,一个方法的当前活动(activation),可以作为通过伪变量命名的一个对象来访问,这个伪变量是作为六个保留字之一的thisContext。通过发送消息至thisContext,一个方法活动可以提问比如:“谁给你发送了这个消息?”。这些设施使得有可能实现协程,或类似Prolog回溯,而不需要修改虚拟机。异常系统也是使用这个设施实现的。这个设施更有趣的用法之一,是在Seaside英语Seaside (software) web框架之中,它通过为每个编辑的页面存储续体,并在它们之间切换来导航一个web站点,缓解了编程者处理Web浏览器的返回按钮的复杂性。使用Seaside编程web服务器,可以使用更常规的编程风格来完成[24]

Smalltalk如何使用反射的一个例子,是处理错误的机制。当一个对象被发送了一个它没有实现的消息的时候,虚拟机发送给这个对象doesNotUnderstand:消息,具有这个消息的实化作为实际参数。这个消息(它是另一个对象,是Message的实例),包含这个消息的选择子和它的实际参数的一个Array。在交互式Smalltalk系统中,doesNotUnderstand:的缺省实现,是打开一个错误窗口(一个Notifier)向用户报告错误。通过它和反射设施,用户可以检查错误在其中发生的上下文,重新定义犯错的代码,并继续,这一切都在这个系统之中,使用Smalltalk-80的反射设施[25][26]

通过建立只理解(实现)doesNotUnderstand:的一个类,可以建立一个实例,经由它的doesNotUnderstand:方法能拦截发送给它的任何消息。这种实例可以叫做透明代理(proxy)[27]。可以使用这种代理来实现很多设施,比如分布式Smalltalk,这里的消息在多个Smalltalk系统之间交换,和数据库接口,这里的对象透明的从数据库中排调试误,还有promise等。分布式Smalltalk的设计影响了如CORBA这样的系统。

基于映像的持久存储

多数流行的编程系统,将静态的程序代码(以类定义、函数或过程的形式),分离于动态的或运行时间的程序状态(比如对象或其他形式的程序数据)。它们在程序启动的时候装载程序代码,而任何先前的程序状态必须从配置文件或其他数据源显式的重新建立。程序(和编程者)未显式保存的设置,在每次重启时都必须再次设立。传统的程序在每次程序保存一个文件、退出和重载的时候,还失去很多有用的文档信息。这会失去细节比如回退历史或光标位置。基于映像的系统不会因为计算机关闭或OS更新,而强制失去所有这些东西。

但是很多Smalltalk系统,不区分程序数据(对象)和代码(类)。事实上,类也是对象。因此,多数Smalltalk系统,存储整个程序状态(包括类和非类对象二者)在一个映像英语system image文件之中。这个映像可以接着由Smalltalk虚拟机装载,将类Smalltalk系统恢复成先前的状态[28]。这是受到了FLEX的启发,它是Alan Kay创建的语言并描述于他的科学硕士毕业论文中[29]

Smalltalk映像类似于(可重启的)核心转储,并可以提供与核心转储相同的功能,比如延迟或远程调试,具有对出错时刻的程序状态的完全访问。将应用代码建模为某种形式的数据的其他语言比如Lisp,也经常使用基于映像的持久存储。这种持久存储的方法,对于快速开发是强力的,因为所有开发信息(比如程序的解析树),都保存而利用于调试。但是它作为一个真实的持久存储机制,也有一个严重的缺点。首先,开发者可能经常想要隐藏实现细节,并使它们在运行时间不可获得。出于法律和维护的原因,允许任何人在运行时间修改程序,对于在运行时间环境不暴露源代码的编译后的系统,不可避免的介入复杂性和潜在的错误。其次,尽管持久存储机制易于使用,它缺乏多数多用户系统需要的真正持久存储能力。最明显的是进行同多个用户并行访问相同的数据库的事务[30]

实现列表

OpenSmaltalk

OpenSmaltalk VM(OS VM)是Smalltalk运行时环境的著名实现,很多现代Smalltalk VM基于或派生自它[31]。OS VM自身是从一组Smalltalk源代码文件(它们叫做VMMaker),转译成原生C语言源代码(通过使用叫做Slang的转译器[32][33]),它依次再针对特定平台和硬件架构来编译,实际上确使Smalltalk映像的跨平台执行。源代码可以在GitHub上获得并在MIT许可证下发布。OS VM的知名派生者有:

  • Squeak,一个开源Smalltalk。
  • Pharo Smalltalk,一个开源跨平台语言。
  • Croquet VM,Croquet OS的一个与Squeak有关的Smalltalk VM。
  • Cuis-Smalltalkf[34],一个开源的小型、简洁和适用的Smalltalk。
  • Haver-Smalltalk[35],Cuis的具有完整模块系统的扩展。

JavaScript VM

其他

参见

引用

延伸阅读

外部链接

Loading related searches...

Wikiwand - on

Seamless Wikipedia browsing. On steroids.

Remove ads