[NAME] ALL.dao.tutorial.routine [TITLE] Routine and Decorator [DESCRIPTION] Routine is a relative independent block of codes that can be reused by invoking it at places where it is needed. It can accept parameters to changes its behaviour. It may also return results to its caller. 0.1 Definition Dao routines are declared with keyword routine (or function or sub, which are exactly equivalent to routine). For example, 1 routine func( a, b ) 2 { 3 a += 10; 4 b += "test"; 5 return a, b; # return more than one results. 6 } 7 8 ( r1, r2 ) = func( 111, "AAA" ); 9 r3 = func( r1, r2 ); defines a function that can take two parameters as input, and return two values as output. 0.2 Named Parameter In Dao the function parameters are named, and parameter values can be passed in by name: 1 func( b => 123, a => "ABC" ); 0.3 Parameter Type and Default Value It is also possible to specify the type and/or the default value of a parameter. 1 routine MyRout( name : string, index = 0 ) 2 { 3 io.writeln( "NAME = ", name ) 4 io.writeln( "INDEX = ", index ) 5 } Here name is specified as string, and index is specified as an integer with default value 0. Any parameter after a parameter with default value must have default values as well. If a routine is called with wrong type of parameters, or no value is passed to a parameter without a default value, an exception will be raised and the execution will abort. 0.4 Routine Overloading Routine overloading by parameter types is also supported in Dao, which means that multiple routines can be defined with the same name, but different parameter signatures. 1 routine MyRout( index : int, name = "ABC" ) 2 { 3 io.writeln( "INDEX = ", index ) 4 io.writeln( "NAME = ", name ) 5 } 6 7 MyRout( "DAO", 123 ) # invoke the first MyRout() 8 MyRout( 456, "script" ) # invoke the second MyRout() 0.5 Routine As First Class Object Dao also support first class functions / routines. They can be created as anonymous function or closure, in the following way: 1 foo = routine( x, y : TYPE, z = DEFAULT ) 2 { 3 codes; 4 } The syntax is nearly identical to the definition of a normal function, except the following differences: 1. There is no need for a function name; 2. The expressions for default parameters do not need to be constant expressions,they are evaluated at running time when the function/closure is created; 3. The function body may contain variables defined in the "upper" function that creates it; depending on the type of the "upper" variable, its copy (for simple types) or reference will be used by the created function. When such function accesses local variables from its outer/upper scope, it is created as a closure, otherwise as an anonymous function. Here is a simple example, 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.6 Coroutine and Generator See module.core.coroutine. 0.7 Code Section Methods Code section/block method is an alternative to functional methods in other languages such as Python. Dao code section is syntactically very similar to the code block in Ruby. Unlike Ruby code blocks which are compiled as closure and passed as parameter (so it's essentially a syntax sugar), Dao code section is really a code section in its host function, no closure is created a runtime. When needed, the method locate the code section in the host function and run that section of codes. To define a code section method, it will be necessary to specify two set of parameters and return types: one for the normal routine, and the other for the code section. 1 routine meth_name( meth_params ) [sect_params => sect_return] => meth_return 2 { 3 ... 4 } The parameter list signature sect_params for the code section specifies what kind of parameters this method will pass to the code section; and the section return type sect_return indicates what type of value this method expects the code section to return. Code section method can be called in the following way: 1 returned = meth_name( meth_params ) { 2 code_block 3 } If there is no method parameter, it can be simply written as: 1 returned = meth_name { 2 code_block 3 } By default, the code section receives the parameters passed in by the method through implicitly defined variables named X and Y. User can choose to use more meaningful names by, 1 returned = meth_name { [index, item] 2 code_block 3 } For example, list type has a code section method for sorting with the following signature, 1 sort( self :list<@T>, k=0 ) [X :@T, Y :@T => int] => list<@T> Here the code section parameters X and Y are used to pass two items of the list for comparison. The code section return type int indicates that the code section is expected to return an integer as the comparison result. So this sort() can be use in the following ways, 1 numlist = { 11, 44, 21, 32, 56, 67, 25 } 2 3 # Sort all by ascend order: 4 numlist.sort { X < Y } 5 6 # Sort by descend order until the largest 3 items are sorted: 7 numlist.sort( 3 ) { X > Y } 8 # Now the first 3 items of the list is the largest 3 items; 9 10 tuplist = { ( 2, 'ghi' ), ( 1, 'def' ), ( 2, 'abc' ), ( 1, 'abc' ) } 11 tuplist.sort { 12 # First sort by the first items of the tuples; 13 if( X[0] != Y[0] ) return X[0] < Y[0]; 14 # Then sort by the second items; 15 return X[1] < Y[1]; 16 } In a user defined code section method, the yield statement can be used to pass parameters and invoke the execution of the code section that is attached to the call. Here is an example for user defined code section method, 1 # A function that can be called with a code section. 2 # The code section is expected to take an integer as parameter, 3 # and return a string. 4 routine Test() [X :int => string] => string 5 { 6 io.writeln( 'In functional method!' ); 7 s = yield( 123 ); # execute the code section; 8 io.writeln( 'Yielded value:', s ); 9 return s; 10 } 11 12 Test { 13 io.writeln( 'In code section:', X ); 14 return 'abc'; 15 } 0.8 Decorator Note: this feature is only functional in the online demos and the latest devel release. Decorators are a special type of functions that can be used to modify (decorate) other functions or create modified versions of these functions. There are two main ways to decorate a function: one is to specify one or more decorators for a function at its definition; the other is to call a decorator in an expression in the same way to call a function. Decorating a function at its definition will change that function, and decorating a function in an expression will create a modified copy of the function. A decorator can be defined in almost identical way as definition a normal function with a few exceptions. First, a decorator must be declared with a name prefixed with @, and second, the first parameter must be a routine type, which determines which type of routines can be decorated by this decorator. A variable to hold the parameters that will be passed to the function, must also be declared inside a pair of brackets right after the name of the first parameter. For example, 1 routine @Decorator( func(args) : routine, extra = 123 ) 2 { 3 io.writeln( 'Calling function:', std.about( func ), extra ); 4 return func( args, ... ); # ... for parameter expanding; 5 } This decorator can be applied to any function. In this decorator, the decorated function is called with args, which is expanded for the call as indicated by .... args is a specially declared variable to hold the parameters that will be passed to the function. In the following example, the function is decorated and modified at its definition, 1 # brackets can be omitted when the decorator take no parameter: 2 @Decorator 3 @Decorator( 456 ) 4 routine Function() 5 { 6 io.writeln( 'Function()' ) 7 } While in this one, a modified copy of the function will be returned, 1 routine Function() 2 { 3 io.writeln( 'Function()' ) 4 } 5 func = @Decorator( Function ) If a decorator expression uses only constants, the decoration can be done at compiling time, 1 const MyFunc = @Decorator( Function ) This feature can be exploited to create modified copies of existing functions that are imported or loaded from other modules, and use the same function names, 1 load MyModule # MyFunction is defined in this module; 2 3 # Create a modified copy and use the same name: 4 const MyFunction = @Decorator( MyFunction ) Decorators can be overloaded just like normal functions, and be applied to overloaded functions, as well as class methods. Please see dao.routine.decorator for additional information.