Icon語言

Icon是一門領域特定的高級編程語言，有著“目標（goal）導向執行”特徵，和操縱字符串和文本模式的很多設施。它衍生自SNOBOL和SL5字符串處理語言。Icon不是面向對象的，但在1996年開發了叫做Idol的面向對象擴展，它最終變成了Unicon。

**歷史**
在1971年8月，SNOBOL的設計者之一Ralph Griswold離開了貝爾實驗室，成為了亞利桑那大學的教授pp=609。他那時將SNOBOL4介入為研究工具pp=629。
作為最初在1960年代早期開發的語言，SNOBOL的語法帶有其他早期編程語言的印記，比如FORTRAN和COBOL。特別是，語言是依賴列的，像很多要錄入到打孔卡的語言一樣，有著列布局是很自然的。此外，控制結構幾乎完全基於了分支，而非使用塊，而塊在ALGOL 60中介入之後，已經成為了必備的特徵。在他遷移到亞利桑那的時候，SNOBOL4的語法已然過時了p=53。
Griswold開始致力於用傳統的流程控制結構如if ~ then ~，來實現SNOBOL底層的成功和失敗概念。這成為了SL5，即“SNOBOL Language 5”的簡寫，但是結果不令人滿意p=53。在1977年，他考慮設計語言的新版本。他放棄了在SL5中介入的非常強力的函數系統，介入更簡單的暫停和恢復概念，併為SNOBOL4自然後繼者開發了新概念，具有如下的原則p=53：- SNOBOL4的哲學和語義基礎；- SL5的語法基礎；- SL5的特徵，排除廣義的過程機制。
新語言最初叫做SNOBOL5，但因為除了底層概念外，全都與SNOBOL有著顯著的差異，最終想要一個新名字。在這個時候Xerox PARC發表了他們關於圖形用戶界面的工作，術語“icon”從而進入了計算機詞彙中。起初確定為“icon”而最終選擇了“Icon”p=53。

**基本語法**
Icon語言衍生自ALGOL類的結構化編程語言，因而有著類似C或Pascal的語法。Icon最類似於Pascal的地方，是使用了:=語法的賦值，procedure關鍵字和類似的語法。在另一方面，Icon使用C風格的花括號來結構化執行分組，並且程序開始於運行叫做main的過程。
Icon還在很多方面分享了多數腳本語言（還有SNOBOL及SL5）的特徵：變量不需要聲明，類型是自動轉換的，就說數字和字符串可以自動來回轉換。另一個常見於很多而非全部的腳本語言的特徵，是腳本中缺少行終止字符；在Icon中，對於不結束於分號的行，由Icon編譯器自動的插入分號來終結，而這種插入的前提是它不會導致跨行的表達式遭到錯誤的分隔。
過程是Icon程序的基本建造塊。儘管它們使用Pascal名稱，但工效上更像C函數並可以返回值；在Icon中沒有function關鍵字。
procedure main() 
write("Hello, world!")
end
Icon允許任何過程，執行"icon"或"icon"語句，來分別返回一個單一值或依次返回多個值中的一個值。過程已經執行到它的"icon"處，或者執行了"icon"語句，它會返回"icon"。例如：
procedure fn(x)
if x > 0 then {
return 1
}
fail
end
這裡的"icon"在不是必需的，因為它緊前於"icon"，增加它是為了清晰性。調用fn(5)將返回1，而調用fn(-1)將返回"icon"。這將導致不明顯的行為，比如write(fn(-1))由於fn(-1)失敗而導致write()也跟著中止了；還有x := fn(-1)對x賦值也不會發生p=75。

**目標導向執行**
Icon的關鍵概念之一就是其控制結構基於表達式的“成功”或“失敗”，而非大多數其他編程語言中的布爾邏輯。這個特徵直接派生自SNOBOL，在其中表達式求值、模式匹配和模式匹配連帶替換，都可以跟隨著成功或失敗子句，用來指定在這個條件下要分支到一個語句標籤。例如，下列代碼打印“Hello, World!”五次：- 打印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而繼續下去。
Icon保留了基於成功或失敗的控制流程的基本概念，但進一步發展了語言。一個變更是將加標籤的GOTO式的分支，替代為面向塊的結構，符合在1960年代後期席捲計算機工業的結構化編程風格p=53。另一個變更是允許失敗沿著調用鏈向上傳遞，使得整個塊作為一個整體的成功或失敗。這是Icon語言的關鍵概念。而在傳統語言中，必須包括基於布爾邏輯的測試成功或失敗的代碼，並接著基於產出結果進行分支，這種測試和分支是固有於Icon代碼的，而不需要明確的寫出p=73。考慮如下複製標準輸入到標準輸出的簡單代碼：
while a := read() then write(a)
它的含義是：“只要讀取不返回失敗，調用寫出，否則停止”p=74。在Icon中，read()函數返回一行文本或"icon"。"icon"不同於C語言中的特殊返回值EOF（文件結束），它被語言依據上下文明確理解為意味著“停止處理”或“按失敗狀況處理”。這裡即使read()導致一個錯誤它都會工作，比如說如果文件不存在。在這種情況下，語句a := read()會失敗，而write()簡單的不被調用。

**沿調用鏈傳遞**
成功和失敗將沿著調用鏈向上傳遞，意味著可以將函數調用嵌入其他函數調用內，在嵌套的函數調用失敗時，它們整體停止。例如，上面的代碼可以精簡為p=2.1：
while write(read())
在read()命令失敗的時候，比如在文件結束之處，失敗將沿著調用鏈上傳，而write()也會失敗。while作為一個控制結構，在失敗時停止。Icon稱謂這個概念為“目標導向執行”，指稱這種只要某個目標達到執行就繼續的方式。在上面的例子中目標是讀整個文件；讀命令在有信息讀到的時候成功，而在沒有的時候失敗。目標因此直接編碼於語言中，不用再去檢查返回碼或類似的構造。

