[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()创建的不支持。