第五章 过程
5.1 过程调用
过程调用的形式是:
expr0 ( expr1 , expr2 , ..., exprn )
• 表达式 expr0 的值为一个过程值。
• 最常见的是,expr0是一个全局名称,该名称由过程声明或作为内置函数的名称
• 表达式 expr0 可能是一个求值或生成过程的表达式,例如,(F|G)(x,y) 将调用 F(x,y),然后如果回溯,将调用 G(x,y)。
• 实际参数列表,expr 1, expr2,…, expr n,可以为空。
• 实际提供的参数数量可以大于或小于过程声明中指定的参数数量。额外的参数将被忽略。缺失的参数被赋值为&null。
• 表达式可以从实际参数列表中省略,例如F(, 2, , 4)。缺失的参数被赋值为&null。
• 数按值传递。如果参数列表中有变量,则将其值传递给过程。但是,Icon会一直等到调用时才获取变量的当前值。例如
write(x:=1," ",x+:=1," ",x+:=1)
会输出
3 3 3
因为每个赋值返回变量x,而 write 在调用前完成其中表达式的求值。
• 参数从左到右求值。所有这些都必须在调用过程之前成功地给出一个值。
• 只要控制回溯到该过程,它就会为参数生成的每组值调用该过程。
• 过程本身可能失败,可能返回一个值,或者可能生成一个值序列。
• 该过程可以返回或生成可以赋值的变量。
5.2 过程声明
过程声明的形式是
procedure name (formal1 , formal2 ,..., formaln )
declarations
initial_expression_option
expression_sequence
end
• 过程声明创建了一个过程值。
• 过程的名称成为一个全局变量,并初始化为该过程值。
• 过程值可以赋值给其他变量并放入数据结构中。
• 列表 formal 1, formal 2,..., formal n 是一个由逗号分隔的零个或多个标识符的列表。它们是过程的形式参数。
• 每个形式参数被赋值为相应的实际参数的值。由于可变对象是通过指针访问的,因此指针被复制到形式参数上,形式参数和实际参数都指向同一个对象,这类似于按引用调用。
• 如果实际参数多于形式参数,则多余的实际参数将被忽略。
• 如果实际参数少于形式参数,则额外的形式参数将被赋值为初始值 &null。
• 你可以通过在最后一个形式参数 formal n 后面加一对括号 [] 来编写一个接受可变数量参数的过程。从 n 开始的所有实际参数将被放入一个列表中,并赋值给 formal n。(列表的内容在第9章讨论。)
• 允许的声明有局部和静态声明。请参见第2.3节
• 初始表达式是可选的。它在第5.7节
• expression_sequence 类似于大括号 {...} 中的表达式序列——表达式按顺序求值,每个表达式产生一个单一值或失败。每个表达式都是有界的:一旦产生了值,就不会回溯进入该表达式。
• 如果完成了表达式序列的执行并到达末尾,过程调用失败。
例13 八进制的位位置
procedure oct(i)
return if i = 0 then "0" else oct(i / 8) || i % 8
end
procedure main()
i := 1
while x:= read() do
{
write("#define ", x, " ", oct(i) )
i +:= i
}
end
5.3 参数的默认值
参数的默认值如下:
/ parameter_name := default_value
如果调用者没有提供实际参数,Icon提供&null。“/”将测试参数的值,只有当参数为&null时才会成功。&null被替换为默认值。
5.4 Return
过程通过执行“return 表达式”来返回一个值。
return e
或
return
返回的行为如下:
• 如果return后无表达式e,则过程返回&null。
• 如果return后有表达式e,则过程将e的值返回给调用者。
• 如果return后有表达式e,并且表达式e失败,则过程调用失败,导致调用方进行回溯。
• return不创建生成器,过程不会暂停。不能重新输入以生成另一个值。最多返回一个值。
• 如果e是一个变量,但不是声明为局部或静态的变量,则从过程返回该变量。也就是说,您可以为过程返回的内容赋值。您可以在“:=”的左侧使用过程调用。无法返回局部变量,因为它在过程返回后不再存在。
下面的过程,它将返回两个数字中的最大值:
例14 两个数字中的最大值
procedure max2(x,y)
x <:= y
return x
end
5.5 Fail
fail表达式
fail
导致此过程的调用失败。失败将使调用者导致回溯。
5.6 Suspend
使用suspend将过程转换为生成器。挂起的行为类似于返回,除了它将过程作为生成器离开,准备被恢复以尝试生成更多的值。格式见下:
suspend e1 do e2
suspend e1
suspend
suspend 有以下行为:
• suspend 计算其包含的表达式e1,并将e1生成的每个值传递给调用者。它类似于返回,但是过程在返回一个值后不会消失。
• 当调用者返回到过程调用时,控制返回到过程,如果存在则执行e2,并返回到表达式e1以生成下一个值。
• 当表达式e1无法生成更多的值时,就从suspend中退出,就像从every中退出一样。如果它未能生成任何值,则控制继续移动,而不为suspend的调用者提供任何值。
• suspend 后面无表达式e1时,suspend将&null返回给调用者。
• 如果e1是变量,但不是局部声明的变量,则从过程返回该变量。也就是说,您可以为过程生成的变量赋值。您可以在“:=”的左侧使用过程调用。
例15 suspend演示
procedure G()
suspend |writes(" e1")\3 do writes(" e2")
write()
suspend |writes(" e3")\2
write()
end
procedure main()
every G() do writes(" e4")
end
会输出
e1 e4 e2 e1 e4 e2 e1 e4 e2 e3 e4 e3 e4
5.7 Initial
静态声明声明过程中的变量,在调用之间保留它们的值。当然,问题出现在第一次调用过程时:变量没有以前的值。Icon通过初始声明提供了一种解决此问题的方法:
initial e
它出现在过程开始时的声明之后,在作为过程主体的表达式序列之前。
所包含的表达式e只在第一次调用过程时执行一次。
初始表达式用于初始化过程中声明的静态变量。它还用于初始化过程集合共享的一些全局变量。
假设您有两个过程,enqueue(x)和dequeue(),它们用于将项放入共享队列(实现为列表)中并从中移除项。它们需要初始化该列表,因此可以这样写(省略实际代码):
例16 使用initial
procedure enqueue(x)
initial /queue:=list()
...
end
procedure dequeue()
initial /queue:=list()
...
end
它们都检查队列是否已经初始化,如果还没有初始化,则对其进行初始化。通过使用初始表达式,他们避免了每次检查。
5.8 字符串调用
可以通过在字符串中提供过程名来调用过程。例如,如果声明了一个过程f,就可以调用它:
"f"(x)
然而,最近Icon已经被优化以节省空间,所以你必须告诉它保留你希望通过它们的名字调用的函数的名字。你可以用两种方式来做:
1) 编译命令的参数
icont -fs ...
用 -fs 指定保留函数的名称。
2) 为您可以通过字符串名称调用的每个过程用invocable声明下,例如:
invocable "f"
在程序中与全局声明处于同一级别,即外部过程。请确保将过程名称用引号括起来。
或使所有过程都可调用,用
invocable all
5.9 将过程应用于列表
假设您希望过程P用放在列表L中元素做参数。使用中缀运算符“!”:
P ! L
P ! [a,b]
5.10 适用于过程的函数
有几个内置函数可以处理过程对象。函数args报告一个过程所需的参数数量。函数proc将返回以字符串命名的过程。对于过程的字符串调用来说,这似乎没什么用,但它也将给出与操作符对应的过程,并允许您在一元和二元,或二元和三元之间进行选择。此外,在Icon程序库中还有两个有用的程序。prockind将告诉您过程对象是哪种过程。procname将返回一个过程的名称。
args(p) #返回过程p所需的参数数。如果p是具有可变参数数量的用户过程,args(p)返回声明p时使用的参数数量的负数。如果p是具有可变数量参数的内置过程,则args(p)返回-1。
proc(s) #s是字符串表示的一个过程名,返回此过程。比如:proc("max2") ! [2,3]。但需要先invocable "max2"
proc(s,i) #返回名称为字符串s的操作符的过程,该操作符接受i个参数,例如:
proc("*",1)(x) *x
proc("*",2)(x,y) x*y
proc("[]",2)(x,y) x[y]
proc("[:]",3)(x,y,z) x[y:z]
proc("...",2)(x,y) x to y
proc("...",3)(x,y,z) x to y by z
prockind(x) #如果x不是以下类型,则失败。否则,如果x是记录构造函数,则返回“c”;如果x是内置函数,则返回“f”;如果x是操作符,则返回“o”;如果x是用户定义过程,则返回“p”。
procname(x) #返回过程值x的名称(也可以是记录构造函数或运算符),如果x不是过程则失败。如果x是一个操作符,则其名称的右边会加上它的形参数,例如:
procname(write) yields "write"
procname(proc("...",3)) yields "...3"
type(p) #这是获取类型函数。p是一个过程的话,返回字符串"procedure"