学习 GNU Smalltalk

学习 GNU Smalltalk

学习 GNU Smalltalk

Table of Contents

1. 前言

2. 安装 GNU Smalltalk

3. Hello World

4. 消息传递

4.1. 类和对象

4.2. 消息传递

4.3. 消息的优先级

4.4. 消息链(message chaining)

4.5. 消息级联(message cascading)

5. 常用类

5.1. 变量

5.2. Smalltalk 常用类

5.2.1. 数字

5.2.2. 字符和字符串

5.2.3. 数组

5.2.4. 集合(set)

5.2.5. 字典(dictionary)

5.2.6. 块(block)

5.2.7. 条件判断

5.2.8. 循环

5.2.9. 异常处理

6. 创建和扩展类

6.1. 创建类

6.1.1. self

6.1.2. super

6.1.3. 默认参数

6.2. 扩展类

7. 参考资料

1. 前言

Smalltalk 是一门完全面向对象的编程语言,少量易学的语法结合面向对象思想实现了整套语言,比如没有 if 语法,而是以 ifTrue 和 ifFalse 这两个方法代替。

Smalltalk 也有许多实现,常见的有:

Squeak:由 Smalltalk-80 小组创建。

Pharo:基于 Squeak 的繁衍版。

GNU Smalltalk:GNU Smalltalk 只实现了 Smalltalk 语法解析及提供了一个交互式环境。

Smalltalk 本身就集成了一套开发环境:虚拟机(VM)、IDE和镜像(image),所有对象(object)都保存在镜像里的,开发应用程序就是在镜像上不断创建和修改对象,最后把这个镜像供给别人用载入到他的 VM 中使用。

在 Smalltalk 集成环境中的修改过程是可以所见即所得的,这种编程模式称为 Live Coding。现在一些前端开发的编辑器实现了类似功能,以实时展现任何变动。

我只专注在语言本身的美,所以用的 GNU Smalltalk,它只包含 Smalltalk 的语法解析器,以减少集成环境的学习成本。

2. 安装 GNU Smalltalk

下载并安装 GNU Smalltalk:http://smalltalk.gnu.org/download

运行 gst 命令进入交互式模式:

$ gst

GNU Smalltalk ready

st>

“st>”是提示符,后面可以键入代码:

st> 1 + 1

2

按 Ctrl+d 终止交互式进程。

执行 gst 进入交互式,或加上代码文件名参数:

$ gst hello.st

一般脚本文件后缀名为 .st。

3. Hello World

"双引号中的是注释"

"单引号中的是字符串"

'Hello World!' printNl

执行结果如下:

st> 'Hello World!' printNl

'Hello World!' ①

'Hello World!' ②

位置①是一个字符串对象调用 printNl 方法输出的内容。printNl 作为“消息”传递给“Hello World!”这个字符串对象,这就是消息传递机制,关于消息传递我会在下章介绍。

位置②是执行的返回值。

这行代码理解为:给“Hello World!”这个字符串对象发送一条消息,让它把自身打印出来。

另还可用 Transcript 的 show 的方法,给它发送个消息,让它把“Hello World!”给打印出来:

Transcript show: 'Hello World!' "=> Hello World!Transcript"

Transcript show: 和 printNl 不同的是,Transcript show: 不会输出单引号,并且没有换行。

4. 消息传递

4.1. 类和对象

“类”和“对象”是面向对象里的两个重要术语。

类是一个对象的“模板”,基于这个“模板”来创建“对象”,对象有自己的内部状态——实例变量。

比如字符串“Hi”是一个字符串对象,它用 String 这个类模板创建。通过给“class”传递消息便可获得对象所属的类名:

'Hi' class "=> String"

1 class "=> SmallInteger "

1.0 class "=> FloatD"

4.2. 消息传递

对象之间的沟通是通过“消息传递”来完成的。“消息”(message)在其他支持面向对象编程的语言中被称作为“方法”,如 1 + 2 意思是:给“1”这个对象发送消息“+”,同时它包含参数对象“2”,多数语言把“+”这些作为运算符,而 Smalltalk 把它定义为消息,甚至逻辑判断、循环都是通过传递消息来完成的。

一条消息,由 选择器(selector) 和 消息参数 组成,消息接收对象被称为 接收者(receiver) ,如:1 + 2

接收者是1

selector是+

参数是2

Smalltalk有三种消息:

一元消息(unary message):不带任何参数的消息,如:'hello' size,它的执行结果只涉及到一个对象。

二元消息(binary message):两个对象参与,如:1 + 2。

关键字消息(keyword messages):带一个或多个参数、一个或多个选择器。如:

"带一个参数:"

3 bitAt: 1 "=> 1"

"多个选择器:"

3 bitAt: 1 put: 1 "=> 3"

