( @(user) ) Login/Signup Logout
The Dao Programming Language
for Scripting and Computing

Home Documentation Download Blog Forum Projects Demo
Label: ♦english ♦feature planning

[467] Possible support of decorator in Dao

Comment
Recent discussion on Adding a test-framework to dao has brought my attention to python decorator. Previously I don't like much decorator, because it can arbitrarily change the logic of a program. But after thinking more about decorator, I start to be believe it offers more benefit than harm. Though decorator can be achieved in some way with Dao macro, having a native support could make decorator more intuitive and readable.

Python decorator is just a syntax sugar, which will replace the original function or class with whatever is returned by the decorator function. Such decorator is probably most easy to support, but I am not sure if this is the kind of decorator I want in Dao. I believe some semantic constraints should be added to decorators, to ensure what kind of function a decorator can be applied to, with certain minimum requirement on the interface of the resulting decorated function. I have got some ideas for function decorator, but still not sure about class decorator.

Basically, my idea is to support a new kind of function: decorator function , like:
routine @decorator_function( original_func( func_params ), decorator_params )
{
   do_something();
}
this will will define a decorator that can be apply to functions whose parameters should be a superset of func_params , which will effective limit what kind of function this decorator can be applied to! Additionally the decorator can take parameters when it is applied to a function. As an example,
routine @mydecorator( func( a : string ), index : int )
{
    io.writeln( 'mydecorator', func, a, index );
    a += 'abc';
    # it will call func() with the original parameter names,
    # namely, func( a, b ), where "a" is appended with 'abc':
    return func(...);
}

@mydecorator( 123 )
routine myfunc( a : string, b = 0 ) 
{
    io.writeln( 'myfunc', a, b );
    return 1;
}
So here @mydecorator can be applied to any function that has a parameter named a with type string , and it will need take an integer as decorator parameter when it is applied. Such decoration will take place during compiling time, it will ensure typing safety of the decoration (function parameter types and returned type).

This syntax is quite reasonable for typed function decorator. Unlike python decorator, which is untyped and can be applied in the same way to classes, the proposed syntax here can not be naturally extended to classes (at least not in a equally simple way). Anyway, this might not be a big issue, I believe class decorator is much less useful than function decorator, it won't be a big problem if it is not supported.

Though untyped python decorator has simpler semantics and is easier to support, I prefer a typed decorator as it will make a better use of dao typing system:). However this is undecided yet, feedbacks are very welcome:)
Comments
Sounds promising. Just one thing: wouldn't it be better to use the native syntax for function argument in decorator? I mean original_func: routine<func_params> -- it would be clear and consistent. And also I think that not only parameters of decorated function should be checked, but its result type as well -- if it is specified in the decorator's function argument. Because theoretically such decorator may return anything, not only function result -- so the ability to restrict this aspect seems reasonable. Again, it would be nicer using the native syntax for routine type.
With the proposed syntax, @ mydecorator can be applied to any function with a parameter named a of string type. Using the native syntax for the function argument, which may imply that it can only be applied to functions with types conforming to the type of the decorator's function argument. Though we can make an exception here, it would look confusing. But I will consider to take this suggestion as a candidate alternative, since it may simplify the implementation:)
Well, if decorated function may have more parameters than specified in decorator argument, the syntax might be just the following: func: routine<a: string, ...> . But why the function parameter must necessarily be named a ?
It is to let the decorator to intercept some of the parameters passed to the original function and do what whatever necessary on the intercepted parameters. This explicitness is to allow the decorator to be compilable on its own.

If the native function type form is to be used, I would prefer func: routine<a: string> , because in any case we have to make an exception on interpreting the function type, to allow it to decorate a function with parameter list which is a superset of what in specified in the decorator's function argument without consideration of the ordering of the parameters.
Hmm, I have an idea which might possibly be realized via decorators, but I suppose not with the current ones proposed.
Suppose I have several routines which should perform the same operations before starting the execution of their own logic. For instance, several methods perform the same check regarding some class fields. Traditional way to simplify this would be to make an auxiliary function to carry this out:
routine check(){...}

routine r1(...)
{
   check();
   ...
}

routine r2(...)
{
   check();
   ...
}
But what if a decorator could be used instead of an auxiliary function?
routine @checking(...)
{
   do_checks();
   return function_argument(...);
}

@checking routine r1 {...}
@checking routine r2 {...}
Another example:
routine @drawing(...)
{
   prepare_drawing_area();
   return function_argument(...);
}

@drawing routine draw_some_figure(...){}
@drawing routine draw_another_figure(...){}
Assuming that draw_some_figure and draw_another_figure may have completely different parameter lists, it seems impossible with the current state of the decorator concept (if I get it right). To put it simple, I think decorators could be used to construct custom modifiers for functions which might be useful for classes and routine libraries.
So, is it possible to support something like this with decorators?
Sure it is possible, you just need to specify NO parameters for the function argument of the decorator, then it can be applied to any function with any parameter list. This is because any parameter list is a superset of an empty parameter list.
Then I suppose decorator won't do the actual call to the function, but just change intercepted function arguments before they have passed to that function, right?
It can do both, intercepting/changing parameters and calling to the decorated function, it depends on how the decorator is implemented.
For now the native routine type form is used for the function argument in the decorator function.

