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