javascript-decorators icon indicating copy to clipboard operation
javascript-decorators copied to clipboard

Execution order for inner and outer functions

Open chocolateboy opened this issue 9 years ago • 3 comments

As a follow-up to the discussion here, I'd like to suggest a change to the execution order for both "outer" functions (i.e. the foo function implementing @foo) and "inner" functions ((target, name, descriptor) -> descriptor?). There's an issue related to the former here, but that's focused on Babel's implementation of the spec for outer functions. I'd like to propose both being executed in top-down order.

As mentioned in #20, it appears that TypeScript and Python execute the outer functions in top-down order. The execution order for annotations in Java (and Groovy) appears to be unspecified. Perl's attributes are executed in lexical order i.e. "top down".

I'm still not sure what the rationale for executing the inner functions bottom-up is. IMO, it violates the principle of least surprise and is painful (or potentially impossible in the case of inner functions with side-effects) to work around.

chocolateboy avatar Jul 22 '15 18:07 chocolateboy

I'm curious, what is driving your expectation with top-down execution of decorators? Conceptually, you are decorating a descriptor, so the method all the way at the bottom will would be evaluated first to create the descriptor. Are you saying then you'd expect it to jump back to the top of the list of decorators?

To be bottom-up makes sense to me because you are decorating the entirety of the items below it, e.g.

@foo @bar @baz method(){}

each decorator decorates the descriptor that was generated by the method/decorator to the right of it, so you could almost take that and pretend it were a method call, e.g.

@foo ( @bar ( @baz ( method(){} )))

loganfsmyth avatar Jul 22 '15 21:07 loganfsmyth

For me, the expectation is that they'll execute in the order in which they're written i.e. a pipeline:

fn.decorate(@foo).decorate(@bar).decorate(@baz)

The functional composition interpretation might make more sense if they're written like that, but, so far, I've always written them like this:

@foo
@bar
@baz
method { ... }

i.e. like a series of statements rather than a single expression. Perl's attributes are written in the style you suggest (newlines aren't allowed), but, as I say, they're executed in lexical (top down) order.

The function composition analogy suggests we expect to build functions like that, but I don't think that's the case. Classes and decorators are new, but (IMO) JavaScript language features/libraries have tended to prefer chaining (jQuery, lodash, promises, the function bind operator &c.) to the baz(bar(foo(fn))) approach:

_(fn).foo().bar().baz()
$(fn).foo().bar().baz()

Indeed, those fluent features/libraries arguably exist, at least in part, to rescue us from the readability/comprehensibility issues associated with deeply nested, right-to-left function composition.

chocolateboy avatar Jul 22 '15 22:07 chocolateboy

[I]t appears that TypeScript and Python execute the outer functions in top-down order

Actually, that's not quite how decorators in Python work:

$ python3
Python 3.4.3 (default, Jul 13 2015, 12:18:23)
[GCC 4.2.1 Compatible Apple LLVM 6.1.0 (clang-602.0.53)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> def decorator(msg):
...     print("Decorating with", msg)
...     def wrapper(fn):
...         print("Returning newly decorated function for", msg)
...         return lambda: (print("Running", msg) or fn())
...     return wrapper
...
>>> @decorator("Top")
... @decorator("Middle")
... @decorator("Bottom")
... def test_function():
...     print("Now running test_function")
...
Decorating with Top
Decorating with Middle
Decorating with Bottom
Returning newly decorated function for Bottom
Returning newly decorated function for Middle
Returning newly decorated function for Top
>>> test_function()
Running Top
Running Middle
Running Bottom
Now running test_function

As you can see, there are 3 phases:

  1. Creating the decorator function (for a decorator that takes no arguments this step may be skipped)
  2. Applying the provided functions to the base function
  3. Running the resulting function

Only in Phase 2 is the order reversed. The reason for this is to ensure that the run time order of the decorated functions is in source order. If the decorators were applied in Phase 2 in source order then they would run in Phase 3 in reverse order, which would be counter-intuitive too.

svieira avatar Aug 26 '15 03:08 svieira