Smalltalk 超简明教程

更新时间:2023-09-16 08:10:01 阅读量: 高中教育 文档下载

说明:文章内容仅供预览,部分内容可能不全。下载后的文档,内容与下面显示的完全一致。下载之前请确认下面内容是否您想要的,是否完整无缺。

Smalltalk 超简明教程

Simberson公司的David Buck 撰写的此文档是为C/C++/C#/Java 程序员介绍Smalltalk 的,而且已经得到他善意的许可而得以发布。如果你对正式的培训有兴趣请联系Simberson,咨询Smalltalk 培训。

我听说很多Java 和C#程序员说他们看不懂Smalltalk 代码。Smalltalk 因为只有很少的语法,所以实际上它非常的简单。Smalltalk 的强大之处是它的环境和类库。 本文意图通过代码对比,使Java 和C#程序员迅速理解Smalltalk。

Copyright David Buck 2005

● 临时变量 Smalltalk 不需要变量的类型声明。临时变量通过两个竖线来定义 Java Smalltalk int a; char b; float c;

● 赋值 Smalltalk 使用:= 赋值 Java a = 5;

● 消息 Smalltalk 有三种消息 类型 一元运算符 二元运算符 关键字 连接符 形式 以小写字母开始的字符和数字 0 1 参数 squared + do:,between: and: 例子 a := 5 | a b c | Smalltalk 使用多个冒号终结文字和数字字符 1或者多个 传输一个或多个参数时,需要使用关键字消息。每一个参数前面需要添加关键字。 Smalltalk不使用{}和;分割参数。 例子: Java myAccount.getBalance(); myAccount.setBalance (10); myAccount.transfer(20,anotherAccount) myAccount.equals (anotherAccount);

Smalltalk myAccountgetBalance myAccountsetBalance: 10 myAccount transfer: 20 to:anotherAccount myAccount = anotherAccount ● 运算优先级: 一元运算符 (首先运算) 二元运算符 (其次运算) 关键字 (最后运算) 在同等优先级下,运算是从左到右 Java 3 + 5 * 6 //答案是33 注意Smalltalk 版本,这个表达式实际上是两个消息: 消息1 接受者:3 消息: + 参数:5 结果: 8

消息2

接受者: 8 消息: * 参数: 6 结果:48

● 语句 Smalltalk 使用句号分割语句。最后一条语句不需句号。 Java Smalltalk 3 + 5 * 6 “答案是:48” Smalltalk myAccount.deposit(20); myAccount deposit: 20. myAccount.transfer(20,anotherAccount); myAccount transfer: 20 to:anotherAccount ● 常量 Smalltalk 中整数类型,字符类型,字符串类型,布尔类型,浮点数类型和双精度浮点数类型都是头等类对象。整数类型是无限精度的,会按需自动增长,而不会溢出(译者注:跟内存大小有关)。 实际上,Smalltalk 中没有char 类型,byte 类型,short 类型,int类型或者long 类型的相对应的类型。它们都是整数类型。 Java Smalltalk 5 01230 0x7f <无对应物> 200L 2e-5 2e-5d 'h' '\Ω' \\

