Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Execution order for inner and outer functions #29

Open
chocolateboy opened this issue Jul 22, 2015 · 3 comments
Open

Execution order for inner and outer functions #29

chocolateboy opened this issue Jul 22, 2015 · 3 comments

Comments

@chocolateboy
Copy link

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.

@loganfsmyth
Copy link

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(){} )))

@chocolateboy
Copy link
Author

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.

@svieira
Copy link

svieira commented Aug 26, 2015

[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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants