Icon语言

Icon是一门领域特定的高级编程语言，有着“目的（goal）导向执行”特征，和操纵字符串和文本模式的很多设施。它衍生自SNOBOL和SL5字符串处理语言。Icon不是面向对象的，但在1996年开发了叫做Idol的面向对象扩展，它最终变成了Unicon。

历史
在1971年8月，SNOBOL的设计者之一离开了贝尔实验室，成为了亚利桑那大学的教授。他那时将SNOBOL4介入为研究工具。

作为最初在1960年代早期开发的语言，SNOBOL的语法带有其他早期编程语言的印记，比如FORTRAN和COBOL。特别是，语言是依赖列的，像很多要录入到打孔卡的语言一样，有着列布局是很自然的。此外，控制结构几乎完全基于了分支，而非使用块，而块在ALGOL 60中介入之后，已经成为了必备的特征。在他迁移到亚利桑那的时候，SNOBOL4的语法已然过时了。

Griswold开始致力于用传统的流程控制结构如if…then，来实现SNOBOL底层的成功和失败概念。这成为了SL5，即“SNOBOL Language 5”的简写，但是结果不令人满意。在1977年，他考虑设计语言的新版本。他放弃了在SL5中介入的非常强力的函数系统，介入更简单的暂停和恢复概念，并为SNOBOL4自然后继者开发了新概念，具有如下的原则：
 SNOBOL4的哲学和语义基础；
 SL5的语法基础；
 SL5的特征，排除广义的过程机制。

新语言最初叫做SNOBOL5，但因为除了底层概念外，全都与SNOBOL有着显著的差异，最终想要一个新名字。在这个时候Xerox PARC出版了他们关于图形用户界面的工作，术语“icon”从而进入了计算机词汇中。起初确定为“icon”而最终选择了“Icon”。

基本语法
Icon语言衍生自ALGOL类的结构化编程语言，因而有着类似C或Pascal的语法。Icon最类似于Pascal的，是使用了:=语法的赋值，procedure关键字和类似的语法。在另一方面，Icon使用C风格的花括号来结构化执行分组，并且程序开始于运行叫做main的过程。

Icon还在很多方面分享了多数脚本语言（还有SNOBOL及SL5）的特征：变量不需要声明，类型是自动转换的，就说数字和字符串可以自动来回转换。另一个常见于很多而非全部的脚本语言的特征是，缺少行终止字符；在Icon中，不结束于分号的行，若其确有意义则由暗含的分号来终结。

过程是Icon程序的基本建造块。尽管它们使用Pascal名称，但工效上更像C函数并可以返回值；在Icon中没有function关键字。

procedure doSomething(aString)
  write(aString)
end

目的导向执行
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（成功），意味着I小于等于5，它分支到命名标签LOOP并继续下去。

Icon保留了基于成功或失败的控制流程的基本概念，但进一步发展了语言。一个变更是将加标签的GOTO式的分支，替代为面向块的结构，符合在1960年代后期席卷计算机工业的结构化编程风格。另一个变更是允许失败沿着调用链向上传递，使得整个块作为一个整体的成功或失败。这是Icon语言的关键概念。而在传统语言中，必须包括基于布尔逻辑的测试成功或失败的代码，并接着基于产出结果进行分支，这种测试和分支是固有于Icon代码的，而不需要明确的写出。考虑如下复制标准输入到标准输出的简单代码：
while a := read() then write(a)
它的含义是：“只要读取不返回失败，调用写出，否则停止”。在Icon中，read()函数返回一行文本或&fail。&fail不是简单的Java中的特殊返回值EOF（文件结束）的类似者，因为它被语言依据上下文明确理解为意味着“停止处理”或“按失败状况处理”。这里即使read()导致一个错误它都会工作，比如说如果文件不存在。在这种情况下，语句a := read()会失败，而写操作简单的不调用。

成功和失败将沿着调用链向上传递，意味着可以将函数嵌入其他函数内，在失败时，它们整体停止。例如，上面的代码可以精简为：

while write(read())

在read()命令失败的时候，比如在文件结束之处，失败将沿着调用链上传，而write()也会失败。while作为一个控制结构，在失败时停止。Icon称谓这个概念为“目的导向执行”，指称这种只要某个目的达到执行就继续的方式。在上面的例子中目的是读整个文件；读命令在有信息读到的时候成功，而在没有的时候失败。目的因此直接编码于语言中，不用再去检查返回码或类似的构造。