5 8r1230 16r7f 3r21012 (你可以选择你想用的基数) <无对应物> 2e-5 2d-5 $h Character value: 16r3A9 'hello' 'can''t' ● 特定词 Smalltalk 中,nil 引用一个真实的对象,它是UndefinedObject类的实例。true 是True类的实例,flase是Flase类的实例 Java Smalltalk this null true false super base (C#) ● 方法返回 Java return value; ● 级联 Java <无对应物> ● 注释 Java /* comment */ // another comment ● 实例创建 Smalltalk 中,类是真实的对象。创建实例,只需向class 发送new。针对类的方法称为类方法(类似于Java 的static methods)。 Java Smalltalk new Reservation(); ● 构造函数 Smalltalk 没有构造函数。如果你想执行实例初始化,你可以重定义“new”类方法来初始化实例。 Java Smalltalk Reservation() { startTime = new GregorianCalendar().getTime(); endTime = new GregorianCalendar().getTime(); }

Reservation class methods: new ^super new initialize Reservation instance method: initialize startTime := Timestamp now. endTime := Timestamp now Reservation new \ Smalltalk Smalltalk myAccount deposit: 20; transfer: 20 to: anotherAccount ^value Smalltalk self nil true false super ● 块

Smalltalk 称对象为块,它是包含可执行代码的对象。Java 中最相似的东西是匿名内部类,C# 2.0 中,跟匿名托管相类似。执行“块”无需参数,可以向它发送一个value 消息

Smalltalk

| block |

block := [3 + 4].

block value \

块可以有参数,每一个块参数的声明必须以冒号(:)开头,以竖线(|)表示参数列表的结束和块代码的开始。

Smalltalk

| block |

block := [:x :y | x * 2 + y].

block value: 5 value: 3 \

● 语法结束

到此为止,我们已经介绍了Smalltalk 所有的语法。其余的事情属于类库部分。你是否注意到我们是否忘记了某些事情?if-then-else 或者while 循环?Smalltalk 只使用块和普通的消息发送。

● 控制结构 Smalltalk 没有在语言中内置控制结构。Smalltalk 使用向true 或者flase对象发生消息作为替代。 Java Smalltalk if (tries > 5) return \else return \注意^表示从方法返回,不只是适用于块。

● 循环 Smalltalk 使用块做循环。由于块是对象,所有我们可以先他们发生消息 Java Smalltalk int tries = 0; while (tries <= 5) { tryAgain(); tries++; } | tries | tries := 0. [tries <= 5] whileTrue: [ selftryAgain. tries := tries + 1] alternatively: 5 timesRepeat: [self tryAgain] tries > 5 ifTrue: [^'Too many tries'] ifFalse: [^'Trying again'] 注意timesRepeat: 是整数类型能理解的消息。它简化了块执行的次数 smalltalk入门

原文标题:I Can Read C++ and Java But I Can’t Read Smalltalk 原文作者:Wilf LaLonde

原文链接:http://www.eli.sdsu.edu/courses/spring01/cs635/readingSmalltalk.pdf

简介

有很多人告诉我他很熟悉C++或Java,但是却完全读不懂Smalltalk的代码。对于他们来说,

Smalltalk简直无法理解!对于这个问题我考虑了很久然后得到的结论是他们是对的。如果我随便挑出一些自己多年以前写的Smalltalk代码,然后假设我只明白Java去阅读的时候,我确信我是无法理解那些代码的。其实要读懂Smalltalk只须了解一些非常简单的概念,当然有些概念也是比较微妙。如果“Johnny读不懂Smalltalk代码”,我有办法。我的办法就是通过实际的例子来帮助新手快速理解Smalltalk的概念。我假设读者了解什么是面向对象变成,对于那些已经掌握Smalltalk的读者也请先假装一会没有学过smalltalk。

如此简单的文法

有些语法是很容易理解的,例如使用双引号来标识注视块;单引号标示字符串,单个字符前面加$(例如:$x标示字符“x”)。除此之外,还有symbol的概念,这是一个特殊的字符串,在整个内存中是唯一的。当源代码被编译的时候,编译器会搜索整个内存,如果发现相同的symbol存在则使用已存在的那个。有理数并不节省内存,但是相对于symbol而言处理速度更快(稍候解释)。

“this is a comment” ‘this is a string’ #’this is a symbol’ #thisIsASymbolToo

对于赋值操作和等于号,差别不是很大。

:= //Means assignment

= //Means equality comparison == //Means identity comparison

如果你给我分别使用’a’和’b’命名的对象的引用,我可以告诉你他们是否是同样的对象(使用a == b, 命名等价)或看上去相同实质上不同的对象(使用 a = b构造等价)。直观的来说,== 比较两个被引用对象的地址是否相同,而 = 则比较两个对象的整个内容是否相同。

Smalltalk代码中很少用到逗号,因为他们不是Smalltalk文法组成的一部分。这就是为什么Smalltalk的数组是没有逗号的。例如:

#(1 2 3 4 5)

然而,都好是smalltalk的一个操作符。因此你会看到他被用来连接两个字符串,例如:

‘string1’,’string2’ 无处不在的关键字

Smalltalk中关键字是无处不在的。他们的存在是为了帮助理解代码而不是增加混淆。要明白这到底是怎样一回事,让我们先来看看C++和Java的语法。例如,

t->rotate(a,v); //For C++ t.rotate(a,v); //For Java 上面的代码向对象t发送消息(注:就是调用类方法)rotate,并指定了参数a和v。为了理解这段代码,读者通常需要继续察看作为参数的变量的申明及其类型。让我们假设其有如下的申明:

Transformation t; float a; Vector v;

在Smalltalk中,变量可以引用任何对象,因此在申明变量的时候没有必要指定变量的类型。例如:

| t a v| 由于没有变量类型申明,好的Smalltalk程序员都会使用能暗示变量类型的变量名。因此,上面的申明我们通常如下表示:

| aTransformation angle aVector| 但是后面请允许我继续使用先前的变量名,因为杂志专栏给我可利用的版面很小。让我们进一步消除那些不必要的东西来继续“优化”C++和Java的语法。例如,下面的代码应该仍然很好理解:

t.rotate(a,v); //原文

t rotate(a,v); //有谁需要句点吗?(t和rotate中间的圆点) t rotate a,v; //有谁需要括号吗?

为了进一步改进上面的语法,我们需要知道参数a 和 v 究竟表示什么。让我们假设整个示例的意思是“围绕端点v进行角度为a的旋转”。那么下一步可能如下:

t.rotate by a around v; //有谁需要逗号吗?

可是如何才能知道上面这个语句中每个单词的意思呢?我们知道,在这个例子当中,t 是一个变量,rotate 是一个类方法的名称,by 是一个分隔符,a 是一个变量,around 又是一个分隔符,最后的 v 是一个变量。为了消除歧义,我们可以假设如下的变换:在所有的分隔符后面添加一个冒号。那么我们就得到下面的句子:

t.rotate by: a around: v; //有谁需要歧义吗?

最终,我们要求所有的分隔符都是方法名称的一部分。也就是说,我们需要的方法的名称是“rotate by: around:”,最后让我们去掉空格,就成了“rotateby:around:”。我们最好将中间的单词开头大写,于是“rotateBy:around:”,因此我们的例子就变成了:

t.rotateBy: a around: v; //这就是Smalltalk

也就是说方法名被分成了好几个部分。幸运的是将这些分开的部分想象成一个整体的名字并不困难。当一个类方法被定义的时候,我们可能会写成下面这样:

selfrotateBy: angle around: vector

|result|

result := COMPUTE ANWSER. ^result

在执行的时候,t和self,a和angle,v和vector有一对一的映射关系。需要注意的是^表示返回,相当于return关键字。变量self则相当于this。如果方法的最后没有显式的返回表达式,则却省为^self。也就是说,不写return语句也不会有什么问题。这同时也意味着无论调用者是否需要,类方法都将返回一个对象的引用。

事实上,Smalltalk的语法要求self不能出现在函数名的最前面,如下所示:

rotateBy: angle around: vector

|result|

result:= COMPUTE ANSWER. ^result

Smalltalk的这种关键字语法的优点就是,针对不同的方法可以使用不同的关键字。例如,我们可以像下面这样定义第二个函数:

trotateAround: vector by: angle 没有必要刻意的去记住参数的顺序,因为关键字暗示了他们的顺序。当然,作为编程者也有可能滥用关键字。例如,如果我们像下面这样定义关键字:

t rotate: angle and: vector 很明显,使用这个函数的人无法通过关键字确定参数的顺序。这是很不好的编程风格。如果只有一个参数的话,则无所谓。但是我们仍然需要只有一个关键字的方法名:

trotateAroundXBy: angle trotateAroundYBy: angle

我更倾向于将关键字理解为一种说明性的参数。但是如果没有参数的时候该怎么办呢?

tmakeIdentity: //Can a colon at the end make sense?

如果关键字是说明性参数,而实际的参数却没有的话,那么我们就不是用关键字。因此一个没有关键字的消息(类方法)将写成下面这样:

tmakeIdentity //This is Smalltalk 当然,二元操作符(也是类方法,类似C++语言的operator)的定义中也可以使用关键字,但是一元操作符不用(上面的makeIdentity是一元消息,不是一元操作符)。当很多消息同时使用的时候,我们可能得到下面这样一个表达式:

a negative | (b between: c and: d)

ifTrue: [a := c negated]

作为读者,我相信你现在知道消息negative被发送到对象a(无参数),然后true或者false将被返回;between: c and: d被发送到对象b,并返回true或者false。然后对这两个返回结果进行或运算,得到的结果对象被作为ifTrue:[a:= c negated]消息的接受者。这是一个if-then条件表达式,但是并不像C++或者Java那样需要使用特别的语法。

在Smalltalk中,这跟其他的类方法调用的语法没有任何差别,只不过消息的接收者是一个布尔对象,而消息的关键字是ifTrue,参数是[a:= c negated](这实际上是一个block对象)。

在Smalltalk中你将不会看到a:= -c这样的表达式,因为没有‘-’这样的一元操作符。但是可以写成–5 这样的表达式,但是这里的负号被作为常量定义的组成部分。因此,当你看到诸如“-3.5 negated truncated factorial”这样的表达式时,你应该立即认识到这里没有关键字。因此-3.5必须作为negated消息的接收者;而得到的结果3.5则被作为truncated消息的接收者;进一步得到的结果3将作为factorial消息的接收者,最终得到结果6。

当然,这里也存在优先级的问题,通常是从左到右,一次元消息优先级最高,多参数消息的优先级最低。对于编程的人来说这很重要,但是对于读者来说这并不影响其阅读和理解。哦,对了从左到右的优先级实际上意味着:

1+2*3 等于 9

操作符之间没有优先级之分。当然你也可能会看到下面这样的表达式,那是因为编写者知道不这样写可能会给不太熟悉Smalltalk的阅读者造成困扰:

(1+2)*3

尽管这里的括号并不必要。

分号与句号的不同

大多数的非Smalltalk读者可能会认为分号是表达式的终结符,但在Smalltalk中使用的是句号。因此我们不会写下面这样的表达式:

account deposit: 100 dollars; collection add: transformation;

正确的写法应该是下面这样:

account deposit: 100 dollars. collection add: transformation.

哦,你可能会很奇怪这里怎么可能有一个dollars消息,其实这没什么特别的。为了使这个表达式合法,Integer类中必须有一个dollars方法的定义(100将被当作Integer对象处理)。虽然在标准的Smalltalk环境中并没有这样的定义,但是我们可以自己添加。Smalltalk的基类可以简单的通过定义新的继承类来扩展。

因此,句号是表达式的终结符,但是在最后一行表达式后面也可以省略(因此你也可以把句号当成表达式之间的分隔符)。但是分号也是合法的,他用在层跌消息语法中(往一个对象中一次发送多个对象的语法)。

例如:

| p |

p := Client new. p name: 'Jack'. p age: 32.

p address: 'Earth'.

对于上面这样一个表达式我们应该如下编写:

| p |

p := Client new. p

name: 'Jack'; age: 32;

address: 'Earth'. 或者干脆写成:

| p |

p := Client new

name: 'Jack'; age: 32;

address: 'Earth'.

上面的例子中格式并没太大关系,只要愿意我们甚至可以把所有的句子都写成一行。很关键的分号指出了前一条消息发送到接受对象并使其状态发生变化之后,下一条消息将被继续发到这个接受对象。(而不是发到上一条消息的返回值对象,上条消息的返回值将被抛弃或者忽视)。

在最后一个例子当中,new被发送到类来生成一个实例(作为返回值)。然后“name:’Jack’”被发送到这个实例。第一个分号表示“name:’Jack’”消息返回的结果将被忽略,紧接着“age:32”被发送到先前的实例。第二个分号表示“age:32”返回的结果被忽略,紧接着“address:’Earth’”被发送到先前的实例。最后,“address:’Earth’”返回的结果被保存到变量p。修改接收者属性的类方法通常都返回接收者自身。因此变量p就最终被绑定到了被修改了好几次的新生成的类实例对象上。

我们可以将上面的分号替换成“AND ALSO”就会感觉很容易理解这段代码的意思了。在Smalltalk中类似这样向同一个对象连续发送消息的语法称作层跌消息。分号也可以在子表达式中使用,例如“p := (Client new name: 'Jack'; age: 32; address: 'Earth')”—注意这里的括号。

Get/Set方法使用与实例变量相同的名称

类似name,age,address这样的类实例变量在Smalltalk中全都属于私有变量。但是类方法可以提供对这些变量的访问。在C++中(Java也类似),例如,我们可以定义下面这样的访问类实例变量的方法:

longgetAge () {return age;}

voidsetAge (long newAge) {age = newAge;} 如果你有好几打的类,并且你使用上面这样的编码约定,你将会发现最终你的代码中有一大堆的以get和set打头的类方法。当然,如果你碰巧决定通过去掉这些重复的前缀以便让类方法名短些,你会发现C++的编译器将会无法编译,因为他无法从名称区分变量和函数。但是Java的编译器对于下面这样的代码则没有什么问题。

long age () {return age;}

void age (long newAge) {age = newAge;} 你能区分变量age和消息age吗?你应该可以。当你使用这条消息的时候,你需要加上括号,就像“age()或者age(22)”;当你引用这些变量的时候,则不需要使用括号。同样的类方法在Smalltalk中可以写成下面这样:

age ^age

age: newAge age := newAge

不过,我们通常通过写成两行来让代码更可读:

age

^age age: newAge

age := newAge

在Smalltalk中没有括号可以帮助你区分变量和消息,但是要区分也不是很难。如果你行的话,应该可以看出下面这句话中哪些是变量,哪些是方法:

ageageage: age age + age age 好吧,答案是3;第一个age必然是一个变量,第四个也是(每个关键字后面都必须是一个子表达式;所有的表达式都必须以变量开头),还有第七个(在一个二元操作符后面,也必须是一个子表达式)。用更简单典型的例子应该更容易理解,如下:

name size //name 必然是一个变量 self name //name必然是一个类方法

Collection的广泛使用

The two most heavily used types of collections in Smalltalk are ordered collection and dictionaries.

Arrays are conceptually ordered collections that can’t change size.

| a b |

a := OrderedCollection new

add: #red; add: #green; yourself.

b := Dictionary new

at: #red put: #rouge; at: #green put: #vert; yourself.

In each of the above assignments, the variable is bound to the result of the last message executed; i.e.,the result of “yourself” which is, hopefully, the newly created collection. Message “yourself” isdesigned to return the receiver (almost like a no-op) but “add:” and “at:put:” do not (they return theirlast parameter). So without “yourself”, we would bind “a” to #green” and “b” to #vert.

I deliberately used cascading above in order to explain why “yourself” is used at all since it appears inisolated methods in the built-in classes.

The big advantage to Smalltalk collections is that you can put any objects whatsoever in the collections.

Even the keys in dictionaries can be any type of object. Also, the objects in a specific collection don’thave to be the same type; they can all be different types. We don’t need to invent new kinds ofcollections just because we want to accumulate instances of a brand new class.

Ordered collections can be accessed like arrays; e.g., “a at: 1” indexed starting at 1. Dictionaries areaccessed the same way too; e.g., “b at: #red”. But there are many applications where we don’t need todeal with the keys. For these, there are very simply looping facilities that iterate through the items; e.g.,

a do: [:item |

USE item FOR SOMETHING]. b do: [:item |

USE item FOR SOMETHING].

Loop variable “item” gets the elements of the collection one-by-one even if they are all different types.

If necessary, we can easily find out what something is at execution time; e.g., “item isKindOf: Boat”returns true or false. There are also many special-case query messages like “item isCollection” or “itemisNumber”. There are also many looping constructs that create and return new collections such as

c := clients select: [:client | client netWorth> 500000]. d := clients collect: [:client | client name].

In the first case, we get a subcollection of those clients that are pretty rich. In the second, we get acollection of names (the original collection was a collection of clients).

序列化抽象无须创建新的class

Every now and then, a reader will find code that looks like

stuff value: x value: y value: z

where the keywords are all “value:”. This clearly looks useless and bewildering to a non-Smalltalkreader. What is happening is that the programmer has (typically) created a new abstraction facility.

Let me explain this fundamental capability that only Smalltalk supports with a challenge. Given thatwe have already introduced the Client class earlier, suppose that we want a simple facility for loopingthrough an individual client’s parts; i.e., we want the loop to first give us the name, then the next timearound, we want the age, and finally we want the address.

The conventional solution in C++ and Java is to create a special stream class or enumerator, say calledClientIterator, with facilities to initialize it, to ask it if it is at the end, and, if not, to ask for the nextobject in the iterator. Then we can have a loop that sets up the iterator and, while not at the end, getsthe next object and processes it. The advantage of the iterator is that it can provide its own variables forkeeping track of where it’s at in the sequencing process; i.e., it’s not necessary to extend the Clientclass with “temporary” instance variables needed for iterating. An example piece of code that might make use of the intended abstraction is shown below:

aClient := CODE TO GET ONE. aClientpartsDo: [:object |

objectprintOn: Transcript]

Notice that partsDo: looks like a looping construct where object is the loop variable. The first iteration,we get the name and print it on the transcript (a special workspace in the Smalltalk programmingenvironment). Then we get the age in the second iteration and finally the address in the third. Also notethat “partsDo:” is a keyword message where “aClient” is the receiver and “[:object | object printOn:Transcript]” (a block) is the parameter.

Before we go too far, let me just show you the Smalltalk solution. Then I’ll explain how it works andprovide more sophisticated examples. All we do is add one method to class Client as follows:

partsDo: aBlock

aBlock value: self name. aBlock value: self age.

aBlock value: self address.

本文来源:https://www.bwwdw.com/article/gt5h.html

Top