本文簡單介紹了Smalltalk語言的一些語法規則,Smalltalk語言中使用MVC模式來構建用戶界面,即MVC模式是起源于Smalltalk語言的,不過Smalltalk的中文資料太少,這篇翻譯文章也是為我自己將要寫的續集文章——淺談MVC框架模式做一個鋪墊。
閱讀Smalltalk程序
我對smalltalk語言的介紹是基于我處理其他語言的方法:
- 審查字符集和符號
- 審查保留字(關鍵字)
- 審查每個獨特的語法格式
- 審查每個獨特的語義形式
- 審查代碼庫 (雖然這一條要求只占了全部要求的20%,但它卻要求你付出80%的努力)
那么從這里開始吧。
1.字符集和符號
標準字符集由十二個特殊字符組成:# : ^ . ' | " ; ( ) [ ]
符號有:{標識符} {數字} {字符串} {注釋} {二進制操作符} {關鍵字} {特殊符號}
標識符
跟你所想的一樣,除了使用大寫字母開頭,而不是下劃線。
capitalLettersLikeThis [??]
rather_than_underscores [?]
數字
這個也是跟你想的一樣
‘字符串’
使用單引號
”注釋“
使用雙引號
二進制操作符
由一個或兩個字符組成,組成二進制操作符的字符在實現之間有一點不同,不過如果只是為了實現閱讀Smalltalk程序這個目標,你可以假設任何不在上面的{特殊符號}里的非字母數字的字符可以組成{二進制操作符}.比如:
+
++
?*
->
關鍵字
僅僅是一個有冒號結尾的標識符,比如anyIdentifierLikeThis:
就是一個{關鍵字}。在Smalltalk里面,一個關鍵字只有在它形成“關鍵字消息”的時候才有意義,它是一種區域性的符號(不同于標識符或字符串),這意味著它作為一個單獨的符號并不特殊。一些語言有像是BEGIN
和END
之類的{關鍵字}在語言內有著特殊的意義,而關鍵字在Smalltalk中則不是這樣的,這是一種嚴格的語法格式。
特殊符號
是一些特殊字符,作為分隔符來解析語言。
#
用于符號的開頭如:#symbol
:
用于關鍵字的結尾如:keyword:
^
用于問答對象如:^answerThisObject
.
分隔語句
'
限定一個字符串
|
表示臨時變量
"
注釋
;
級聯語句
(
表達式的開始
)
表達式的結束
[
閉包域的開始
]
閉包域的結束
2.保留字
有五個保留字:nil false true self super.
這些都是保留字,因為編譯器、優化器和VM都知道它們。
nil
這個值代表未初始化的值,它也代表了某個值可能忘了初始化,就好像“我沒有主意”,“從未有過任何值”之類的。nil有時候會被NullObject或是ExceptionalValues錯誤的使用。
true and false
分別屬于單例類True和False。
self
當你在閱讀或是使用這個詞時,表示你當前使用的對象的引用。如果對象的類沒有這個方法,你要去最近的超類(父類)中讀取這個方法。
super
與self表示相同的引用對象。
閱讀下面這一段100次,直到你接受了這個事實才繼續往下看。
為什么兩個名字會指向相同的對象呢?這可能很難理解直到你使用它,super和self是指同一個對象,當你試圖弄清楚對象將執行哪個方法來響應發送的消息時,假設對象的類沒有這樣的方法。換句話說,如果對象的類確實有一個方法來傳遞你發送的消息,始終會從對象的超類中開始查找這個方法。這樣你就可以擴展超類的行為,而不必重寫它。比如,定義和超類一樣的方法aMethod,然后:
>>aMethod
super aMethod.
self doSomeMoreStuff.
或者定義aMethod來做一些新的事情,然后跟著超類的方法。
>>aMethod
self doSomeMoreStuff.
super aMethod.
3.語法格式
在Smalltalk中有一個很重要的,但你以前可能不太熟悉的概念:
任何事物都是對象
以及
所有代碼都采用單一的概念形式:anObject withSomeMessageSentToIt.(注:Smalltalk中的消息發送機制)
如果你想繼續使用C++、Java或其他面向對象的語言工作,那么你很可能不能理解這是什么。
如果它開始對你有意義,那你就停止閱讀Smalltalk,因為你正處于危險之中,稍后將詳細介紹這些...
這里有六個語法格式:
1. 一元消息發送
object isSentThisUnaryMessage.
2. 二元消息發送
object {isSentThisBinaryOperator} withThisObjectAsOperand.
3. 關鍵字消息發送
object isSentThisKeywordMessage: withThisObjectAsParameter.
object isSent: thisObject and: thisOtherObject.
object is: sent this: message with: 4 parameters: ok.
object is: sent this message: with parameters: (1 + 2).
object is: (sent this) message: (with) parameters: (3).
這可能有一些奇怪,直到你明白為止。關鍵字消息寫成C函數調用可能會看起來像這樣:
isSentThisKeywordMessage(object,andParameter);
isSentAnd(object,thisObject,thisOtherObject);
isThisWithParameters(object,sent,message,4,ok);
isMessageParameters(object,this(sent),with,(1+2));
isMessageParameters(object,(this(sent)),(with),(3));
關鍵字消息就像這樣:
isSentThisKeywordMessage:
isSent:and:
is:this:with:parameters:
is:message:parameters:
注意一個參數,或是一個二元消息的操作符,既可以是一個對象,也可以是向一個對象發送的消息。就像在C里面一個參數可能是操作符,也可能是操作數;既可以是一個字面值{對象},一個常量,一個變量,一個指針表達式,或是一個函數調用。
4. 代碼塊(也叫閉包)
[thisObject willGetThisUnaryMessageSentToIt]
[:someObject| someObject willGetThisMessage]
[:first :second| thisObject gets: first and: second]
[:first :second| first gets: thisObject and: second]
一個塊可以被認為是一個臨時類的唯一實例,沒有超類,只有一個方法。{不是真的,但是這樣思考直到你真的明白}。什么是一個方法?這取決于參數的數量:
如果一個閉包含有 | 這是唯一已知的方法 | |
---|---|---|
沒有參數 | ["一個沒有參數的閉包"] | value |
一個參數 | [:x| “有一個參數的閉包”] | value: actualParameter |
兩個參數 | [:x :y|"有兩個參數的閉包"] | value:firstActual value:secondActual |
... |
示例:
[object messageSent] value.
當這個必報接受一個一元消息,這個一元消息messageSent將被傳遞給對象object。
[some code] value.
value消息將會是閉包執行some code。
[:one| any code can be in here] value: object.
value: object消息將會與第一個參數one綁定,然后執行代碼。
5. 返回值
^ resultingObject
任何方法都會至少有一個返回值,即便你看不到它。一般情況下你都可以看到它,在方法的最后一行,如果你沒有看到它,默認將會返回^self
它的另一個作用是提前退出,比如:
object isNil ifTrue: [^thisObject].
object getsThisMessage.
^self
這可能會讓你有些不習慣,因為這不符合結構化編程里面的“單一入口/單一出口”原則。注意Smalltalk的程序很短,不,是非常的短,我們根本不在乎。一個方法僅僅只有幾行程序會讓你頭腦清楚,并且如果我們以后要對所有的出口點作出改變也比較容易。
6. 定義方法
使用瀏覽器時,實際上并沒有看到這種語法形式,但是當Smalltalk在外部環境中被描述時,使用以下語法來表示方法的定義:
- 一元
ClassName>>methodSelector
someObject getsThisMessage. someOtherObject
getsThisOtherMessage.
^answerYetAnotherObject
這意味著類名“ClassName”有一個一元消息方法methodSelector,它的定義如上面的代碼所示。
- 二元
ClassName>>+ operand
instanceVariable := instanceVariable + operand.
^self
這意味著類名“ClassName”有一個二元消息方法+ operand,它的定義如上面的代碼所示。
- 關鍵字
ClassName>>keyword: object message: text
Transcript nextPut: object; nextPut: ' '; nextPutAll: text; cr.
這意味著類名“ClassName”含有一個定義了兩個參數的關鍵字消息方法keyword: message:,它的定義如上面的代碼所示。
7. 賦值
好吧,我撒謊了,這是第七個語法格式。
在前面那個二元消息方法中,你看到了賦值語句,它非常的特別,有兩個原因:
- 因為它可能是一個二進制消息,但事實并非如此。
- 因為它沒有遵循其他消息的一致形式:
someObject isSentSomeMessage
(注:沒有遵循消息發送的形式)
8.級聯
好吧,我又撒謊了,兩次。這是第八個語法格式,另一個“一致形式”的例外。在前面的關鍵字消息方法中,你看到了一些分號。分號是這些東西的縮寫:
發送下一個消息到相同的對象(接受前一個消息的對象)
因此,下面的示例
Transcript nextPut: object; nextPut: ' '; nextPutAll: text; cr.
是指
發送nextPut: 這個關鍵字消息(和參數object)給一個叫做“Transcript”的對象,
然后發送另一個nextPut:消息(和參數' ')給同一個對象(Transcript),
然后發送一個nextPutAll:消息(和參數text)給相同的對象,
然后發送一個cr消息給它,
最后,把自己作為這個方法的返回值.(在結尾隱含了^self
)。
4.運算符優先級
每個人都喜歡記憶練習。你知道多少種優先級和結合性的組合?你應該知道多少?以下是Smalltalk的規則:
消息 | 優先級 |
---|---|
一元 | 高優先級 |
二元 | |
關鍵字 | |
符號 | 低優先級 |
其他 | 從左至右 |
當然,括號可以改變優先級,就像其他語言,就是這樣!
你會覺得不能這樣做,它并不會像你想像的那樣運行:
3 + 4 * 5 = 35 ! ? !
但是它的確這么做了,這才是對的。
這非常的愚蠢!
是的,它非常的愚蠢,把你逼瘋了大約一個星期,然后它就消失了,就像沒有問題一樣。
(注:因為在Smalltalk中并不遵循四則運算的優先級,而是從左至右運算)
就是這樣
讓我重復一遍——就是這樣!這就是整個語言,剩下的就是學習庫,學習這門語言的技巧和方言。
現在,精明的讀者可能在想一些東西,比如:
等一下,獨特的語義形式在哪里?你并沒有講控制流,也沒有涉及到變量、類型、分配和重新分配內存、指針、模版、虛函數、靜態方法等等...
很好,不過這是錯誤的,我涵蓋了所有這些。好吧,好吧,你贏了。我從來沒講過關于變量的任何東西,那是因為它沒有,除了賦值語句:
instVar1 := 'aString'.
以及臨時符號:
|aTemp anotherTemp|
你可以定義實例變量,通過在browser窗口中輸入它們的名字,以及將類變量輸入到不同的特殊位置。這里沒有特殊的語義格式,因為它們不是代碼的一部分,這里沒有類型,也沒有算法,轉換,解引用之類的‘builtin’語法,這里有分配,不過只能通過消息發送:
someClassName new
并且這里無法重新分配,當最后一個對象的引用不存在的時候,對象被垃圾回收了。你不能使用*(void *)(0)
,其他東西都不存在。
假的,你說。你沒有提到檢查控制流的特殊語法。
是的,我沒有,沒有提到任何相關的語法,因為你不需要這樣的概念,就像控制流會打亂你的語法一樣。
oh,不要覺得不可思議,當然你會覺得,它完全是特別的。
很抱歉讓你失望了,還記得我說過的“一個塊可以被認為是一個臨時類的唯一實例,沒有超類,只有一個方法“嗎?
這就是真相,block還會對其他一些消息作出響應,就像:
[ ] whileTrue: [ ]
意思是“發送一個消息給一個對象”,從字面上發送一個關鍵字消息whileTrue:(以及一個參數(第二個block))給一個對象(第一個block)。你認為當它(第一個block)得到這樣一個消息時,會做什么?第一個block將會對自身進行計算(發送一個value消息給自己),如果結果為真,將會把value消息發送給第二個block,然后重新開始。否則,它會退出并返回false。
當然,布爾值也有類似的消息方法:
False>>isTrue: aBlock
^nil
False>>ifFalse: aBlock
^aBlock value
False是一個類,它有兩個消息的方法。由于每個對象false都是類False的實例,所以沒什么可測試的。它會忽略ifTrue:的請求,當接受ifFalse:的請求時,它總是會做點什么。另一個類,True,跟False有剛好相反的特性。(別再想這個了,你會開始認為Smalltalk不想某些人認為的那么慢,比如,它比Java快...)
檢查這個庫,看看如何變更這些簡單的主題來建立你曾經想過的那些控制結構,除了一個,沒人會把switch/case語句放在庫中。這會讓初學者不爽,以后你會發現你的方法總是很短,不能容下這樣的語句,如果你想要使用它們,這意味著你的設計沒有利用好多態性,所以你應該修復你的程序...
最后一個語法糖是:
'這是一個字符串'
#這是一個符號
這兩者幾乎是相同的,只是后者是一個單例,有一個唯一的哈希值,用于查找之類的,但你可以忽略它。
希望這些能幫助你嘗試閱讀Smalltalk程序,不過你要當心!當你理解了所有的這些,你可能會發現很難繼續使用你現在正在使用的語言...無一例外,我已經警告過你了;-)
下一步是什么?
為了進一步探索Smalltalk,你可以:
安裝
Object Arts的Dolphin Smalltalk
非常精彩的作品,玩玩它,嘗試一些示例,閱讀教育中心的材料。
(這些都是免費的,直到你上癮)
閱讀
comp.lang.smalltalk
Smalltalk: Best Practice Patterns (Kent Beck)
Smalltalk Companion to Design Patterns (Brown, et al)
(和你喜歡的其他Smalltalk書籍)
掌握
Cincom系統的VisualWorks
不可思議的工具集,Smalltalk行業的標桿,強大且穩定。
一個巨大的類圖書館(a.k.a已經為你做了這些工作)
(非商業版本的VisualWorks也是免費的)。
以上就是reading smalltalk這本小小的smalltalk教程的全部翻譯內容,如果我有翻譯得不好的地方或是錯誤,希望大家可以提出來,我會改掉它。
smalltalk是一門不同于其他語言的面向對象語言,它的所有功能幾乎都是基于消息發送機制,這是其他語言沒有的特點。很多設計模式的誕生其實都是在Smalltalk語言中,包括四人幫寫的《設計模式》一書,也使用了C++和Smalltalk共同來描述那些模式。除此之外,Smalltalk擁有許多方言和不同的環境,就像Lisp一樣。上面提到的Dolphin Smalltalk是一個,Squeak也是一個比較活躍的Smalltalk方言和環境。
如果有機會我會繼續翻譯Squeak方言的一些簡單教程,希望這些內容能幫助大家了解Smalltalk,以及更好的學習設計模式。
本文遵循自由轉載-非商用-非衍生-保持署名(知識共享3.0協議)
This article follows the CC-3.0 License.