Icon使用目的导向机制用于进行传统的布尔测试，尽管有着微妙的差异。一个简单的比较如，这里的if子句，不像在多数语言中那样意味着：“如果右侧运算求值为真”；转而它的意味更像是：“如果右侧运算成功”。在这种情况下，如果这个比较为真，<算子成功。如果if子句的这个表达式成功，则调用then子句，如果它失败了，则调用else子句或下一行。结果同于在其他语言中见到的传统if…then，如果a小于b，if进行then子句。微妙之处是相同的比较表达式可以放置在任何地方，例如：
write(a < b)

另一个不同是<算子如果成功，返回它的第二个实际参数，在这个例子中，如果b大于a，则导致它的值被写出，否则什么都不写。因为并非测试本身，而是一个算符返回一个值，它们可以串联在一起，允许像if a < b < c这样的事情 ，在多数语言中平常类型的比较下，必须写为两个不等式的结合，比如if (a < b) && (b <  c)。

将成功和失败的概念与异常的概念相对比是很重要的；异常是不寻常的状况，不是预期的结果。在Icon中失败是预期的结果；到达文件的结束处是预期的状况而不是异常。Icon没有传统意义上的异常处理，尽管失败经常被用于类似异常的状况下。例如，如果要读取的文件的不存在，read失败而不指示出特殊状况。在传统语言中，没有指示这些“其他状况”的自然方式，典型的异常处理是“抛出”一个值，下面是用Java处理缺失文件的例子：

try {
    while ((a = read()) != EOF) {
        write(a);
    }
} catch (Exception e) {
    // 某个事情出错了，使用这个catch来退出循环
}

这种情况需要两个比较：一个用于文件结束（EOF）而另一个用于所有其他错误。因为Java不允许异常作为逻辑元素来比较，就像Icon中那样，转而必须使用冗长的try/catch语法。try块即使没有异常抛出，也强加了性能上的惩罚，Icon避免了这种。

目的导向执行的一个关键方面，是程序可能必须在一个过程失败时倒转到以前的状态，这个任务叫做回溯。例如，考虑设置一个变量为一个开始位置，并接着进行可以改变这个值的操作，这是在字符串扫描中常见情况，这里前进游标通过它所扫描的字符串。如果这个过程失败了，任何对这个变量的后续读取都返回最初的状态，而非被内部操纵后的状态是很重要的。对于这种任务，Icon有一个“可逆赋值”算子<-，和“可逆交换”算子<->。例如，考虑如下尝试在一个更大字符串内找到一个模式字符串的代码：
{
  (i := 10) &
  (j := (i < find(pattern, inString)))
}
这个代码开始于移动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处的时候发生。例如：

procedure f(x)
  if x > 0 then {
    return 1
  }
end

调用f(5)将返回1，而调用f(-1)将返回&fail。这将导致不明显的行为，比如write(f(-1))将什么都输出，因为f失败而暂停了write的操作。

将一个过程转换成一个生成器，要使用suspend关键字，它意味着“返回这个值，并且在再次调用时，从这一点开始执行”。例如：

procedure ItoJ(i, j)
  while i <= j do {
    suspend i
    i +:= 1
  }
  fail
end

建立一个生成器，它返回一系列的数，开始于i并结束于j，接着在它们之后返回&fail。suspend i停止执行，并返回i的值，而不重置任何状态。当对相同函数做出另一次调用的时候，执行在这一点上拾起以前的值。在这种情况下，导致它进行i +:= 1，循环回到while的开始处，并接着返回下一个值并再次暂停。这将持续直到i <= j失败，在这一点上它退出这个块并调用fail。这允许轻易的构造迭代器。

另一种类型的生成器建造器是“子”（alternator），它的感观和运算就像布尔算子or，例如：

if y < (x | 5) then write("y=", y)
这看起来是在说“如果y小于x或者5那么...”，实际上它是生成器的一种简写形式，它返回值直到脱离于这个列表的结束处。这个列表的值被注入到运算之中，在这里是<。所以这个例子，系统首先测试y < x，如果x实际上大于y，它返回x的值，这个测试通过，而y的值在then子句中写出。然而，如果x不大于y，它失败了，交替子继续，进行y < 5。如果这个测试通过，写出y。如果y不小于x或者5，交替子用完了，测试失败，if子句失败，而不进行write。因此，y的值如果小于x或5，则它将出现在控制台上，从而履行了布尔or的作用。函数不会被调用，除非求值它们的参数成功，所以这个例子可以简写为：