当给一个对象发送消息时,Smalltalk会顺着类的继承关系一直向父类寻找,直到找到同名的消息,否则会报错,提示 did not understand ××,如:

1 test

"

报错:

Object: 1 error: did not understand #test

MessageNotUnderstood(Exception)>>signal (ExcHandling.st:254)

SmallInteger(Object)>>doesNotUnderstand: #test (SysExcept.st:1448)

UndefinedObject>>executeStatements (a String:1)

nil

"

4.3. 消息的优先级

Smalltalk 在发送多个消息时,一定会存在优先级问题。牢记一点:Smalltalk 是根据消息来分优先级的,否则会出现习惯性的错误,如:

3 + 2 * 10 "=> 50"

结果之所以是 50 是因为 Smalltalk 并不是按运算符优先级(Smalltalk 里没有运算符),“+”和“*”的优先级是相同的,所以这里先发送消息”+“给 3,计算结果会产生新的对象,然后再在这个对象上发送”*”消息。

优先级从高到低如下:

一元消息

二元消息

关键字消息

改变优先级要用括号包围:

3 + (2 * 10) "=> 23"

例:(3 + 5 bitAt: 3 put: 1) printNl,执行结果如下:

(3 + 5 bitAt: 3 put: 1) printNl "=> 12"

这里首先执行:3 + 5,然后执行:bitAt:3 put :1,最后执行:printNl。

4.4. 消息链(message chaining)

表达式为:objectName message1 message2 message3 …

首先 message1 消息发送给 objectName,然后 message2 发送给 objectName message1 返回的结果,如此循环,例如:

'hello' reverse asUppercase "=> 'OLLEH'"

4.5. 消息级联(message cascading)

用途:将多个消息发送给一个对象。

语法如下:

objectName Message1; Message2

和消息链的区别就在每个消息后面多了一个分号。

例:

'hello' reverse; asUppercase "=> 'HELLO'"

首先 reverse 消息发送给字符串 hello,

然后将 asUppercase 消息发送给字符串 hello。

所以最后结果是 HELLO。

还记得 Transcript show: 默认不会打印换行符吗,可以这样解决:

Transcript show: 'Hello world'; cr

5. 常用类

5.1. 变量

创建变量的语法如下:

variableName := object

示例:

a := 1 "=> 1"

b := 20 "=> 20"

a * b "=> 20"

"也可以一次创建多个变量,语法为:"

"| var1 var2 var3 |"

| x y z |

x := 1 "=> 1"

y := 2 "=> 2"

z := 3 "=> 3"

x * y * z "=> 6"

5.2. Smalltalk 常用类

5.2.1. 数字

10 "正数"

-10 "负数"

10.0 "浮点数"

10/3 "分数"

" 基本运算: "

1 + 1 "=> 2"

3 * 3 "=> 9"

4 / 4 "=> 1"

4 // 3 "取模"

" 判断数字是否相等,记住这里不是赋值 "

10 = 10 "=> true"

2 = 3 "=> false"

" 取 -3 的绝对值 "

-3 abs "=> 3"

" 幂运算 "

2 raisedTo: 10 "=> 1024"

" 判断3是否在 1~10 范围内 "

3 between: 1 and: 10 "=> true"

" 进制表示 "

" 语法:进制 r 数字,例如: "

" 二进制 "

2r111 "=> 7"

" 十六进制 "

16rA "=> 10"

" 三进制 "

3r2 "=> 2"

" 科学计数 "

1e2 "=> 100.0"

1e10 "=> 1.0e10"

5.2.2. 字符和字符串

" 单引号之间的内容为字符串,如: "

'hello world'

" 字符前面加 $,如: "

$a "表示字符 a"

" 判断对象是否为字符串 "

'hello' isString "=> true"

" 连接两个字符串 "

'hello ', 'world' "=> 'hello world'"

" 逆转字符串 "

'hello world' reverse "=> 'dlrow olleh'"

" 返回字符串长度 "

'hello world' size "=> 11"

" 判断字符串是否相等 "

'hello' = 'hello' "=> true"

'hello' = 'hi' "=> false"

" 取字符串第一个字符,注意索引不是从 0 开始的,返回的是字符 "

'hello' at: 1 "=> $h"

" 取前 5 个字符组成新的字符串 "

'hello world' copyFrom: 1 to: 5 "=> 'hello'"

5.2.3. 数组

" 有两种方法可以创建数组。 "

" 方法1,使用语法糖 #(item1 item2): "

#(1 2 3) "=> (1 2 3 )"

" 方法2,使用 Array 类创建对象: "

" 创建包含 10 个元素的数组,数组初始值为 nil "

aArray := Array new: 10 "=> (nil nil nil nil nil nil nil nil nil nil )"

" 数组引用:给数组对象传递“at:”消息可按下标引用。 "

