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

Already on GitHub? Sign in to your account

Change `method` to be runtime. #38

Closed
schwern opened this Issue Oct 1, 2011 · 20 comments

Comments

Projects
None yet
4 participants
Contributor

schwern commented Oct 1, 2011

That func should happen at compile time is pretty clear. It jives with sub and it's not uncommon to write a script where you put all the functions at the bottom.

That method should happen at compile time is less clear. It's rare that one defines a class in the same file as they use it. There are other runtime components which must be resolved for the class to be fully functional, such as has if you're using Mo[ou]se.

So... should method be changed to a runtime thing?

NOTE This used to be about getting rid of Devel::BeginLift and all compile time measures, which is why the discussion might seem a bit odd.

Contributor

schwern commented Oct 1, 2011

A few things are causing me to give up on BeginLift.

First, it's failure modes are CRAZY SAUCE! Deep and scary action at a distance bugs in the compiler. Second, the people I'd normally go to to fix it have no interest in doing so. Third, I don't know anyone who can and wants to try and fix it. Fourth, if they do it's going to be increasingly difficult to make work with older Perls. Crazy bugs, no support.

zefram's new thing, Memoize::Lift, is 5.14 only so that's no good. And I don't want to introduce different behaviors for different versions of Perl.

If we were to drop it, I don't know how to smooth the incompatibility. My hope is that we won't have to, that the compile time nature won't be in wide use. My logic? Unlike procedural functions, there's not many reasons to call a method before the class is compiled. And func is fairly new.

It would be interesting if we could warn when a function is being called before the runtime has reached the definition. That would provide users with a transitional warning.

Alternatively, I wonder if we can ditch BeginLift and make Devel::Declare insert a BEGIN block around the function definition.

iarna commented Oct 17, 2011

FWIW, the lack of compile-time func in perl5i's signatures is why I switched to Method::Signatures originally.

Contributor

schwern commented Oct 19, 2011

@iarna Fortunately it seems #39 offers a way to have our cake and lie about it, too.

Wait.

To understand the use case better, what do you use compile time functions for?

Contributor

barefootcoder commented Oct 19, 2011

I don't think I would ever use compile-time functions, personally. The vast majority of my functions (if not all) would be declared in modules, which means they would be declared inside a BEGIN block. Thus, making them "compile-time" only gets them available a few microseconds before they would be available anyway, ANAICT. Plus, as I noted elsewhere, those few extra microseconds are actually a bad thing when it comes to method modifiers. So I don't think it matters to me one way or the other whether we can support compile-time functions or not.

That having been said, I do agree that more options is (almost) always better, so I'm glad we've found a way to keep the eaten cake. :)

Contributor

schwern commented Oct 20, 2011

@barefootcoder Sorry, I missed the arguments why compile-time methods conflict with method modifiers. Could you repeat them?

Contributor

barefootcoder commented Oct 20, 2011

Okay, imagine this code:

class Foo extends Bar
{
    around foo () {}
}

Now, Method::Signatures::Modifiers is going to turn that around into a call to add_around_modifier. With BeginLift, that call happens practically instantaneously, before the end of the class is reached. (And it should be the same with the alternate method of injecting a BEGIN block.) But the extends isn't actually processed until the class is done being defined. So, at the time add_around_modifier is called, the superclasses aren't set up yet, and you're trying to add a modifier for a method that isn't in the methodlist (yet), because Moose (or probably more accurately Class::MOP or somesuch) doesnt know that foo is going to be inherited from Bar. And, consequently, you get an exception from Moose about trying to modify a method that doesn't exist.

The same would apply if the method came from a role.

And of course, with the exception of accessors automatically generated from attributes, it's almost always the case that you're modifying methods that come from somewhere else. And, for certain modifiers (override, augment), it's always the case.

Of course, as I say, in these cases your class declarations are usually in a module that you've used, so essentially your methods are getting processed at compile-time anyway.

Contributor

schwern commented Oct 21, 2011

Oh, I see. I thought you were saying methods being defined at compile time was interfering with method modifiers.

