[NAME] ALL.dao.tutorial.routine [TITLE] Dao Routine (Function) [DESCRIPTION] 函数是一可反复使用的代码块,它由关键字 routine定义。 它可在被使用时接受参数,参数的名称 ,类型和缺省值需在定义时给定。 它也可在使用结束时返回一个或多个结果。 考虑到一些用户习惯 ,关键词 function, sub也可用来定义函数, 其作用跟 routine完全等同。 0.1 定义 1 routine name( [ param1 [ : type | [:]= const_expr ] [, param2 [...], ... ] ] ) 2 { 3 [...] 4 } 1 routine func( a, b ) 2 { 3 io.writeln( a, b ); 4 a = 10; 5 b = "test"; 6 return a, b; # return more than one results. 7 } 8 9 r1, r2; 10 ( r1, r2 ) = func( 111, "AAA" ); 11 r3 = func( r1, r2 ); 12 io.writeln( "r1 = ", r1 ); 13 io.writeln( "r2 = ", r2 ); 14 io.writeln( "r3 = ", r3 ); 0.2 命名参数传递 道语言函数的参数可按参数名字传递: 1 func( b => 123, a => "ABC" ); 0.3 参数类型与缺省值 正如道语言变量可声明为有类型的,道函数的参数也可声明为有类型,并还可声明为有缺省值。 1 routine MyRout( name : string, index = 0 ) 2 { 3 io.writeln( "NAME = ", name ) 4 io.writeln( "INDEX = ", index ) 5 } 这里参数 name被声明为字符串,参数 index被声明为整数,并以零为缺省值。 如果一个函数调用使 用错误的参数,或没有数据传递给没有缺省值的参数, 将导致报错,并终止运行。 参数缺省值可指定给参数表中的任意参数,而不管指定缺省值的参数后面的参数是否有缺省值。如, 1 routine MyRout2( i=0, j ){ io.writeln( i, " ", j ) } 2 MyRout2( j => 10 ) 在这种情况下,如果你想用 i的缺省值,那么你需要将数据按参数名字传给 j。 0.4 常量参数 要申明不为函数所修改的常量参数,只需在参数类型前添加 const 即可: 1 routine Test( a : const list<int> ) 2 { 3 a[1] = 100; # 错误!!! 4 io.writeln( a ); 5 } 6 a = { 1, 2, 3 } 7 Test( a ); 0.5 按引用传递参数 在函数调用时,如果参数名前使用了 &, 那么该参数将作为引用被传递。 只有基本数据类型的局部 变量可作为引用被传递给函数调用,‘ 并且也只有在函数参数列表里创建引用。 1 routine Test( p : int ) 2 { 3 p += p; 4 } 5 i = 10; 6 Test( & i ); 7 io.writeln( i ); 0.6 参数聚组 类似于Python里,道语言也支持参数聚组(grouping), 它由一对括号定义,将括号里的参数聚集 为一组。 当一个元组被作为参数传递给有参数聚组的函数时, 如果与参数聚组对应的元组的元素类 型与组里的参数类型相符合, 那么该元组将被展开,并将其元素传递给组里相应的参数。 1 routine Test( a : int, ( b : string, c = 0 ) ) 2 { 3 io.writeln( a, b, c ); 4 } 5 t = ( 'abc', 123 ) 6 Test( 0, t ) 0.7 函数重载 道语言里,函数可按参数类型进行重载。也就是可对于拥有不同参数类型的函数 使用同样的名称, 函数调用时,道虚拟机根据调用中的参数选择正确的函数来运行。 1 routine MyRout( index : int, name = "ABC" ) 2 { 3 io.writeln( "INDEX = ", index ) 4 io.writeln( "NAME = ", name ) 5 } 6 7 MyRout( "DAO", 123 ) # 调用上例中的MyRout() 8 MyRout( 456, "script" ) # 调用此例中的MyRout() 0.8 匿名函数 在道语言里有匿名函数,是基本类型(也就是first-class function), 他们按如下方式定义: 1 foo = routine( x, y : TYPE, z = DEFAULT ) 2 { 3 codes; 4 } 除了以下三点不同外,这种函数的定义跟普通函数的定义完全一样: 1. 它不需要函数名,但当它被创建时,需要赋值给一个变量; 2. 参数缺省值不必是常量,可以是任意表达式,该表达时将在函数被创建时求值; 3. 函数体里可使用定义在外层函数的局部变量;根据变量的类型,他们的拷贝或 引用将被输入到 被创建的函数里。 例子: 1 a = "ABC"; 2 3 rout = routine( x, y : string, z = a+a ){ 4 a += "_abc"; 5 io.writeln( "lambda ", a ) 6 io.writeln( "lambda ", y ) 7 io.writeln( "lambda ", z ) 8 } 9 10 rout( 1, "XXX" ); 0.9 发生器(Generator)和协程(Coroutine) 当一个函数被调用时,如果它函数名前有 @符号, 此调用将不执行该函数,而是返回一个发生器或 协程。 在这样的函数里,可使用 yield语句来向调用者返回值。 当 yield语句被执行时,此函数的 运行将被暂停, 并将运行控制权转给其调用者,而当调用者再次重新执行 此函数时,函数将从暂停 处继续执行。 yield语句执行完后,它将会象函数调用一样返回值, 被返回的值就是其调用者作为 参数传入的值。 当函数运行至函数末尾或 return语句后, 函数将终止运行,并且不可续。 1 # int => tuple<int,int> 2 routine gen1( a = 0 ) 3 { 4 k = 0; 5 while( k ++ < 3 ) a = yield( k, a ); 6 return 0,0; 7 } 8 routine gen2( a = 0 ) 9 { 10 return gen1( a ); 11 } 12 g = @gen2( 1 ); 13 # 第一次调用时,参数可省略, 14 # 创建时用到的参数将被在第一次调用时使用: 15 io.writeln( 'main1: ', g() ); 16 io.writeln( 'main2: ', g( 100 ) ); 17 io.writeln( 'main3: ', g( 200 ) ); 1 routine foo( a = 0, b = '' ) 2 { 3 io.writeln( 'foo:', a ); 4 return yield( 2 * a, 'by foo()' ); 5 } 6 7 routine bar( a = 0, b = '' ) 8 { 9 io.writeln( 'bar:', a, b ); 10 ( r, s ) = foo( a + 1, b ); 11 io.writeln( 'bar:', r, s ); 12 ( r, s ) = yield( a + 100, b ); 13 io.writeln( 'bar:', r, s ); 14 return a, 'ended'; 15 } 16 17 co = @bar( 1, "a" ); 18 19 io.writeln( 'main: ', co() ); 20 io.writeln( 'main: ', co( 1, 'x' ) ); 21 io.writeln( 'main: ', co( 2, 'y' ) ); 22 # 协程已运行完,再调用将产生异常: 23 io.writeln( 'main: ', co( 3, 'z' ) ); 发生器和协程也可由标准方法 stdlib.coroutine()来创建, 不过这种情况下,函数必须同时使用标 准方法 stdlib.yield() 来返回值。 另外,这两种方法创建的发生器或协程有个很重要的区别。 使 用前缀 @创建的发生器或协程支持类型检查, 而使用 stdlib.coroutine()创建的不支持。