A basic example: function_decorator.dao
I like this feature -- very powerful, yet simple and nice. It is "the way of Dao", I suppose :) All in all, a good idea and a major enhancement.
It's interesting to me: if decorator has an additional argument with a default value, then decorator's short form should be @decorator() or just @decorator ? And another thing: should decorator be allowed to be called explicitly as @decorator(func(a, b)) ?
Glad you like it and feel it simple, I hope other people, especially people used to python decorator, will find it simple too:)

Currently @decorator() is treated the same as @decorator , it should be less confusing. I agree that decorator should be allowed to be called explicitly, but @func(params) in expression was previously used for creating an instance of coroutine and generator. We need to find a new syntax for them.
Good addition.

I have one suggestion to bring it to the next level and make it even more useful. If it could be applied to an existing routine, without modifying it at all, in order to create a new routine. This would be very powerful. Let me explain.
It often happens that there is some 3rd-party module that you happen to use, and it has that great function you would like to connect with some other 3rd-party module. But unfortunately, the signature doesn't fit, or it needs one more parameter or whatever. To solve this problem, there are design patterns like "wrapper" or "adapter". But we know by now that a design pattern is just a workaround to missing language support.
Something around this, with decorators, might solve it in Dao: (The example is not the best, but I had no better idea right now)
# In some 3rd-party module you can't/won't edit!
routine doTheGreatThing(a : string, b : int) => double
{
    # Let's, do it baby!
}

routine doEvenGreaterThings(c : int) => double
{
    # Let's, do it again baby!
}

# In some other 3rd-party module you can't/won't edit either!
routine doStuffRemotely(stuff : routine<...>, host : string) => ServerResponseObject
{
   # Connect to host, exec routine there or whatever.
}

# Finally, in your code, the usual wrapper-way would be:
routine doTheGreatThingOnMars(a : string, b : int) => double
{
    sro = doStuffRemotely(doTheGreatThing, "mars.org")
    if(sro.connectionFailed) {
        if(requestReconnect()) {
            return doTheGreatThingsOnMars(a, b)
        }
    }
    return sro.value
}

routine doEvenGreaterThingsOnMars(c : int) => double
{
    sro = doStuffRemotely(doEvenGreaterThings, "mars.org")
    if(sro.connectionFailed) {
        if(requestReconnect()) {
            return doEvenGreaterThingsOnMars(a, b)
        }
    }
    return sro.value
}

doTheGreatThingOnMars("Hi", 5)
doEvenGreaterThingsOnMars(10);

# Now my idea comes to play:
routine @doOnMars(what : routine<...>)
{
    sro = doStuffRemotely(what, "mas.org")
    if(sro.connectionFailed) {
        if(requestReconnect()) {
            sro2 = doStuffRemotely(what, "mars.org")
            if(sro2.connectionFailed) { throw exception...}
            return sro2
            # Great thing would be a "self" or something like that here, 
            # meaning "the routine that resulted from me decorating another routine.
            # Then it would have the same semantics as above and simplify to:
            # return self(what)
        }
    }
    return sro.value
}

# Now, we could use this decorator to create a new routine:
routine doTheGreatThingOnMars = @doOnmars routine doTheGreatThing
routine doEvenGreaterThingsOnMars = @doOnMars routine doEvenGreaterThings

doTheGreatThingOnMars("Hi", 5)
doEvenGreaterThingsOnMars(10)

Now, I hope this example made it clear: I can apply a decorator to an already existing routine, creating a new one. The example was really not good and now I think a better example would go with the "logging" decorator, then you might want to log existing 3rd-party routines too, but can't change the code. Being able to add the decorator like in my example above would solve it. I try it again:
doTheGreatThing = @logging routine doTheGreatThing
doEvenGreaterThings = @logging routine doEvenGreaterThings
This example shows that it would even be useful to hide the original name by the decorated one.
The syntax could also be different, like:
doTheGreatThing = decorate(doTheGreatThing, @logging)
doEvenGreaterThings = decorate(doEvenGreaterThings, @logging)

Anyway, I know my examples aren't the best, but I hope you understand my idea and see why it can sometimes be very useful. What do you think? Am I overkilling it, or is it a good idea?

PS: I had the idea because I often felt the need for such a feature in other languages.
It's a quite reasonable idea, not a overkill. Actually it is already supported :). The syntax is exactly the same as a normal function call:
doTheGreatThing2 = @logging( doTheGreatThing )
doEvenGreaterThings2 = @logging( doEvenGreaterThings )
The only problem for now is that it can not bind to the original symbol name, because that's a constant. Maybe we can allow to redefine a constant as long as it is not defined in the same module.
Cool, this is awesome, super, great :)
Now it is possible to do the following with function decoration:
const doTheGreatThing = @logging( doTheGreatThing )
const doEvenGreaterThings = @logging( doEvenGreaterThings )
Supposing doTheGreatThing and doEvenGreaterThings are from 3rd-party modules, then they can be overrode in the current module and in the downstream modules that load this one. So the above codes effectively redefine these two functions in the current namespace.

Change picture:

Choose file:
Visitor Map This site is powered by Dao
Copyright (C) 2009-2013, daovm.net.
Webmaster: admin at daovm dot net