" 注意数组下标是从 1 开始索引的,不是 0,否则会报错: "

" error: Invalid index 0: index out of range "

" 同样字符串对象也是如此: "

'abc' at: "报错:Object: 'abc' error: Invalid index 0: index out of range"

" 示例: "

aArray := #(1 2 3) "=> (1 2 3 )"

aArray at: 2 "=> 2"

"修改数组元素"

array := Array new: 10 "=> (nil nil nil nil nil nil nil nil nil nil )"

array at: 10 put: 10 "=> 10"

array "=> (nil nil nil nil nil nil nil nil nil 10 )"

5.2.4. 集合(set)

"集合用于创建不重复的元素集"

"创建集合"

set := Set new "=> Set ()"

"插入元素:"

set add: 'lx' "=> 'lx'"

set add: 'www.shellcodes.org' "=> 'www.shellcodes.org'"

"集合不包含重复元素,所以这里是无效操作"

set add: 'lx'

set "=> Set ('lx' 'www.shellcodes.org' )"

"删除集合里指定元素"

set remove: 'lx'

set "=> Set ('www.shellcodes.org' ) "

5.2.5. 字典(dictionary)

"创建字典"

dict := Dictionary new "=> Dictionary ()"

"添加元素:"

dict at: 'name' put: 'lx' "=> 'lx'"

dict at: 'website' put: 'www.shellcodes.org' "=> 'www.shellcodes.org'"

dict at: 'github' put: 'github.com/1u4nx' "=> 'github.com/1u4nx'"

dict "=> Dictionary ( "

" 'website'->'www.shellcodes.org' "

" 'name'->'lx' "

" 'github'->'github.com/1u4nx' "

" ) "

"按 key 取值"

dict at: 'name' "'lx'"

"获得所有的 key "

"因为 key 是不重复的,所以返回的 set 对象"

dict keys "=> Set ('github' 'name' 'website' )"

"获得所有的 value "

dict values "=> ('www.shellcodes.org' 'lx' 'github.com/1u4nx' )"

5.2.6. 块(block)

"块对象类似类似匿名函数,可将表达式放在一起。语法如下:"

"[:arg1 :arg2 | expression-1. expression-2. expression-3]"

"或者不带参数:"

"[expression-1. expression-2. expression-3]"

"每条表达式用“.”分隔开。"

"通过 value: 来传递参数"

[:msg | message := 'hi, ', msg . message printNl.] value: 'lx'

"将 Block 对象赋值"

sayHello := [:msg | ('Hello, ', msg) printNl.]

sayHello value: 'lx' "=> 'Hello, lx'"

"多个参数需要指定多个 value: 选择器"

[ :a :b :c | (a printNl) . (b printNl) . (c printNl)] value: 1 value: 2 value: 3

"输出:"

"1"

"2"

"3"

5.2.7. 条件判断

"真假分别用 true 和 false"

"条件判断"

"Smalltalk 里没有 if 语法,条件判断都是通过传递消息完成。"

"a = b,判断 a 和 b 的值是否相等:"

'hi' = 'hi' "=> true"

'hi' = 'h1' "=> false"

"a == b,判断 a 和 b 是否指向同一个对象:"

value := 10

a := 1

b := 2

c := value

c == value "=> true"

a == a "=> true"

a == b "=> false"

"a ~= b,判断 a 和 b 的值是否不等:"

'hi' ~= 'hi' "=> false"

'hi' ~= 'hl' "=> true"

"a ~~ b,判断 a 和 b 是否指向的不同的对象:"

a := 1

b := 2

a ~~ b "=> true"

a ~~ a "=> false"

"ifTrue:,对象返回 true 时,执行 block。"

a := 100

(a = 100) ifTrue: ['a equal 100' printNl] "=> 'a equal 100'"

"ifFalse:,与 ifTrue: 相反。"

(a ~= 100) ifFalse: [a printNl] "=> 100"

"ifTrue:ifFalse:,等同其他语言中的 if ... else ..."

test := (n > 10) "=> false"

test ifTrue: ['yes' printNl] ifFalse: ['no' printNl] "=> 'no'"

5.2.8. 循环

"whileTrue:,类似其他语言的 while 语句:"

"例,从 1 加到 100:"

sum := 0

n := 0

[sum < 100] whileTrue: [sum := sum +1. n := n + sum]

n "=> 5050"

"to:do:"

1 to: 10 do: [:n | n printNl]

"输出:

1

2

3

4

5

6

7

8

9

10

"

"to:by:do:,类似 to:do:,可指定步长:"

1 to: 10 by: 2 do: [:n | n printNl]

"输出:

1

3

5

7

9"

"by: 指定为负数时,做递减操作:"

10 to: 1 by: -1 do: [:n | n printNl]

"输出:

10

9

8

7

6

5

4

