|
|
Label: ♦english
♦feature planning
fu
created at Saturday, 2010-11-13, 20:45:09
15 Replies, 4603 Hits
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 )
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,
{ do_something(); }
routine @mydecorator( func( a : string ), index : int )
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).{ 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; } 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
Nightwalker commented at Saturday, 2010-11-13, 22:17:34
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.
fu commented at Saturday, 2010-11-13, 23:14:36
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:)
Nightwalker commented at Sunday, 2010-11-14, 00:07:13
Nightwalker modified at Sunday, 2010-11-14, 00:08:17
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 ?
fu commented at Sunday, 2010-11-14, 01:07:05
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.
Nightwalker commented at Sunday, 2010-11-14, 11:41:35
Nightwalker modified at Sunday, 2010-11-14, 11:56:02
Hmm, I have an idea which might possibly be realized via decorators, but I suppose not with the current ones proposed.
routine check(){...}
But what if a decorator could be used instead of an auxiliary function?
routine r1(...) { check(); ... } routine r2(...) { check(); ... }
routine @checking(...)
Another example:
{ do_checks(); return function_argument(...); } @checking routine r1 {...} @checking routine r2 {...}
routine @drawing(...)
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.
{ prepare_drawing_area(); return function_argument(...); } @drawing routine draw_some_figure(...){} @drawing routine draw_another_figure(...){}
fu commented at Sunday, 2010-11-14, 19:07:16
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.
Nightwalker commented at Sunday, 2010-11-14, 19:26:31
Nightwalker modified at Sunday, 2010-11-14, 19:32:19
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?
fu commented at Sunday, 2010-11-14, 19:36:34
It can do both, intercepting/changing parameters and calling to the decorated function, it depends on how the decorator is implemented.
fu commented at Monday, 2010-11-15, 02:00:59
For now the native routine type form is used for the function argument in the decorator function.
A basic example: function_decorator.dao
Nightwalker commented at Monday, 2010-11-15, 19:30:20
Nightwalker modified at Monday, 2010-11-15, 19:30:57
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.
fu commented at Tuesday, 2010-11-16, 00:48:55
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.
Pompei2 commented at Monday, 2010-12-20, 10:01:16
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
This example shows that it would even be useful to hide the original name by the decorated one.doEvenGreaterThings = @logging routine doEvenGreaterThings 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.
fu commented at Monday, 2010-12-20, 19:25:11
fu modified at Monday, 2010-12-20, 19:30:30
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 )
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.doEvenGreaterThings2 = @logging( doEvenGreaterThings )
Pompei2 commented at Sunday, 2010-12-26, 00:45:15
Cool, this is awesome, super, great :)
fu commented at Thursday, 2012-03-08, 07:39:13
Now it is possible to do the following with function decoration:
const doTheGreatThing = @logging( doTheGreatThing )
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.const doEvenGreaterThings = @logging( doEvenGreaterThings ) |
fu: Dao has finally become feature complete! After the recent implementation of communication channel for tasklets, deferred blocks and exception ... (May.18,05:46) fu: A new feature for concurrent programming: tasklet communication channels! I have been looking for ways to improve Dao's support for concurrent programming. The most recent imp ... (May.18,00:35) fu: Dao now supports Go-style panic/exception handling! I recently looked into the panic/ exception handling in the Go programming language (defer- recover), ... (May.07,02:04) |