GNU Smalltalk

GNU Smalltalk是Smalltalk程式語言的GNU計劃實現。

這個實現不同於其他Smalltalk環境，使用文本文件作為程序輸入，並將其內容解釋為Smalltalk代碼。在這種方式下，GNU Smalltalk表現得更像是一種解釋器，而非傳統Smalltalk方式下的一種環境。GNU Smalltalk包括了對很多自由軟體庫的綁定，包括SQLite、libSDL、cairo、gettext和Expat (庫)等。

## 簡單例子

下面的例子可工作在GNU Smalltalk 3.0和以後版本上。經典的Hello, World!例子：

```smalltalk
'Hello, World!' displayNl
```

GNU Smalltalk聲明了三個文件：`stdin`、`stdout`和`stderr`，作為文件串流類（`FileStream`）的全局實例，並綁定了適合傳遞給C虛擬機的值。對象類（`Object`）定義了特有於GNU Smalltalk的四個方法：`print`、`printNl`、`store`和`storeNl`。它們對接收者做一次`printOn:`或`storeOn:`至`Transcript`對象。這個對象是文本搜集器類（`TextCollector`）的唯一實例，它通常將寫操作委託給`stdout`。

一些基本的Smalltalk代碼：

```smalltalk
"所有东西，包括一个文字，都是一个对象，所以如下可行:"
-199 abs.                                               "199"
'gst is cool' size.                                     "11"
'Slick' indexOf: $c.                                    "4"
'Nice Day Isn''t It?' asLowercase asSet asSortedCollection asString "' ''?acdeinsty'"
```

兩個`"`包圍的是注釋；`$c`是字符常量`c`；兩個`'`包圍的是字串，字串中的`''`是`'`的轉義序列。

## 搜集

構造和使用一個陣列：

```smalltalk
a := #(1 'hi' 3.14e0 1 2 (4 5)).

a at: 3.       "3.14"
a reverse.     "((4 5) 2 1 3.14 'hi' 1)"
a asSet        "Set(1 'hi' 3.14 2 (4 5))"
```

構造和使用一個散列表，它是散列搜集類（`HashedCollection`）的子類即字典類（`Dictionary`）的實例：

```smalltalk
hash := Dictionary from: { 'water' -> 'wet'. 'fire' -> 'hot' }.
hash at: 'fire'     "'hot'".

hash keysAndValuesDo:
       [ :k :v | ('%1 is %2' % { k. v }) displayNl ].
"=> fire is hot
 => water is wet"

"删除 'water' -> 'wet'"
hash removeKey: 'water'
```

GNU Smalltalk在字串類（`String`），和作為它的超類的陣列式搜集類（`ArrayedCollection`）之間，又介入了特有的字符陣列類（`CharacterArray`），它的`%`方法，將其接收者中具有的特殊轉義序列，替換為由參數給出的搜集中的元素，其中`%n`被替代為這個搜集的第`n`個元素（`1 <= n <= 9`或`A <= n <= Z`）。這裡給它的搜集，是在`{`和`}`之間包圍的，當前Smalltalk變體一般都提供的動態數組，其中的元素可以在運行時間求值。

## 塊和迭代器

參數傳遞到是為閉包的一個塊：

```smalltalk
"remember被绑定到一个块."
remember := [ :name | ('Hello, %1!' % { name }) displayNl ].

"时机成熟时 -- 调用这个闭包!"
remember value: 'world'
"=> Hello, world!"
```

從一個方法返回由兩個閉包構成的一個陣列：

```smalltalk
Integer extend [
    asClosure [
        | value |
        value := self.
        ^{ [ :x | value := x ]. [ value ] }
    ]
].
 
blocks := 10 asClosure.
setter := blocks first.
getter := blocks second.
getter value.       "10"
setter value: 21.   "21"
getter value        "21"
```

這裡用`extend`為現存的類擴展新方法是GNU Smalltalk特有的語法。

下面的考拉茲猜想例子，展示將兩個塊傳遞給接收者，並將結果資訊發送回到調用者：

```smalltalk
Integer extend [
    ifEven: evenBlock ifOdd: oddBlock [
        ^self even
            ifTrue: [ evenBlock value: self ]
            ifFalse: [ oddBlock value: self ]
    ]
].

10 ifEven: [ :n | n / 2 ] ifOdd: [ :n | n * 3 + 1 ]    "5"
```

搜集類（`Collection`）的`collect:`方法，將接收者的每個元素傳遞給一個塊來求值，返回這些結果的搜集。這類似於函數式程式語言中map函數。例如計算從`1`到`10`的平方：

```smalltalk
(1 to: 10) collect: [ :x | x squared ] "(1 4 9 16 25 36 49 64 81 100 )"
```

可迭代類（`Iterable`）定義了由子類實現的`do:`方法，將一個塊迭代於接收者的每個元素之上。這也被稱為隱式迭代器。例如迭代於陣列和區間之上：

```smalltalk
array := #(1 'hi' 3.14e0).
array do: [ :item | item displayNl ].
"=> 1"
"=> hi"
"=> 3.14"

(3 to: 6) do: [ :item | item displayNl ]
"=> 3"
"=> 4"
"=> 5"
"=> 6"
```

可迭代類的`inject:into:`方法，接受一個參數和一個塊二者；它迭代於接收者的每個元素之上，在其上執行某個函數並保持結果為一個聚集。這類似於函數式程式語言中foldl函數。這個方法是憑藉調用`do:`方法實現的。例如：

```smalltalk
#(1 3 5) inject: 10 into: [ :sum :element | sum + element ] "19"
```

在第一個趟時，這個塊接受`10`（要注入的實際參數）作為`sum`，和`1`（這個陣列的第一個元素）作為元素，結果為`11`。`11`接著成為在下一趟時的`sum`，這時向它加上`3`得到`14`。`14`接著加上`5`，最終返回`19`。