3

2

1

"

"遍历数组"

array do: [:n | n printNl]

"输出:

2

4

8

16

32

=> (2 4 8 16 32 )"

"遍历字典"

dict := Dictionary new

dict at: 'name' put: 'lx' "=> 'lx'"

dict at: 'website' put: 'www.shellcodes.org' "=> 'www.shellcodes.org'"

dict do: [:value | value printNl]

"输出:

'www.shellcodes.org'

'lx'

=> Dictionary (

'website'->'www.shellcodes.org'

'name'->'lx'

)"

5.2.9. 异常处理

"异常处理很简单,调用 on: 方法即可:"

array := Array new: 10.

1 to: 11 do: [ :i |

[array at: i put: i] on: SystemExceptions.IndexOutOfRange

do: [

'Index out of Range' printNl

]

].

array printNl "=> (1 2 3 4 5 6 7 8 9 10 )"

"ensure:保证无论是否发生异常最终都会执行指定的代码块。上面代码稍调整一下:"

array := Array new: 10.

1 to: 11 do: [ :i |

[array at: i put: i] ensure: [ 'done.' printNl]

].

array printNl "=> (1 2 3 4 5 6 7 8 9 10 )"

6. 创建和扩展类

6.1. 创建类

每个类都默认有个 new 方法。

所有类都是 Object 的子类。

创建类最简单的方法:

父类 subclass: 类名 [

方法 [

...

^返回一个对象

]

]

看得出,Smalltalk 中定义新的类也是基于消息传递来完成——通过调用 subclass: 来继承父类。

“^”类似其他语言中的“return”关键字,用于指定返回值。Smalltalk 的每个方法都有返回值,默认返回的 self。也正是因为默认返回 self,所以才可以用消息链(参见“消息传递”一节)。

方法命名约定:

Smalltalk 对方法的访问没有类似 Java 等语言的 public、private 属性,一般来说通过命名来约定。Smalltalk 有一些常见的命名约定如下:

my 或 self 开头,表示私有。

is 开头的返回 true 或 false。

add:、put:返回插入数据后的新对象。

remove: 返回删除后的新对象。

例,一个简单的类:

Object subclass: Say [

hello: msg [

('Hello, ', msg) printNl

]

]

say := Say new.

say hello: 'lx'. "=> 'Hello, lx'"

上面代码创建了一个 Say 类,并定义了 hello 这个 实例方法 。定义 类方法 如下:

父类 subclass: 子类 [

子类 class >> 方法名: 参数 [

...

^返回一个对象

]

]

例:

Object subclass: Say [

Say class >> msg: msg [

msg printNl

]

]

Say msg: 'Hi' "=> 'Hi'"

类方法和实例方法不同的地方在于,类方法不需要创建一个对象就可以直接调用,类似其他语言中的静态方法。

6.1.1. self

可给自身发送消息,例:

Object subclass: UserInfo [

| name |

setName: userName [

name := userName.

]

getName [

^name

]

+ otherUser [

^(self getName), ' ', (otherUser getName)

]

]

user1 := UserInfo new.

user1 setName: 'user1'.

user2 := UserInfo new.

user2 setName: 'user2'.

(user1 + user2) printNl "=> 'user1 user2'"

6.1.2. super

方法的查找过程默认是从自身开始的,如果想从父类开始就用 super。经常用在 new 方法中。

Object subclass: Say [

Say class >> say: msg [

msg printNl

]

Say class >> hi [

self say: 'haha'

]

]

Say subclass: SayHello [

SayHello class >> say: msg [

('I say: ', msg) printNl

]

SayHello class >> hi [

super hi

]

]

Say hi "=> 'haha'"

6.1.3. 默认参数

Object subclass: Say [

Say class >> msg: m [

self msg: m punctuation: '.'

]

Say class >> msg: m punctuation: p [

(m, p) printNl

]

]

Say msg: 'hello world'. "=> 'hello world.',没有为 punctuation: 传递参数"

Say msg: 'hello world' punctuation: '!' "=> 'hello world!'"

6.2. 扩展类

可以对现有的类进行扩展:

类名 extend [

方法 [

...

^返回一个对象

]

]

例,对 SmallInteger 进行扩展,增加一个 printSelf 方法:

SmallInteger extend [

printSelf [

self printNl

]

]

100 printSelf "=> 100"

7. 参考资料

《Computer Programming using GNU Smalltalk》

《Smalltalk by Example》

相关推荐

盘点与同学结婚的8位明星,看看哪对最幸福?
万博365下载

盘点与同学结婚的8位明星,看看哪对最幸福?

📅 01-19 👁️ 2670
JFF Theater上线!全球免费观看日本电影的绝佳平台
脖子短粗的救星!瘦脖子运动,4个步骤快速拉长脖子瘦出天鹅颈