I'd say that's a consequence of class and extends not happening at compile time. Put more generally, all the declarations for the class should happen in the same phase. Since it's easier to make them happen at runtime, and there's not much benefit for method to happen at compile time, as opposed to func, maybe method should be changed to runtime only.

Contributor

barefootcoder commented Oct 21, 2011

Yeah, if extends and with were happening instantly, that would fix it. But I'm not familiar enough with how the MOP works to know if that's practical or not.

method working at runtime certainly wouldn't bother me. But, being that I'm not a primary user of compile-time functions, we should probably get the opinion(s) of chipdude and/or larna.

iarna commented Oct 21, 2011

I've never had any need for method to work at compile time.

However, with func it's been utility functions of various kinds. Either helper/sugar functions declared that are both exported and called from the same class, or in small one-off scripts that have a handful of subroutines.

All of these cases are simply due to my preference of keeping the function declaration below the code calling it in the file for aesthetic reasons.

Contributor

schwern commented Nov 13, 2011

Since we have a probable work around for Devel::BeginLift, I've co-opted this discussion to be about turning method into a runtime thing.

Contributor

barefootcoder commented Nov 21, 2011

... I've co-opted this discussion to be about turning method into a runtime thing.

Meaning, can we just remove all traces of BeginLift when it's method instead of func? Well, philosophically, I'm totally fine with that, but then we've already established that I'm not the target audience for the compile-time featureness anyway. :-) I have difficulty picturing a situation where BeginLift on method actually makes a significant difference in the calling code--I'm sure it must be possible, technically, but probably so rare that it's not worth worrying about.

Implementationally, I think it would just be changing the if in code_for to look like this:

if( defined $name && $self->{name} eq 'func' && $self->_do_compile_at_BEGIN ) {

Does that look right? Not sure I care much for accessing the attribute directly like that, but I don't see a public accessor anywhere. This would break if we decide to give folks the option to change func to some other keyword, but I suppose we could cross that bridge when we come to it. (Alternatively, we could go with $self->{name} ne 'method', but of course that's the same problem, only vice versa.)

Contributor

schwern commented Nov 21, 2011

Yeah, the implementation is pretty straightforward. We can put a flag on $self to clearly indicate if it's a method or function.

I have only two realistic situations where this change might break things.

The first is something like this:

Class->setup();
method setup {
    ...
}

That sort of thing will break. I'm not too worried, it should be very rare.

The other one involves circular dependencies where A uses B uses C uses A. Each module's runtime may not be fully executed before they try to use each other. This is part of the reason why you see BEGIN { our @ISA = qw(...) } so inheritance is setup as early as possible. Having methods done at compile time might mitigate this problem, but it should be very rare and we're only masking the problem.

Contributor

barefootcoder commented Dec 20, 2011

This is part of the reason why you see BEGIN { our @isa = qw(...) } so inheritance is setup as early as possible.

Hmmm ... okay, that's a good point. I don't know if this type of thing is even possible in Mooseworld: I'm pretty sure just wrapping your extends call in a BEGIN block wouldn't cut it. So I guess Moose folks are just used to this restriction already and so manage to avoid triggering it. But the non-Moose folks might be in a different bucket.

So now I see how it could make a concrete difference, but I have to agree with your ultimate assessment:

Having methods done at compile time might mitigate this problem, but it should be very rare and we're only masking the problem.

So ... do you want me to go ahead and make this change? perhaps in a branch? I don't know how much time I've got available any time soon, but I'm happy to add it to my burgeoning TODO list. :)

If so, what thoughts/criticisms do you have for the simplistic implementation two comments up?

Contributor

barefootcoder commented Dec 2, 2012

Should we still do this now that #39 has been fixed? or is this moot now?

Contributor

schwern commented Feb 17, 2013

I'm now of the opinion that methods and functions should happen at compile time. Unless there's objection I will close this ticket. Here's my rationale...