write("y=", (x | 5) > y)

在内部，交替子不是简单的一个or，它还可以用来构造值的任意列表。这可以用来在任意的一组值上迭代，比如：

every i := (1|3|4|5|10|11|23) do write(i)

every类似于while，循环经过一个生成器的返回的所有项目，在失败时退出。

因为整数列表在很多编程场景都是很常见的，Icon还包括了to关键字来构造“事实上的”整数生成器：

every k := i to j do write(k)

在这种情况下，从i到j的值，将注入到write并写出多行输出。它可以简写为：

every write(i to j)

Icon不是强类型的，所以交替子列表可以包含不同类型的项目：

every i := (1 | "hello" | x < 5)  do write(i)

这将依赖于x的值，写出1、"hello"或可能的5。

同样的“合取算符”&，以类似于布尔算符and的方式来使用：

every x := ItoJ(0,10) & x % 2 == 0 do write(x)

这个代码调用ItoJ并返回一个初始值0，它被赋值给x。接着进行合取的右手端，并且因为x % 2不等于0，它写出这个值。接着再次调用ItoJ生成器，它赋值1到x，这使得右手端失败而不打印任何东西。最终结果是从0到10的所有偶数的一个列表。

生成器的概念对于字符串操作是很强大的。在Icon中，find函数是个生成器。下面的例子代码，在一个字符串中找出"the"的所有出现位置：

s := "All the world's a stage. And all the men and women merely players"
every write(find("the", s))

find在每次被every恢复的时候，将返回"the"的下一个实例的索引，最终达到字符串结束处并失败。

当然人们有时会想要找到在输入中某点之后的一个字符串，例如，扫描包含多列数据的一个文本文件。目的导向执行也能起效：

write(5 < find("the", s))

只返回"the"出现在位置5之后的那些位置；否则比较会失败。成功的比较返回右手侧的结果，所以把find放置到这个比较的右手侧是重要的。

搜集
Icon包括了一些搜集类型，包括列表，它还可以用作堆栈和队列，表格（在其他语言中也叫做映射或字典），和等。Icon称它们为“结构”。搜集是固有的生成器，并可以使用“叹号语法”来轻易调用。例如：

lines := []                    # 建立一个空列表
while line := read() do {      # 循环从标准输入读取行
  push(lines, line)            # 使用类堆栈语法来将行压入列表
}
while line := pop(lines) do {  # 循环于行可以从列表弹出之时
  write(line)                  # 将行写出
}

使用如前面例子中见到的失败传播，可以组合测试和循环：

lines := []                    # 建立空列表
while push(lines, read())      # 压入直到为空
while write(pop(lines))        # 写直到为空

由于列表搜集是个生成器，可以使使用叹号语法进一步简化：

lines := []
every push(lines, !&input)
every write(!lines)

在这种情况下，在write内的叹号，导致Icon从数组一个接一个的返回一行文本，并且在结束处失败。&input是基于生成器的read的类似者，它从标准输入读取一行，所以!&input继续读取行直到文件结束。

因为Icon是无类型的，列表可以包含任何不同类型的值：

aCat := ["muffins", "tabby", 2002, 8]

在列表内的项目可以包括其他结构。为了建造更大的列表，Icon包括了list生成器；i := list(10, "word")生成包含"wold"的10个复本的一个列表。

就像其他语言中的数组，Icon允许项目按位置来查找，比如weight := aCat[4]。就像那样，索引是在元素之间的，可以通过指定范围来获得列表的分片，比如aCat[2:4]产生列表["tabby",2002]。

表格本质上是具有任意索引键而非仅为整数的列表：
symbols := table(0)
symbols["there"] := 1
symbols["here"] := 2
这个代码建立使用的0作为任何未知键的缺省值的一个table。接着向它增加了两个项目，具有键"there"和"here"，和分别的值1和2。

集合也类似于列表，但是只包含任何给定值的一个单一成员。Icon包括了++来产生两个集合的并集，**用于交集，和--用于差集。Icon包括一些预定义的Cset，即包含各种字符的集合。在Icon中有四个标准Cset：&ucase、&lcase、&letters和&digits。可以通过用单引号包围字符串来建造Cset，例如vowel : 'aeiou'.

字符串
在Icon中，字符串是字符的列表。作为一个列表，它们是生成器，并可以使用“叹号语法”来迭代：
write(!"Hello, world!")
这将在独立行上打印出字符串的每个字符。

