[NAME] ALL.dao.routine.decorator [TITLE] Function Decorator [DESCRIPTION] 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 enhance the functionalities of other functions by decorating them, which may either modify these functions or create a modified version of these functions. There are different ways to decorate a function, but only functions that are decorated at their defintion points are modified. In all other cases, function decoration always produces new and modified copies of the original functions. A decorator is almost identical to a noraml function except that it must be declared with a name prefixed with @, and 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. 0.1 Decorator for Any Functions If a decorator will be used to decorate any functions, the routine type for the first parameter of the decorator can be declared as the most generic function type routine Here is a simple decorator that can decorate any function. 1 routine @Decorator( func(args) : routine ) 2 { 3 io.writeln( 'Calling function:', std.about(func) ); 4 return func( args, ... ); # ... for parameter expanding; 5 } Decorators can be applied at the places where functions are defined. 1 @Decorator() 2 routine Function(){ io.writeln( 'Function()' ); } If the decorator does not take any parameter other than the function to be decorated,the brackets can be omitted: 1 @Decorator 2 routine Function( a : int ){ io.writeln( 'Function(int)', a ); } 3 4 Function(); 5 Function(123); 0.2 Decorator for Specific Function Type A decorator can be defined such that it can only be applied to a specific type of functions, if the type of such functions is specified as the first parameter of the decorator. Here is a deocrator that can only be applied to functions that accepts an integer as parameter and returns a string. For a decorator for testing such as this one, the expected output can be passed as additional parameters to the decorator: 1 routine @TestDecorator( func(args) : routine<index:int=>string>, expected = '' ) 2 { 3 res = func( args, ... ); 4 io.writeln( res ); 5 io.writeln( 'Test result:', res == expected ? 'passed' : 'failed' ); 6 return res; 7 } 8 9 @TestDecorator( 'Hello' ) 10 routine Hello( index = 0 ) 11 { 12 io.writeln( 'Calling Hello(int)' ); 13 return 'Hello'; 14 } 15 16 Hello(); 0.3 Overloaded Decorator Decorators can be overloaded just like the normal functions. Here is an overloaded deocrator that can only be applied to functions that accepts a string as parameter and returns an integer. 1 routine @TestDecorator( func(args) : routine<name:string=>int>, expected = 0 ) 2 { 3 res = func( args, ... ); 4 io.writeln( res ); 5 io.writeln( 'Test result:', res == expected ? 'passed' : 'failed' ); 6 return res; 7 } # Decorators can be chained: 1 @Decorator 2 @TestDecorator( 123 ) 3 routine Hello( name : string ) 4 { 5 io.writeln( 'Calling Hello(string)', name ); 6 return 123; 7 } 8 9 io.writeln( Hello( 'abc' ) ); 0.4 Decorator in Expression Decorator can be used in the same way as normal functions, namely calling a decorator like a normal function, and pass the function to be decorated and other values as parameters to the decorator. 1 anotherHello = @Decorator( Hello ) Using decorator in this way will produce a modified copy of the function. If all the parameters passed to a decorator called in constant form (namely not used through a varaible), this expression of calling the decorator is a constant expression and can be evaluated at compiling time. 1 const Hello3 = @Decorator( Hello ) 2 Hello3( 'def' ) 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 = @MyDecorator( MyFunction ) When a decorator is called in non-constant expression, the decoration will be done at running time, 1 routine Hello2( index = 123 ) 2 { 3 io.writeln( 'Calling Hello2(int)', index ); 4 return 'Hello'; 5 } 6 # Running time decoration: 7 func = @TestDecorator( Hello2, 'Hello' ); 8 io.writeln( '--------' ) 9 func(123) 10 11 func = @TestDecorator( func ); 12 io.writeln( '--------' ) 13 func(123) Just like normal functions, decorators can be assigned to variables and used at running time: 1 deco = @Decorator; 2 func = deco( func ); 3 4 io.writeln( '--------' ) 5 func(789); 0.5 Decorator for Class Method Decorators can be applied to class methods. Such decorators can be defined outside of the class as well as inside the class as a class member. If the routine type of the first parameter of a decorator does not contain a self parameter, this decorator can be applied to static methods, 1 routine @StaticDecorator( meth(args) :routine<id:int=>int>, value = 123 ) 2 { 3 io.writeln( args, value ); 4 args.id = value; 5 return meth( args, ... ); 6 } Otherwise, it can be applied to instance methods, 1 routine @MethodDecorator( meth(args) :routine<self:@T,id:int=>int>, value = 123 ) 2 { 3 io.writeln( args, value ); 4 args.id = value; 5 return meth( args, ... ); 6 } When a decorator is defined as a class method, it must be declared as a static method, because its first parameter must be a routine type, 1 class Klass 2 { 3 static routine @ClassDecorator( meth(args) :routine<id:string=>?>, value = 'abc' ){ 4 io.writeln( args, value ); 5 args.id = value; 6 return meth( args, ... ); 7 } 8 9 # Decorator can be applied to class methods: 10 @StaticDecorator( 456 ) 11 static routine StaticMeth( id :int ){ io.writeln( id ); return id } 12 13 @ClassDecorator 14 static routine StaticMeth( id :string ){ io.writeln( id ) } 15 16 @MethodDecorator( 789 ) 17 routine Meth( id :int ){ io.writeln( id ); return id } 18 } 19 20 Klass::StaticMeth( 0 ); 21 Klass::StaticMeth( 'a' ); 22 23 k = Klass(); 24 k.Meth(1);