Loading AI tools
来自维基百科,自由的百科全书
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]:
新语言最初叫做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
。
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.