There's two rationales for doing methods at runtime. First is that Devel::BeginLift is made of hair and insanity and is disavowed by its own author. We're not using it anymore (see #39) so that's gone.

The second is that is better to have methods compile at runtime. There's several arguments for/against this, but I've always held that since sub works at compile time it would be surprising to have func and method happen at compile time. But I have a better one now, interaction with Roles.

The Role syntax, with "Some::Role", happens at runtime. A proper role needs to know what attributes and methods its consuming class has in order to check for required methods and conflicts. If methods happen at runtime then the with statement has to happen at the end of the class. This is bizarre. If we knew when the end of the class was, as in MooseX::Declare, we could mitigate this, but we don't have that level of control.

Contributor

thoughtstream commented Feb 17, 2013

Schwern is now of the opiinion...

I'm now of the opinion that methods and functions should happen at compile time.
Unless there's objection I will close this ticket.

FWIW, I strongly concur. Like subs, funcs and methods should
definitely be compile-time.

Damian

Contributor

barefootcoder commented Feb 17, 2013

The Role syntax, with "Some::Role", happens at runtime. A proper role needs to know what attributes and methods its consuming class has in order to check for required methods and conflicts. If methods happen at runtime then the with statement has to happen at the end of the class. This is bizarre.

I'm not sure what you're saying here. In the following code:

package Some::Class;
use Moose;
with Some::Role;

method foo () {}

if method happens at compile-time, it happens before with, which happens at run-time. Which doesn't really hurt anything. OTOH, this:

package Some::Role;
use Moose::Role;
method foo () {}

package Some::Class;
use Moose;
with Some::Role;

around foo () {}

has a serious problem: if the around were to happen at compile-time, it would happen before Some::Class even realized it was getting a foo from Some::Role. Thus, it would be adding a method modifier for a method which does not (yet) exist, which Moose considers a fatal error.

Consequently, Method::Signatures::Modifiers never allows compile-time methods, and, since it handles method as well as the various modifiers, that handles that. Consequently, it doesn't matter a whit to MSM what happens with this issue.

Of course, although the modifiers all have this problem, method itself can never actually run into this. This makes sense, since, in the absence of Method::Signatures, sub would be at compile-time, and the modifiers (before, after, etc) would be at run-time. So, theoretically, it shouldn't be possible for method being at compile-time to cause any problems. But I also think it's going to be fairly unusual for it being at run-time to cause any problems. That's because methods are in classes (or roles) and classes (and roles) generally come from use statements, which themselves happen at compile-time, so in this case "at run-time" is actually "at compile-time, just slightly later than the other stuff that's happening at compile-time." So it should be pretty damn rare that a class won't know what its roles' methods are. Roles, OTOH, wouldn't know the methods of their consuming classes. But none of that should keep this issue from being closed, I wouldn't think.

Contributor

schwern commented Feb 18, 2013

Method and attribute modifiers are special. Particularly with respect
to roles. This issue wasn't taking them into account, just method and
function declarations.

The points you bring up are valid, and I think you're right that
modifiers are best done at runtime else the thing they're modifying may
not exist. I don't see that method/func needs to be runtime though.

We (@Ovid and me and... can't remember who else) gave them some
discussion at the 2012 QA Hackathon. @Ovid has Opinions about this, and
if he wants to chime in we should listen because he's done a lot of
study of the subject.

IMO the deep problem is these are a series of functions pretending to be
declarations about a class. Each has to do its thing immediately and so
it becomes very order dependent.

Ideally, the language knows when the class declarations are complete.
Only then can it apply all the various declarations and the order is not
so important. In general we cannot guarantee this. However, with
something like MooseX::Declare, which has an end of class, and
Moose->meta->make_immutable, you can.

When I was spec'ing out Mite, a Perl OO compiler (ie. it generated Perl
code from Moose-like code for performance and 0 dependencies), I
realized this. Many problems go away if one knows when a class is
finished making declarations.

Contributor

barefootcoder commented Mar 21, 2013

So we can close this issue now ... right? :-D

Contributor

schwern commented Mar 21, 2013

Right.

@schwern schwern closed this Mar 21, 2013

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment