Icon是一門領域特定的高級編程語言,有着「目標(goal)導向執行」特徵,和操縱字符串和文本模式的很多設施。它衍生自SNOBOL和SL5字符串處理語言[7]。Icon不是面向對象的,但在1996年開發了叫做Idol的面向對象擴展,它最終變成了Unicon。
歷史
在1971年8月,SNOBOL的設計者之一Ralph Griswold離開了貝爾實驗室,成為了亞利桑那大學的教授[8]。他那時將SNOBOL4介入為研究工具[9]。
作為最初在1960年代早期開發的語言,SNOBOL的語法帶有其他早期編程語言的印記,比如FORTRAN和COBOL。特別是,語言是依賴列的,像很多要錄入到打孔卡的語言一樣,有着列布局是很自然的。此外,控制結構幾乎完全基於了分支,而非使用塊,而塊在ALGOL 60中介入之後,已經成為了必備的特徵。在他遷移到亞利桑那的時候,SNOBOL4的語法已然過時了[10]。
Griswold開始致力於用傳統的流程控制結構如if…then
,來實現SNOBOL底層的成功和失敗概念。這成為了SL5,即「SNOBOL Language 5」的簡寫,但是結果不令人滿意[10]。在1977年,他考慮設計語言的新版本。他放棄了在SL5中介入的非常強力的函數系統,介入更簡單的暫停和恢復概念,並為SNOBOL4自然後繼者開發了新概念,具有如下的原則[10]:
- SNOBOL4的哲學和語義基礎;
- SL5的語法基礎;
- SL5的特徵,排除廣義的過程機制。
新語言最初叫做SNOBOL5,但因為除了底層概念外,全都與SNOBOL有着顯著的差異,最終想要一個新名字。在這個時候Xerox PARC發表了他們關於圖形用戶界面的工作,術語「icon」從而進入了計算機詞彙中。起初確定為「icon」而最終選擇了「Icon」[10]。
基本語法
Icon語言衍生自ALGOL類的結構化編程語言,因而有着類似C或Pascal的語法。Icon最類似於Pascal的,是使用了:=
語法的賦值,procedure
關鍵字和類似的語法。在另一方面,Icon使用C風格的花括號來結構化執行分組,並且程序開始於運行叫做main
的過程。
Icon還在很多方面分享了多數腳本語言(還有SNOBOL及SL5)的特徵:變量不需要聲明,類型是自動轉換的,就說數字和字符串可以自動來迴轉換。另一個常見於很多而非全部的腳本語言的特徵是,缺少行終止字符;在Icon中,不結束於分號的行,若其確有意義則由暗含的分號來終結。
過程是Icon程序的基本建造塊。儘管它們使用Pascal名稱,但工效上更像C函數並可以返回值;在Icon中沒有function
關鍵字。
目標導向執行
Icon的關鍵概念之一就是其控制結構基於表達式的「成功」或「失敗」,而非大多數其他編程語言中的布爾邏輯。這個特徵直接派生自SNOBOL,在其中表達式求值、模式匹配和模式匹配連帶替換,都可以跟隨着成功或失敗子句,用來指定在這個條件下要分支到一個語句標籤。例如,下列代碼打印「Hello, World!」五次[11]:
* 打印Hello, World!五次的SNOBOL程序
I = 1
LOOP OUTPUT = "Hello, World!"
I = I + 1
LE(I, 5) : S(LOOP)
END
要進行循環,在索引變量I
之上調用內建的函數LE()
(小於等於),並且S(LOOP)
測試它是否成功,即在I
小於等於5
之時,分支到命名標籤LOOP
而繼續下去[11]。
Icon保留了基於成功或失敗的控制流程的基本概念,但進一步發展了語言。一個變更是將加標籤的GOTO
式的分支,替代為面向塊的結構,符合在1960年代後期席捲計算機工業的結構化編程風格[10]。另一個變更是允許失敗沿着調用鏈向上傳遞,使得整個塊作為一個整體的成功或失敗。這是Icon語言的關鍵概念。而在傳統語言中,必須包括基於布爾邏輯的測試成功或失敗的代碼,並接着基於產出結果進行分支,這種測試和分支是固有於Icon代碼的,而不需要明確的寫出[12]。考慮如下複製標準輸入到標準輸出的簡單代碼:
它的含義是:「只要讀取不返回失敗,調用寫出,否則停止」[13]。在Icon中,read()
函數返回一行文本或&fail
。&fail
不是簡單的Java中的特殊返回值EOF
(文件結束)的類似者,因為它被語言依據上下文明確理解為意味着「停止處理」或「按失敗狀況處理」。這裡即使read()
導致一個錯誤它都會工作,比如說如果文件不存在。在這種情況下,語句a := read()
會失敗,而寫操作簡單的不調用。
成功和失敗將沿着調用鏈向上傳遞,意味着可以將函數調用嵌入其他函數調用內,在嵌套的函數調用失敗時,它們整體停止。例如,上面的代碼可以精簡為[14]:
在read()
命令失敗的時候,比如在文件結束之處,失敗將沿着調用鏈上傳,而write()
也會失敗。while
作為一個控制結構,在失敗時停止。Icon稱謂這個概念為「目標導向執行」,指稱這種只要某個目標達到執行就繼續的方式。在上面的例子中目標是讀整個文件;讀命令在有信息讀到的時候成功,而在沒有的時候失敗。目標因此直接編碼於語言中,不用再去檢查返回碼或類似的構造。
Icon使用目標導向機制用於進行傳統的布爾測試,儘管有着微妙的差異。一個簡單的比較如if a < b then write("a is smaller than b")
,這裡的if
子句,不像在多數語言中那樣意味着:「如果右側運算求值為真」;轉而它的意味更像是:「如果右側運算成功」。在這種情況下,如果這個比較為真,<
算子成功。如果if
子句的這個表達式成功,則調用then
子句,如果它失敗了,則調用else
子句或下一行。結果同於在其他語言中見到的傳統if…then
,如果a
小於b
,if
進行then
子句。微妙之處是相同的比較表達式可以放置在任何地方,例如:
另一個不同是<
算子如果成功,返回它的第二個實際參數,在這個例子中,如果b
大於a
,則導致它的值被寫出,否則什麼都不寫。因為並非測試本身,而是一個算子返回一個值,它們可以串聯在一起,允許像if a < b < c
這樣的事情[14] ,在多數語言中平常類型的比較下,必須寫為兩個不等式的結合,比如if (a < b) && (b < c)
。
將成功和失敗的概念與異常的概念相對比是很重要的;異常是不尋常的狀況,不是預期的結果。在Icon中失敗是預期的結果;到達文件的結束處是預期的狀況而不是異常。Icon沒有傳統意義上的異常處理,儘管失敗經常被用於類似異常的狀況下。例如,如果要讀取的文件的不存在,read()
失敗而不指示出特殊狀況[13]。在傳統語言中,沒有指示這些「其他狀況」的自然方式,典型的異常處理是「拋出」一個值,下面是用Java處理缺失文件的例子:
try {
while ((a = read()) != EOF) {
write(a);
}
} catch (Exception e) {
// 某个事情出错了,使用这个catch来退出循环
}
這種情況需要兩個比較:一個用於文件結束(EOF)而另一個用於所有其他錯誤。因為Java不允許異常作為邏輯元素來比較,就像Icon中那樣,轉而必須使用冗長的try/catch
語法。try
塊即使沒有異常拋出,也強加了性能上的懲罰,Icon避免了這種分攤成本。
目標導向執行的一個關鍵方面,是程序可能必須在一個過程失敗時倒轉到以前的狀態,這個任務叫做回溯。例如,考慮設置一個變量為一個開始位置,並接着進行可以改變這個值的操作,這是在字符串掃描中常見情況,這裡前進游標通過它所掃描的字符串。如果這個過程失敗了,任何對這個變量的後續讀取都返回最初的狀態,而非被內部操縱後的狀態是很重要的。對於這種任務,Icon有一個「可逆賦值」算子<-
,和「可逆交換」算子<->
。例如,考慮如下嘗試在一個更大字符串內找到一個模式字符串的代碼:
這個代碼開始於移動i
到10
,這是查找的開始位置。但是,如果find()
失敗,這個塊將作為整體失敗,作為一個不想要的副作用,它導致i
的值留下為10
。故而應將i := 10
替代為i <- 10
,指示i
在這個塊失敗時應當被重置為它以前的值。這提供了執行中的原子性的類似者。
生成器
在Icon中表達式經常返回一個單一的值,例如5 > x
,將求值並且如果x
的值小於5
則成功並返回x
,否則失敗。但是,Icon還包括了過程不立即返回成功或失敗,轉而每次調用它們之時返回一個新值的概念。這些過程叫做生成器,並且是Icon語言的關鍵部份。在Icon的用語中,一個表達式或函數的求值產生一個「結果序列」。結果序列包含這個表達式或函數生成的所有可能的值。在結果序列被耗盡的時候,這個表達式或函數失敗。
Icon允許任何過程返回一個單一值或多個值,使用fail
、return
和suspend
關鍵字來控制。缺乏任何這種關鍵字的過程返回&fail
,它在執行進行到一個過程的end
處的時候發生。例如:
調用f(5)
將返回1
,而調用f(-1)
將返回&fail
。這將導致不明顯的行為,比如write(f(-1))
將什麼都輸出,因為f
失敗而暫停了write()
的操作[15]。
將一個過程轉換成一個生成器,要使用suspend
關鍵字,它意味着「返回這個值,並且在再次調用時,從這一點開始執行」。例如[13]:
建立一個生成器,它返回一系列的數,開始於i
並結束於j
,接着在它們之後返回&fail
。[a]suspend i
停止執行,並返回i
的值,而不重置任何狀態。當對相同函數做出另一次調用的時候,執行在這一點上拾起以前的值。在這種情況下,導致它進行i +:= 1
,循環回到while
的開始處,並接着返回下一個值並再次暫停。這將持續直到i <= j
失敗,在這一點上它退出這個塊並調用fail
。這允許輕易的構造迭代器[13]。
另一種類型的生成器建造器是|
即「交替算子」(alternator),它的感觀和運算就像布爾算子or
,例如:
這看起來是在說「如果y
小於x
或者5
那麼...」,實際上它是生成器的一種簡寫形式,它返回值直到脫離於這個列表的結束處。這個列表的值被注入到運算之中,在這裡是<
。所以這個例子,系統首先測試y < x
,如果x
實際上大於y
,它返回x
的值,這個測試通過,而y
的值在then
子句中寫出。然而,如果x
不大於y
,它失敗了,交替算子繼續,進行y < 5
。如果這個測試通過,寫出y
。如果y
不小於x
或者5
,交替算子用完了,測試失敗,if
子句失敗,而不進行write()
。因此,y
的值如果小於x
或5
,則它將出現在控制台上,從而履行了布爾or
的作用。函數不會被調用,除非求值它們的參數成功,所以這個例子可以簡寫為:
在內部,交替算子不是簡單的一個or
,它還可以用來構造值的任意列表。這可以用來在任意的一組值上迭代,比如:
every
類似於while
,循環經過一個生成器的返回的所有項目,在失敗時退出[15]。
因為整數列表在很多編程場景都是很常見的,Icon還包括了to
關鍵字來構造「事實上的」整數生成器:
在這種情況下,從i
到j
的值,將注入到write()
並寫出多行輸出[15]。它可以簡寫為:
Icon不是強類型的,所以交替算子列表可以包含不同類型的項目:
這將依賴於x
的值,寫出1
、"hello"
或可能的5
。
同樣的「合取算子」&
,以類似於布爾算子and
的方式來使用[16]:
這個代碼調用ItoJ
並返回一個初始值0
,它被賦值給x
。接着進行合取的右手端,並且因為x % 2
不等於0
,它寫出這個值。接着再次調用ItoJ
生成器,它賦值1
到x
,這使得右手端失敗而不打印任何東西。最終結果是從0
到10
的所有偶數的一個列表[16]。
生成器的概念對於字符串操作是很強大的。在Icon中,find()
函數是個生成器。下面的例子代碼,在一個字符串中找出"the"
的所有出現位置:
find()
在每次被every
恢復的時候,將返回"the"
的下一個實例的索引,最終達到字符串結束處並失敗。
當然人們有時會想要找到在輸入中某點之後的一個字符串,例如,掃描包含多列數據的一個文本文件。目標導向執行也能起效:
只返回"the"
出現在位置5
之後的那些位置;否則比較會失敗。成功的比較返回右手側的結果,所以把find()
放置到這個比較的右手側是重要的。
搜集
Icon包括了一些搜集類型,包括列表(它還可以用作堆棧和隊列)、表格(在其他語言中也叫做映射或字典)和集合等。Icon稱它們為「結構」。搜集是固有的生成器,並可以使用「嘆號語法」來輕易調用。例如:
使用如前面例子中見到的失敗傳播,可以組合測試和循環:
由於列表搜集是個生成器,可以使使用嘆號語法進一步簡化:
在這種情況下,在write()
內的嘆號,導致Icon從數組一個接一個的返回一行文本,並且在結束處失敗。&input
是基於生成器的read()
的類似者,它從標準輸入讀取一行,所以!&input
繼續讀取行直到文件結束。
因為Icon是無類型的,列表可以包含任何不同類型的值:
在列表內的項目可以包括其他結構。為了建造更大的列表,Icon包括了list
生成器;i := list(10, "word")
生成包含"wold"
的10
個複本的一個列表。
就像其他語言中的數組,Icon允許項目按位置來查找,比如weight := aCat[4]
。就像陣列分片那樣,索引是在元素之間的,可以通過指定範圍來獲得列表的分片,比如aCat[2:4]
產生列表["tabby",2002]
。
表格本質上是具有任意索引鍵而非僅為整數的列表:
這個代碼建立使用的0
作為任何未知鍵的缺省值的一個table
。接着向它增加了兩個項目,具有鍵"there"
和"here"
,和分別的值1
和2
。
集合也類似於列表,但是只包含任何給定值的一個單一成員。Icon包括了++
來產生兩個集合的併集,**
用於交集,和--
用於差集。Icon包括一些預定義的Cset
,即包含各種字符的集合。在Icon中有四個標準Cset
:&ucase
、&lcase
、&letters
和&digits
。可以通過用單引號包圍字符串來建造Cset
,例如vowel := 'aeiou'
.
字符串
在Icon中,字符串是字符的列表。作為一個列表,它們是生成器,並可以使用「嘆號語法」來迭代:
這將在獨立行上打印出字符串的每個字符。
子字符串可以使用在方括號內的一個範圍規定從字符串中提取出來。範圍規定可以返回到一個單一字符的一個點,或字符串的一個分片(slice)。字符串可以從左或從右索引。在一個字符串內的位置被定義為在字符之間:1A2B3C4
,也可以從右規定:−3A−2B−1C0
。例如:
這裡最後例子採用了x1[i1+:i2] : x2
表達式,產生x1
在i1
和i1 + i2
之間的子字符串。
子字符串規定可以用作字符串內的左值。這可以被用來把字符串插入到另一個字符串,或刪除字符串的某部份。例如:
Icon的下標索引是在元素之間的。給定字符串s := "ABCDEFG"
,索引是1A2B3C4D5E6F7G8
。分片s[3:5]
是在索引3
和5
之間的字符串,它是字符串"CD"
。
字符串掃描
對處理字符串的進一步簡化是「掃描」系統,通過?
來發起,它在一個字符串上調用函數:
Icon稱呼?
的左手端為「主語」,並將它傳遞到字符串函數中。所調用的find()
接受兩個參數,查找的文本作為參數一,而要在其中查找的字符串是參數二。使用?
,第二個參數是隱含的,而不由編程者來指定。在多個函數被依次調用在一個單一字符串上的常見情況下,這種風格可以顯著的所見結果代碼的長度並增加清晰性。
?
不是簡單的一種語法糖,它還為任何隨後的字符串操作,建立一個「字符串掃描環境」。這基於了兩個內部變量,&subject
和&pos
,這裡的&subject
是要掃描的字符串,而&pos
是在這個主語字符串內的「游標」或當前位置。例如:
將產生:
內建和用戶定義的函數,可以被用於在要掃描的字符串上移動。所有內建函數缺省採用&subject
和&pos
,來允許用上掃描語法。比如函數tab (i) : s
,它設置掃描位置:產生&subject[&pos:i]
,並將i
賦值到&pos
。下列例子代碼,寫出在一個字符串內,所有空白界定出的word
:
這個例子介入了一些新函數。pos()
返回&pos
的當前值。為何需要這個函數,而不簡單的直接使用&pos
的值,不是顯而易見的;原因是&pos
是一個變量,而不能呈現值&fail
,而過程pos()
能。因此pos()
提供對&pos
的輕量級包裝,它允許輕易使用Icon的目標導向控制流,而不用針對&pos
提供手寫的布爾測試。在這種情況下,測試是「&pos
是零」,在Icon的字符串位置的特異編碼中,零是行結束。如果它不是零,pos()
返回&fail
,它通過not
反轉而使得循環繼續。
many()
從當前&pos
開始,找到提供的Cset
參數的一個或多個例子。在這種情況下,它查找空格字符,所以這個函數的結果是在&pos
之後的第一個非空格字符的位置。tab()
移動&pos
到那個位置,這種情況下再次具有潛在的&fail
,例如many()
在字符結束處脫離。upto()
本質上是many()
的反函數;它返回緊前於提供的Cset
的例子的位置,接着由另一個tab()
來設置&pos
。這裡的交替用來在行結束處也停止。
這個例子通過使用更合適的「字分隔」Cset
,可以包括句號、逗號和其他標點,還有其他空白字符如tab和不換行空格,能夠變得更加健壯。這個Cset
可以接着用於many()
和upto()
。
一個更複雜的例子演示了在這個語言內生成器和字符串掃描的集成:
表達式*x
計算x
的大小。算子||
串接兩個字符串。這裡介入了內建函數match (s1,s2,i1,i2) : i3
,它匹配初始字符串:如果s1 == s2[i1+:*s1]
,產生i1 + *s1
,否則失敗;它設定有缺省值:s2
為&subject
;i1
在s2
缺省時為&pos
,否則為1
;i2
為0
。
參見
註解
引用
參考書目
外部連結
Wikiwand in your browser!
Seamless Wikipedia browsing. On steroids.
Every time you click a link to Wikipedia, Wiktionary or Wikiquote in your browser's search results, it will show the modern Wikiwand interface.
Wikiwand extension is a five stars, simple, with minimum permission required to keep your browsing private, safe and transparent.