塊可以和很多內建方法一起工作，下面例子向一個文件寫一行文本，然後再讀取它的每一行並顯示：

```smalltalk
(File name: 'file.txt') withWriteStreamDo:
    [ :file | file nextPutAll: 'Wrote some text.'; nl ]

(File name: 'file.txt') readStream linesDo:
    [ :each | each displayNl ] ; close
"=> Wrote some text."
```

文件類（`File`）的特有於GNU Smalltalk的`name:`方法，返回具有絕對路徑的文件名。GNU Smalltalk有特有的文件路徑類（`FilePath`），它的`withWriteStreamDo:`方法，對接收者調用`writeStream`方法打開一個只寫的文件串流類（`FileStream`）實例，在其上調用一個塊，並在這個塊的動態範圍結束處保證（`ensure:`）關閉這個串流；它的`readStream`方法，在接收者上打開一個只讀的文件串流類（`FileStream`）實例。

串流類（`Stream`）的特有於GNU Smalltalk的`linesDo:`方法，對它的接收者的每一行都求值它的參數塊一次。在GNU Smalltalk中，文件串流類（`FileStream`）的超類，不再是作為可定位串流類（`PositionableStream`）子類的讀寫串流類（`ReadWriteStream`），而是其特有的作為串流類（`Stream`）子類的文件描述符類（`FileDescriptor`），它的`close`方法關閉這個文件。

## 類

GNU Smalltalk建立新類採用特有的語法形式：

```smalltalk
超类名字 subclass: 新类名字 [
    | 诸实例变量 |
    pragmas
    消息模式1 [ 诸语句 ]
    消息模式2 [ 诸语句 ]
    ...
    类变量1 := 表达式.
    类变量2 := 表达式.
    ...
]
```

類似的，為現存的類擴展新方法採用特有的語法形式：

```smalltalk
类表达式 extend [
    ...
]
```

在Smalltalk有關書籍中有一個常見版式約定，將一個類中的方法引用為`类名字 >> 方法名字`，這不是Squeak/Pharo語法的一部份，GNU Smalltalk將`类名字 class >> 方法名字`作為定義類方法的語法形式。

下面的代碼定義叫做`Person`的一個類，這個類有兩個實例變量`name`和`age`，它們有各自的變異子與訪問子。定義了有兩個關鍵字參數的類方法，用來創建新的類實例。定義了單獨用`age`來進行比較的`&lt;`方法，通過從`Magnitude`派生，這個類自動繼承了所有的其他比較方法的定義。這個類還通過覆寫`printOn:`的方式，定製了這個對象的列印（`print`）/顯示（`display`）方式：

```smalltalk
Magnitude subclass: Person [
    | name age |
    Person class >> name: name age: age [
        ^self new name: name; age: age; yourself
    ]

    < aPerson [ ^self age < aPerson age ]
    name [ ^name ]
    name: value [ name := value ]
    age [ ^age ]
    age: value [ age := value ]
    printOn: aStream [ aStream nextPutAll: ('%1 (%2)' % { name. age }) ]
].

group := {
    Person name: 'Dan' age: 23.
    Person name: 'Mark' age: 63.
    Person name: 'Cod' age: 16
}.

group asSortedCollection reverse
```

這裡用`asSortedCollection`方法對搜集進行排序，然後用`reverse`方法來做反轉。最終結果是按`age`反序列印了三個人的資訊：

```text
OrderedCollection (Mark (63) Dan (23) Cod (16) )
```

## 異常

要發起能夠捕獲的異常，需要調用異常類（`Exception`）及其子類的`signal`或`signal:`方法。錯誤類（`Error`）表示不可恢復的致命錯誤，警告類（`Warning`）表示重要但可恢復的錯誤，停機類（`Halt`）表示通常是漏洞（bug）的可恢復錯誤。例如：

```smalltalk
Error signal.
Error signal: 'Illegal arguments!'
```

異常通過塊閉包（`BlockClosure`）的`on:do:`方法來處理，還可以只捕獲特定的異常（和它們的子類）：

```smalltalk
[ 做些事情
] on: Exception do: [ :ex |
    处理ex中异常
]

[ 做些事情
] on: Warning do: [ :ex |
    处理ex中异常
]
```

處理器子句使用它能獲得的異常對象，可以退出或恢復一個塊；退出是預設的，但也可以顯式的指示：

```smalltalk
[ Error signal: 'foo' 
] on: Error do: [ :ex |
    ex return: 5
]

(Warning signal: 'now what?') printNl     "=> nil"

[ (Warning signal: 'now what?') printNl
] on: Warning do: [ :ex |
    ex resume: 5 
]                "=> 5"
```

在因異常狀況而要進入調試器，可以調用對象類（`Object`）的`halt`方法，或增加了一個消息參數的`halt:`方法；這二者實際上調用了對象類的`error:`方法，它通過原始操作停止執行及或啟動調試器，並展示這個錯誤消息：

```smalltalk
self halt. "halt encountered"
self halt: 'This is a message'.
self error: 'This is a message'
```

## 基本資料

- 開發者：Steve Byrne（直到1.1.5）、Paolo Bonzini（自從1.6）
- 發行日期：2003年1月12日
- 軟體版本週期：3.2.5
- 編程范型：面向對象, 腳本
- 作業系統：Unix（Linux, Cygwin, Mac OS X/Darwin）
- 語言家族：Smalltalk
- 施影響於：ANSI Smalltalk
- 文件擴展名：.st
- 許可證：GPL（針對虛擬機） + LGPL（針對類庫和映像）
- 網站： https://www.gnu.org/software/smalltalk/