**成敗測試**
Icon使用目標導向機制用於進行傳統的布爾測試，儘管有著微妙的差異。一個簡單的比較如code|if a if子句，不像在多數語言中那樣意味著：“如果測試運算求值為真”；轉而它的意味更像是：“如果測試運算成功”。在這個例子情況下，如果這個比較為真，則運算成功。如果if子句測試這個表達式成功，則調用then子句，如果它失敗了，則調用else子句或下一行。結果同於在其他語言中見到的傳統if ~ then ~，它表示如果a小於b為真，則執行then關聯的語句。微妙之處是相同的比較表達式可以放置在任何地方，例如：
write(a 
另一個不同是算子如果成功，返回它的第二個運算元，在這個例子中，如果b大於a，則導致b的值被寫出，否則什麼都不寫。因為並非測試本身，而是一個算子返回一個值，它們可以串聯在一起，允許如下這樣的鏈式測試p=2.1：
a 
在多數語言中的平常類型的比較之下，同樣的語義必須寫為兩個不等式合取，比如在C語言中表達為code|a =進行數值比較，採用==進行字符串比較，以此類推。
需要注意如果測試一個變量比如c，意圖確定它是否已初始化，它在未初始化時返回一個值"icon"，所以對它的測試總是成功的，故而需要測試"icon"或"icon"。

**回溯**
目標導向執行的一個關鍵方面，是程序可能必須在一個過程失敗時倒轉到以前的狀態，這種任務叫做回溯。例如，考慮設置一個變量為一個開始位置，並接著進行可以改變這個值的操作，這是在字符串掃描中常見情況，這裡前進遊標通過它所掃描的字符串。如果這個過程失敗了，任何對這個變量的後續讀取都返回最初的狀態，而非被內部操縱後的狀態是很重要的。對於這種任務，Icon有一個可逆（reversible）賦值算子，和可逆交換算子。
例如下列表達式：
i := 10 & j := i 
其中的"icon"在指定字符串中查找符合特定模式的子字符串並依次返回其起始位置。這個表達式由其優先級比賦值:=更低的&分隔為兩部份，在第一部份中設置了一個閾值i為10。在第二個部份中，如果"icon"能成功返回了一個值並且這個值大於閾值，則code|i j；否則失敗，對j的賦值和整個表達式都會一起失敗。作為在失敗情況下不想要的副作用，這個表達式的第一部份會導致i的值遺留為10，故而應將i := 10替代為i ：
i 
在這個表達式失敗之時，i會被重置為它以前的值。這提供了在執行中的類似於原子性的機制。

**例外**
將成功和失敗的概念與例外的概念相對比是很重要的：異常是不尋常的狀況，不是預期的結果；而例外是預期的結果，比如讀取文件時到達文件的結束處，是預期的例外狀況而不是異常。Icon沒有傳統意義上的例外處理，儘管失敗經常被用於類似例外的狀況下。例如，如果要讀取的文件的不存在，read()失敗而不指示出特殊狀況p=74。在傳統語言中，沒有指示這些“其他狀況”的自然方式，典型的例外處理是throw“拋出”一個值。例如Java使用try/catch語法來處理例外：
try {
// 讀取文件等等。
} catch (EOFException e) {
// 遇到文件結束例外
} catch (IOException e) {
// 發生IO異常
}

**生成器**
在Icon中表達式經常返回一個單一的值，例如5 > x，將求值並且如果x的值小於5則成功並返回x，否則失敗。但是，Icon還包括了過程不立即返回成功或失敗，轉而每次調用它們之時返回一個新值的概念。這些過程叫做生成器，並且是Icon語言的關鍵部份。在Icon的用語中，一個表達式或函數的求值產生一個“結果序列”。結果序列包含這個表達式或函數生成的所有可能的值。在結果序列被耗盡的時候，這個表達式或函數失敗。
因為整數列表在很多編程場景都是很常見的，Icon包括了to中綴表達式來構造整數生成器：
every k := i to j do write(k)
遍歷生成器所生成的所有結果，可以使用表達式"icon"，這裡的do子句是可選的，它針對expr1生成的每個結果求值expr2，在expr1不再產生結果時失敗。
中綴表達式to的優先級高於賦值算子。在這種情況下，從i到j的值，將注入到write()並寫出多行輸出p=75。它可以簡寫為：
every write(i to j)
在Icon中，經常將合取算子&用於控制流程，它的使用方式類似於C語言和Bourne Shell中短路求值的邏輯與算子&&，需要注意不同於C語言的情況，&的優先級低於賦值算子:=p=76：
every x := 0 to 10 & x % 2 == 0 do write(x)
這段代碼調用整數生成器並得到其初始值0，它被賦值給x。接著進行合取的右手端，因為x % 2確實等於0而成功，進而合取"icon"成功，隨即執行write()寫出x的值。接著再次調用這個生成器，它賦值1到x，這使得合取的右手端失敗進而合取失敗，故而不寫出任何東西。最終結果是寫出從0到10的一個列表中的所有偶數p=76。
生成器的概念對於字符串操作是很強大的。在Icon中，"icon"函數是個生成器。下面的例子代碼，在一個字符串中找出"the"的所有出現位置：
s := "All the world's a stage. And all the men and women merely players"
every write(find("the", s))
在每次被every恢復的時候，將返回"the"的下一個實例的索引，最終達到字符串結束處並失敗。
在想要在一個字符串的某點之後的進行查找之時，目標導向執行也能起效：
every write(5 
如果找出的"the"出現在位置5之後，則比較成功並返回右手側的結果，否則比較失敗進行下一次查找。這裡把"icon"放置到這個比較的右手側是重要的。

**交替表達式**
最常見類型的生成器建造器是|即交替（alternation）表達式，求值交替者的嚴格語法描述為："icon"，"icon"首先生成expr1的結果，然後生成expr2的結果，最終依次產生多個值x1, x2, ...。它可以用來構造值的任意列表。例如可以在任意的一組值上進行迭代：
every i := 1|3|4|5|10|11|23 do write(i)
Icon不是強類型的，所以交替表達式形成的列表可以包含不同類型的項目：
every i := 1 | "hello" | x 
這將寫出1、"hello"和在x 的情況下出現的5。
交替表達式的優先級低於比較算子，高於to表達式和賦值算子:=。它在條件語句中擔當C語言和Bourne Shell中短路求值的邏輯或算子
功能，例如：
if y 
這段代碼中的|符號起到了邏輯或的作用：如果y小於x或者5，那麼寫出y的值。實際上x | 5是生成器的一種簡寫形式，它依次返回這個列表的值直到脫離於這個列表結束處，而這個列表的值被注入到這裡的運算之中。首先測試y ，如果x實際上大於y，則這次測試成功並且if子句成功；否則這次測試失敗而交替運算繼續，從而再次測試y 。如果直到交替運算完成而所有測試都未通過，則if子句失敗。在if子句成功之後，才會執行then子句write()寫出y的值。
函數只有在求值它們的參數成功之後才會被調用，所以這個例子可以簡寫為：
write("y=", (x | 5) > y)

**暫停表達式**
要將一個過程轉換成一個生成器，可以使用表達式"icon"，這裡的do子句是可選的；它暫停當前過程，將expr1生成的每個結果作為這個過程所產生結果返回。在再次調用這個過程之時於這個暫停之處恢復執行，此時先求值expr2再恢復expr1；如果expr1是生成器併產生了新結果，則再次暫停並返回它的結果；如果expr1不是生成器或者是生成器但不再產生新結果，則繼續執行後面的語句。
例如：自己定義一個ItoJ生成器p=74：
procedure ItoJ(i, j)
while i 
它建立一個生成器，它返回一系列的數，開始於i並結束於j，接著在它們之後返回"icon"。"icon"停止執行，並返回i的值，而不重置這個函數的任何狀態。當對相同函數做出另一次調用的時候，在這一點上於以前的狀態下恢復執行。它接著進行i +:= 1，然後循環回到while的開始處，接著返回下一個值並再次暫停。這個循環將持續直到i 失敗，這時調用"icon"退出。這種機制允許輕易的構造迭代器p=74。
將例子稍作修改：
procedure ItoJ(i, j)
while i 
將產生：
1
1
2
4

**數據結構**
Icon將值的蒐集稱為“結構”，包括：記錄、列表、集合和表格。
記錄是固定大小的並且它們的值通過字段名來提及。記錄像過程那樣聲明並且對整個程序而言是全局的，一個記錄的實例可以通過記錄構造子來創建。例如：
record employee(name, age, ssn, salary)
clerk := employee("John Doe", 36, "123–45–6789", 35000.0)
記錄的字段通過名字.字段形式的表達式來提及，例如"icon"。
列表在Icon有兩個角色。它是可用下標定位的一維數組（向量），也是可用專屬訪問函數操縱從而增長或縮短的堆棧和隊列。因為Icon是無類型的，列表可以包含任何不同類型的值：
clerk := "John Doe", 36, "123–45–6789", 35000.0
就像其他語言中的數組，Icon允許項目按編號從1開始的位置來查找，例如"icon"。在Icon中，可以通過指定範圍來獲得列表的分節（section），就像Array slicing那樣，索引設立在元素之間，比如code|clerk2:4|"icon"產生列表code|36, "123–45–6789"|"icon"。在列表內的項目可以包括其他結構。Icon包括了"icon"列表創建函數，用來建造更大的列表；例如"icon"，生成10個0.0的一個列表。
類似於列表，但是隻包含任何給定值的一個單一成員。Icon包括了++來產生兩個集合的並集，**用於交集，和--用於差集。可以通過用單引號包圍字符串來建造Cset，例如：
vowel := 'aeiou'
Icon包括一些預定義的Cset，即包含各種字符的集合，它有四個標準Cset："icon"、"icon"、"icon"和"icon"。
表格是有序對的蒐集，有序對被稱為元素，它由鍵和對應的值組成。表格在其他語言中叫做關聯數組、映射或字典。表格類似於列表，但是它的鍵或稱為“下標”不必須為整數而可以是任何類型的值：
symbols := table(0)
symbols"here" := 1
symbols"there" := 2
表格創建函數"icon"建立了使用的0作為任何未知鍵的缺省值的一個表格。接著向它增加了兩個項目，具有鍵"here"和"there"，及其各自的值1和2。

**蒐集遍歷**
蒐集可以通過固有的生成器來遍歷。例如針對文件的read()、針對隊列的get()和針對堆棧的pop()：
lines :=   # 建立一個空列表
while line := read() do {  # 循環從標準輸入讀取行
put(lines, line)       # 將行添加到隊列
}
while line := get(lines) do {  # 循環從隊列取出行
write(line)                # 將行寫到標準輸出
}
使用如前面例子中見到的失敗傳播，可以組合測試和循環：
lines :=   # 建立空列表
while put(lines, read())  # 添加直到所讀文件為空
while write(get(lines))   # 寫出直到所取隊列為空
前綴算子"icon"，其嚴格語法描述為"icon"，從一個運算元x1生成多個值x2, x3, ..., xn：
*如果x1是一個文件，!x1生成x1餘下的諸行。
*如果x1是一個字符串，!x1生成x1的一個字符的諸子串，並且如果x1是變量則產生諸變量。
*如果x1是一個列表、表格或記錄，!x1生成具有x1諸元素的諸變量。產生次序，對於列表和記錄是從開始至結束，而對於表格是不可預測的。
*如果x1是一個集合，!x1以不可預測的次序生成x1的諸成員。 
遍歷蒐集可以使使用歎號語法進一步簡化：
lines := 
every put(lines, !&input)
every write(!lines)
在這種情況下，在write()內的!lines，導致從列表lines一個接一個的返回一行文本，並且在結束處失敗。而"icon"，類似於從標準輸入文件"icon"讀取一行的read()，一個接一個讀取並返回諸行直到文件結束。
在Icon中，字符串是字符的列表，可以使用“歎號語法”來迭代：
every write(!"Hello, world!")
這將一行一個的打印出字符串的每個字符。

**子字符串提取**
字符串是字符的列表，可以使用在方括號內的一個範圍規定從字符串中提取出子字符串。子字符串範圍規定，可以返回到一個單一字符的一個點，或字符串的一個分節（section）或分片（slice）。
字符串可以從左至右索引或者從右至左索引，並且在一個字符串內的位置被定義為在字符之間：1A2B3C4或者−3A−2B−1C0。例如：
"Wikipedia"1 == "W"
"Wikipedia"3 == "k"
"Wikipedia"-1 == "a"
"Wikipedia"1:3 == "Wi"
"Wikipedia"-2:0 == "ia"
"Wikipedia"2+:3 == "iki"
會導致失敗。這裡最後例子採用了code|x1i1+:i2 : x2|"icon"表達式，產生x1在i1和i1 + i2之間的子字符串。
子字符串規定可以用作字符串內的左值。這可以被用來把字符串插入到另一個字符串，或刪除字符串的某部份。例如：
s := "abc"
s2 := "123"     # s現在的值是"a123c"
s := "abcdefg" 
s3:5 := "ABCD"  # s現在的值是"abABCDefg"
s := "abcdefg"
s3:5 := ""      # s現在的值是"abefg"

**字符串掃描**
對處理字符串的進一步簡化是“掃描”系統，通過?來發起，它在一個字符串上調用函數：
s ? write(find("the"))
Icon稱呼?的左手端為“主語”，並將它傳遞到字符串函數中。所調用的生成器函數"icon"接受兩個參數，查找的文本和要在其中進行查找的字符串。使用?之時，第二個參數是隱含的，而不再由編程者來指定。多個函數被依次調用於一個單一字符串上，是一種常見情況，這種風格可以顯著的縮減代碼長度並增加清晰性。
?不是簡單的一種語法糖，它還為任何隨後的字符串操作，建立一個“字符串掃描環境”。這基於了兩個內部變量，"icon"和"icon"，這裡的"icon"是要掃描的字符串，而"icon"是在這個主語字符串內的當前位置或“遊標”。
表達式"icon"，保存當前的主語和位置，並接著分別設置它們為x和1，接著求值expr；它的產出就是expr的產出，在從expr退出時它將主語和位置復原為保存的值。例如：
procedure main()
local s
s := "this is a string"
s ? write("subject=",&subject," pos=",&pos,"")
end
將產生：
subject=this is a string pos=1
內建和用戶定義的函數，可以被用於在要掃描的字符串上移動。所有內建函數缺省採用"icon"和"icon"，來允許用上掃描語法。比如設置掃描位置的函數"icon"：產生子字符串code|&subject&pos:i|"icon"，並且將i賦值到"icon"。下列例子代碼，寫出在一個字符串內，所有空白界定出的word：
procedure main()
local s, t, words
s := " this is  a string"
words := 
s ? {  # 建立字符串掃描環境
while not pos(0) do {  # 測試字串結束
tab(many(' '))                  # 躍過任何空白
put(words, tab(upto(' ') | 0))  # 添加直到下一個空白或結束之前的諸字符
}
}
t := get(words) 
every t 
**" " 

words
write(t)
end
將產生：
this is a string
算子
串接兩個字符串，
**是一種增廣賦值，a 
**b等價於a := a 
b。這個例子介入了一些新函數，"icon"用於測試掃描位置，如果"icon"返回"icon"，否則失敗。這裡的循環以通過not反轉的"icon"作為條件，0在Icon的字符串位置編碼中指示行結束，如果當前掃描位置不是0，"icon"返回"icon"。Icon不簡單的直接使用"icon"，因為"icon"是一個變量，不能提供"icon"值，需要提供函數"icon"對"icon"做輕量級包裝，從而便於使用Icon的目標導向控制流程。
函數從當前"icon"開始，找到在所提供的Cset參數中字符的最長序列之後的位置。在這例子中，它查找空格字符，所以這個函數的結果是在"icon"之後的第一個非空格字符的位置；接著用"icon"移動"icon"到那個位置。"icon"函數返回在所提供的Cset中字符之前的位置，接著由另一個"icon"來設置"icon"。
這個例子通過可以使用更合適的“字分隔”Cset，包括上句號、逗號和其他標點，還有其他空白字符如tab和不換行空格，從而變得更加健壯，還可以將其用於"icon"和"icon"函數。
下面給出結合了生成器與字符串掃描的一個更復雜的例子：
procedure main()
local s
s := "Mon Dec 8" 
s ? write(Mdate() | "not a valid date")
end
# 定義一個匹配函數，它返回匹配“星期 月份 日期”的一個字符串
procedure Mdate()
local retval
static months, days
initial {  # 定義一些初始值
days := 
"Mon","Tue","Wed","Thr","Fri","Sat","Sun"
months := 
"Jan","Feb","Mar","Apr","May","Jun",
"Jul","Aug","Sep","Oct","Nov","Dec"
}
suspend retval 
將產生：
Mon Dec 8
初始化子句形式為"icon"，其中的expr是於所在函數首次被調用時求值的表達式。表達式"icon"等價於"icon"。表達式"icon"計算x的大小。這裡介入了內建函數"icon"，它匹配初始字符串：如果"icon"，產生"icon"，否則失敗；它設定有缺省值：s2為"icon"；i1在s2缺省時為"icon"，否則為1；i2為0。

**八皇后問題例子**
採用回溯法求解八皇后問題的例子代碼：
global n
procedure main()
local i, r, s, u, v, x
n := 8
s := "abcdefghijklmnopqrstuvwxyz"
every x := pipeline(1) do {
r := 
every i := 1 to n do put(r, sxi 
i)
u := sort(r)
v := get(u)
every v 
**"," 

u
write(v)
}
end
procedure pipeline(i)
if i 
算子|
串接兩個列表。可逆（reversible）賦值算子，在被恢復的時候，上次暫停時它所設置的值被逆轉回原來的值。不使用的話，可以將這裡的code|suspend colc queens.icn文件中，下面演示其執行結果並提取其92個解中的前兩個解：
$ icon ./queens.icn | wc -l
92
$ icon ./queens.icn | sed -n '1,2p'
a1,b7,c5,d8,e2,f4,g6,h3
a1,b7,c4,d6,e8,f2,g5,h3

**參見**
協程

**引用**

**參考書目**

**外部鏈接**
 Icon homepagedate=20201108112544 - Oral history interview with Stephen Wampler, Charles Babbage Institute, University of Minnesota. Wampler discusses his work on the development Icon in the late 1970s.- Oral history interview with Robert Goldberg, Charles Babbage Institute, University of Minnesota. Goldberg discusses his interaction with Griswold when working on Icon in the classroom at Illinois Institute of Technology.- Oral history interview with Kenneth Walker, Charles Babbage Institute, University of Minnesota. Walker describes the work environment of the Icon project, his interactions with Griswold, and his own work on an Icon compiler.- The Icon Programming Language page date=20220430212512  on The Rosetta Code comparative programming tasks project site