子字符串可以使用在方括号内的一个范围规定从字符串中提取出来。范围规定可以返回到一个单一字符的一个点，或字符串的一个分片（slice）。字符串可以从左或从右索引。在一个字符串内的位置被定义为在字符之间：1A2B3C4，也可以从右规定：−3A−2B−1C0。例如：
"Wikipedia"[1]     == "W"
"Wikipedia"[3]     == "k"
"Wikipedia"[0]     == "a"
"Wikipedia"[1:3]   == "Wi"
"Wikipedia"[-2:0]  == "ia"
"Wikipedia"[2+:3]  == "iki"
这里最后例子采用了表达式，产生x1在i1和i1 + i2之间的子字符串。

子字符串规定可以用作字符串内的左值。这可以被用来把字符串插入到另一个字符串，或删除字符串的某部份。例如：

s := "abc"
s[2] := "123"
# s现在的值是"a123c"
s := "abcdefg"
s[3:5] := "ABCD"
# s现在的值是"abABCDefg"
s := "abcdefg"
s[3:5] := ""
# s现在的值是"abefg"
Icon的下标索引是在元素之间的。给定字符串s := "ABCDEFG"，索引是1A2B3C4D5E6F7G8。分片s[3:5]是在索引3和5之间的字符串，它是字符串"CD"。

字符串扫描
对处理字符串的进一步简化是“扫描”系统，通过?来发起，它在一个字符串上调用函数：

s ? write(find("the"))

Icon称呼?的左手端为“主语”，并将它传递到字符串函数中。所调用的find接受两个参数，查找的文本作为参数一，而要在其中查找的字符串是参数二。使用?，第二个参数是隐含的，而不由编程者来指定。在多个函数被依次调用在一个单一字符串上的常见情况下，这种风格可以显著的所见结果代码的长度并增加清晰性。

?不是简单的一种语法糖，它还为任何随后的字符串操作，建立一个“字符串扫描环境”。这基于了两个内部变量，&subject和&pos，这里的&subject是要扫描的字符串，而&pos是在这个主语字符串内的“游标”或当前位置。例如：

s := "this is a string"
s ? write("subject=[",&subject,"] pos=[",&pos,"]")
将产生：
subject=[this is a string] pos=[1]

内建和用户定义的函数，可以被用于在要扫描的字符串上移动。所有内建函数缺省采用&subject和&pos，来允许用上扫描语法。比如函数，它设置扫描位置：产生，并将i赋值到&pos。下列例子代码，写出在一个字符串内，所有空白界定出的word：
s := "this is a string"
s ? {                           # 建立字符串扫描环境
  while not pos(0) do {         # 测试字串结束
    tab(many(' '))              # 跃过任何空白
    word := tab(upto(' ') | 0)  # 下一个word是直到下一个空白或行结束
    write(word)                 # 写这个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。

一个更复杂的例子演示了在这个语言内生成器和字符串扫描的集成：
procedure main()
  s := "Mon Dec 8"
  s ? write(Mdate() | "not a valid date")
end
# 定义一个匹配函数
# 它返回匹配day month dayofmonth的一个字符串
procedure Mdate()
# 定义一些初始值
static dates
static days
initial {
  days := ["Mon","Tue","Wed","Thr","Fri","Sat","Sun"]
  dates := ["Jan","Feb","Mar","Apr","May","Jun",
            "Jul","Aug","Sep","Oct","Nov","Dec"]
}
every suspend (retval <- tab(match(!days)) ||  # 匹配一个day
                =" " ||                        # 跟随着一个空白
                tab(match(!dates)) ||          # 跟随着一个month
                =" " ||                        # 跟随着一个空白
                matchdigits(2)) &              # 跟随着最多2位数字
              (=" " | pos(0)) &                # 要么是空白要么是字符串结束
              retval                           # 最终返回这个字符串
end
# 返回最多n位数字的一个字符串的匹配函数
procedure matchdigits(n)
  suspend (v := tab(many(&digits)) & *v <= n) & v
end
表达式计算x的大小。这里介入了内建函数，它匹配初始字符串：如果，产生，否则失败；它设定有缺省值：s2为&subject，如果s2缺省了，i1为&pos，否则为1，i2为0。

参见
协程

注解

引用

参考书目

外部链接

Icon homepage
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  on The Rosetta Code comparative programming tasks project site

动态类型编程语言
模式匹配编程语言
公有领域软件
面向文本编程语言
1977年建立的程式語言