Chapel: Should new initializers support a "done initializing fields" routine / marker?

Created on 24 Jan 2018  ·  44Comments  ·  Source: chapel-lang/chapel

This issue proposes an extension to the core "new initializers" proposal that would
permit a method C.init() to enable treating this as a C prior to returning it
with the goal of adding power to the init() routines and potentially avoid the need
for "post-init" routines for some cases. This should probably be considered something
of a power-user feature.

In the core proposal of "new initializers" (issue #8283), the assumption is that for
the duration of the init() method's body after its super.init() call, the this
object's dynamic type would be that of its parent class, and only upon returning
from init() would it become an instance of the current class. That is:

class A {
}

class B : A {
  var x: int;
  var y: real;

  proc init() {
    // here, `this` is just a blob of memory
    super.init();
    // here, `this` is an `A` and can be used for anything an A can be
    this.x = 2;
    // compiler automatically initializes `y` here before returning
  }
}  // upon returning from init(), `this` is a `B`

One implication of this is that if the author wants to do things with the
object that only a B can do, they can only do so by writing a "post-init"
routine. Another is that, once they're writing that post-init routine, they
no longer have control over whether the object's dynamic type is a B or
a subclass of B.

This issue proposes an extension to the core proposal in which the user can assert
that the field initialization is complete at a certain point within init() in order
to treat it as a B before returning. For example, imagine that the way of doing
so is called this.initDone() (not a name anyone is particularly excited about, so
feel free to substitute your favorite alternative call name or syntax). This would
permit B to be written as:

class B : A {
  var x: int;
  var y: real;

  proc init() {
    // here, `this` is just a blob of memory
    super.init();
    // here, `this` is an `A` and can be used for anything an A can be
    this.x = 2;
    this.initDone(); // causes compiler to insert initialization of `y` just prior to this point
                     // and also makes `this` into an instance of `B`
    // here, `this` is a `B` and can be used for anything that a B can be
  }
}

If adopted, an alternate way of describing the core proposal is that if an init()
routine doesn't contain a this.initDone() call then the compiler automatically
inserts one as the last line in the routine.

This issue is designed to capture thoughts on this proposal including suggestions
for what name / syntax should be used if it is adopted.

Some of the tradeoffs of this proposal discussed in a meeting today include:

Advantages:

  • gives the class designer a somewhat unique opportunity to work with the object
    as a B prior to having a subclass initializer run, specialize it into something else,
    and potentially further mutate B's fields.
  • permits local variables declared within init() to be used for post-initialization-of-B
    actions as well.
  • for many common cases (those that don't want to dispatch to child class
    methods), provides a way to write fairly arbitrary initializer code in a single routine
    rather than needing to split between init() and "post-init" routines.
  • related to the above, supports the ability to call methods within a record or
    base-class initializer
  • may reduce the need for having post-initializers take arguments as in issue #8291 by
    supporting richer computation in initializers.

Disadvantages:

  • the dual of the first advantage above: requires the class designer to think about
    whether a given initialization-time operation on this is intended to occur on
    dynamic type B (by putting it into init() after this.initDone() or is intended
    to dynamically dispatch for dynamic subtypes of B (by putting it into the
    "post-init" routine.
  • one more phase of initialization to understand and implement
Language Design

Most helpful comment

perl uses bless for a concept that's at least superficially related, and bless seems at least superficially language-appropriate.

All 44 comments

In a meeting on this topic today, @mppf pointed out that D automatically determines when
all of an object's fields have been initialized and could be thought of as auto-inserting
the call to this.initDone() just after that point (whereas, above, I was proposing that if
it was omitted, it would be inserted as the last statement in the initializer).

Speaking for myself, I think I prefer for this to be explicitly visible (if it were to occur
anywhere other than the end) because of potential confusion w.r.t. other implicit actions—
particularly compiler-inserted field initialization. But in any case, I think that doing so
would represent a potential second step after agreeing on this first one (which we haven't
done yet).

If #8283 is adopted, I support this proposal. I agree with Brad that it is better to make it explicit rather than implicit.

I'm fine with having some kind of explicit marker here, but I really don't like the initDone() name for the method.

For one thing, it doesn't meet my preferred style of verb-y method names.
For another, it's fundamentally a rewording of the same English concepts as postInit (from #8283).

I'd prefer a name that is more connected to what it does, rather than when it happens, such as:

  • setCID (but this seems unnecessarily implementation-y)
  • setMyClassType()
  • setThisType()
  • transformIntoCurrentType()
  • enableVirtualDispatchOnThis()
  • ...

I'd prefer a name that is more connected to what it does, rather than when it happens, such as:

+1 to the transformIntoCurrentType(), perhaps with different words.

I agree with @mppf that initDone() isn't a great name for various reasons and like the direction his suggestions are going.

I expressed my reservation about this proposal in the meeting because it strikes me as all the complexity and subtlety of the present-day super.init() but with less benefit. #8283 already proposes a clean way to achieve "phase 2" initialization, and though the final syntax is not yet determined, a separate function seems barely heavier than a magic-word function call:

proc init() {
}

proc postInit() {
}
proc init() {
  this.initDone();
}

The only advantage I see is the ability to use variables in the scope of init(), but that seems to be a slight one.

this.initDone() causes complexity in the compiler because it could be hiding in esoteric control flow, so we would need to invest time and code to detect it. Meanwhile users can easily trip over the subtle power of inline this.initDone( ), a complaint we've already received about inline super.init(). Furthermore this syntax would effectively create two ways to write a postInit, confusing new users already unfamiliar with the postInit concept.

In my opinion, we should implement #8283 and only consider this proposal if it is truly painful to write a separate postInit() for small phase 2 actions.

Preston, I sympathize with your points and don't _necessarily_ disagree with them (I'd call myself on the fence though leaning towards supporting this proposal). But to play devil's advocate:

With a well-chosen name, I don't think this concept has all the same problems that using super.init() as a phase 1 / phase 2 separator did. Part of the confusion of super.init() is that what it literally says is "call my superclass's initializer" but what it also meant was "Compiler, please finish initializing any fields for me that you haven't, and when you return the restrictions of phase 1 will be lifted." So the name only said part of what it did. By contrast, if this separator's name imparted the message "make me into a B / this is the point where we're done initializing my B-ness" then it would state what it is actually doing: "Compiler, do everything that's required to complete my transformation from A to B." -- i.e., finish initializing any fields I haven't taken care of and change my dynamic type."

As a simple motivation for the support, imagine I'm writing an initializer, and before returning, I want to be able to print it out to see if my initializer is behaving as I'd like. With this proposal, I could say:

proc init(...) {
  // ...
  this.makeMeIntoA(B);  // not the real name of course...
  if debug then writeln(this);
}

Whereas without this support, I have to either move my debugging code into a new post-initialization routine. This feels a bit heavyweight for some debugging code that I literally want to write "right now" before any subclass initializers potentially mess with / overwrite the fields I've set. It also has the downside that the object will print out as a Z rather than a B. Alternatively, I can print out the new fields manually one by one and not use a simple writeln(), but that feels pretty onerous. As a power user, I know that I'm ready for this thing to be a B, act like a B, and do anything a B can do. I also know that the compiler is just about to do that for me, and I'd really like a way to hasten its occurrence for convenience and productivity.

Being able to use variables within the scope of the init() is a good point (and one that's been mentioned before, but that I forgot about). I'll add it to the top-level lis this issue.

I don't disagree that there is compiler effort involved in implementing this, but I don't think it has to be as bad as you're saying. For example, we could require the call to be at the top-level scope within the init() for clarity (and we could do the same for super.init()). Or, if we've already implemented the logic for super.init() we can re-use that code for correct detection of this separator (i.e., the rules about where the markers can go should presumably be the same for these two cases).

I don't really buy the argument that users may trip over the subtle power of it. In the current scheme, there are many initializers that you simply can't write without understanding super.init() as a result of our "phase 2 by default" decision --- specifically, any for generic classes or classes with const fields. This has required the author of a not-so-complex object type (including a mere base class or record) to understand what super.init(), phase 1, and phase 2 are. In contrast, the feature in this issue is never required to be used, and simply there as a convenience for power users. Readers of that power user's code might have to understand it of course, but again, I think if we get the name right, it won't be that hard to understand or to look up. In contrast, super.init() was needed in many cases where the user could care less about initializing the superclass, which is part of what made educating people about it challenging/embarrassing.

I also don't agree that it creates two ways to write a post-init. The post-init call will only run after all inits up and down the hierarchy have completed. This code would run at the end of my class's initializer, but before any of my subclasses's initializers have run and potentially mutated the state.
So the execution occurs at a completely different time, and on a completely different dynamic type. That means it adds new power not redundancy.

Thanks for the counterpoint Brad. Based on that, it seems this proposal would create a distinct (yet entirely optional) initialization phase set off by a clear (but still somewhat magical) method call. Whether that is worth the effort to support is still undecided for me.

If I had to choose between this proposal and #8291 for richer init computation, this one is a clear winner due to clarity, I'm just not convinced that users really need either at this point. #8283 is very powerful as is and this proposal could be implemented at a later time, so I still think we should take a wait and see approach instead of adopting it for the next release.

so I still think we should take a wait and see approach instead of adopting it for the next release.

The intention is definitely to agree and proceed with #8283 first before considering any of these side issues. I'd be hard-pressed to say how either will relate to release schedules yet, as I think that depends on how far we can get how fast (e.g., if converting domain maps to initializers were easier with this feature than without it, I wouldn't sit on it for a release out of principle).

perl uses bless for a concept that's at least superficially related, and bless seems at least superficially language-appropriate.

proc init(...) {
   // ...
   this.makeMeIntoA(B);  // not the real name of course...
   if debug then writeln(this);
}

Whereas without this support, I have to either move my debugging code into a new post-initialization routine.
...
Alternatively, I can print out the new fields manually one by one and not use a simple writeln(), but that feels pretty onerous.

FWIW, I think there's at least one case where you can't even print the fields manually in init() without an initDone(). That is, if the last field of the class is not explicitly initialized in init(), it'll still be uninitialized when you get to the writeln() (right?):

class C {
  var x = 1, y = 2, z = 3;
  proc init() {
    x = 11; y = 22;
    writeln(x, y, z);
  }
}

If a record has 3 fields x, y, z, and init() initializes only x and y explicitly, when is z initialized (implicitly)?

I propose that for records we set things up so that the users do not HAVE to use postInit(). Given that records are simpler than classes in that they do not have super/sub, it makes sense to make the structure of the initializer simpler as well.

Towards that end we can define that for a record's init() method, the "initDone" point occurs implicitly immediately after the last explicit field initialization. For example, in my "x, y, not z" setup, immediately after the y field is initialized, the compiler inserts implicit initialization of z and allows methods on 'this' to be invoked.

That would address my case for records, allowing either the writeln(this) or writeln(x,y,z) forms, right?

I surmise the reason you're limiting your proposal to records is that it wouldn't completely replace postIinit(). In cases where we're in C.init() on behalf of D.init(), and won't be a D in C.init() -- even after the implicit initDone() -- as we would in C.postInit(). Is that the reason, or is there something else?

But I think it would address Brad's use case for classes as well. I don't think the super/sub of classes would interfere with this, as far as it goes. All the field initializations are after the super.init(), so the implicit initDone (completing the field initializations and promoting the object to a C in C.init()) must also be after the super.init().

In fact, with this implicit initDone(), I'm hard pressed to think of a case where you'd need an explicit one. Maybe if in C.init() you wanted to make a method call as if this were still a B? (Is that a reasonable thing to do?) Or if after initializing a field to a value (or letting it be default initialized), you want to set it to a new value as an assignment. Then you'd need something so the compiler knows you're not trying to initialize the field twice.

In the work to transition initializers into the new proposal, we've found that most examples which required more than just fields initialization seemed to benefit more from the initDone construct than from the presence of a postInit() function, making it more of a necessity to include both in the proposal going forward.

One example would be:

record Foo {
  var d : domain(1);
  var a : [d] int;

  proc init(_d : domain(1), val : int) {
    d = _d;

    initDone();      // Cause ‘a’ to be initalized

    // Now assign to ‘a’
    for i in d do a[i] = val;
  }
}

Another, less contrived example can be found in the initializer at line 36 of test/studies/amr/lib/cfboundary/CoarseningTransfer.chpl, a helper for the AMR tests.

It is still an open question what the final appearance of initDone will be. I'll try to include some examples here for discussion, but I'm timing out quickly for an appointment.

While I have my reservations, this seems like a good use case for initDone() so I'll let it proceed. I do rather like the bless idea for syntax that @cassella presented, though it isn't terribly clear.

A few (probably bizarre) alternatives to 'initDone':

this.promotePhase();
this.promoteType();

this.* = <default init keyword>
this.otherwise = <default init keyword>

this as Z; // where 'Z' is the type we want to become

Compiler.initFields(); // seems a bit too technical and not language-y enough

// rather verbose + some new param behavior not yet defined
for field in this.fields() do
  if field.initialized == false then field = <default init keyword>

OK, so a lot of that is definitely bizarre. I'm mostly posting these weird options in case they give other people ideas. Don't feel the need to talk me out of any of these because I won't defend them.

Other options:
promoteClass() and promoteRecord()

  • Clearer than initDone about what is happening to the type. Different syntax depending on the type being a class or record seems unsatisfactory, though.

allowDerivedMethods

  • Seems too specialized for the class-with-inheritance case.

finalizeObject or completeObject or markObjectReady

  • Might be confusing with the "object" type, from which only classes inherit.

allowCurrentTypeMethods

  • Too wordy

finalizeCurrentType

  • Still pretty wordy, but maybe better

From Michael:
"Marking where fields are all initialized"
finishFieldInit
finishFieldInitialization
finishFields
finalizeFields
initRemainingFields
fieldsInited
fieldsDone
countFields
tallyFields
acceptFields
recognizeFields

"The compiler now considers the type to be the current type"
finalizeType
establishType
showType
evinceType
completeType
setType
considerType
recognizeType
acceptType
allowType

"The initialization is now complete"
completeInit
finalizeInit

Other 1-word options

reify
complete
typify
actualize
bless
embody
certify
ratify
accept

I'm leaning towards a camelCase two-word option, from the first 2 of my categories.
I'd prefer camelCase over a 1-word option so that it's fairly unique and Google-able.
Also, I don't want users to accidentally write unrelated methods with the same name.

My thinking here is that we pick one of the two concepts "Marking where fields are all initialized"
or "The compiler now considers the type to be the current type" (but not both) and
then choose a relatively short name that communicates the concept. Then, in documentation,
we describe that this marker also has the 2nd effect.

From Greg:
A 2-word name seems attractive to me also, which makes it ironic that most of the others I’ve thought of have 3:

objDone
objHasType
isTheObj
isAnObj

(or “object” in place of “obj” in any of these)

From Brad:
For the two-word approaches, I tend to prefer "the compiler now considers the type to be the current type" category the best because it seems more proactive and like we're asking something to be done (finish remaining fields for us and make us that type) rather than just informing the compiler of something (hey we're done).

Of those options, I like these, probably ranked in this order:

  • completeType
  • finalizeType
  • setType

(setType is attractive in its succinctness, but maybe it's too vague? Maybe not).

While I understand the software engineering advantages of the two word approaches, I'm glad we use 'this' 'super', 'init', and 'int' in defining the language rather than 'thisObject' 'myParentClass', 'initializeMyself' and 'integerType'... So that leads me to not want to throw away one-word options with the thought of making it feel more "built-in" (it is) and less "just a library call". Of those, my favorites are the following, probaby in this order:

  • complete
  • bless
  • certify
  • typify

@benharsh's this as Z proposal made me wonder: what about using the cast operator here? E.g.

this: Foo;

My issue with finalize is that it seems to imply that the instance itself is completed, which is not the case. I like using "current" in anything including something of that nature because it clarifies that we are only done with a particular step not the whole thing. For this reason, "establishType" is also appealing to me, and maybe "completeType", because they can be specific in that regard.

the term finalize is not necessarily a great idea in this context because it's used in some GC'd languages to write the equivalent of a destructor.

@benharsh's this as Z proposal made me wonder: what about using the cast operator here? E.g.

this: Foo;

This is intriguing, though I think it's more compelling when written as:

this = this: Foo;

since a cast expression normally evaluates to a new result expression rather than modifying the input expression. Maybe put another way, I don't think we would want this: Foo to behave so differently within an initializer than it does elsewhere in terms of whether or not it modifies this.

But after the initial intrigue, I also think it's a little vague / idiomatic in that we're doing more than merely assigning this a cast of itself (such as filling in missing initializer fields). It also doesn't give a good handle on something to search for if you're new to Chapel, reading code, and wondering what this seemingly tautological statement is doing there. Whereas choosing a new name (rather than relying on overloading existing features) gives an easy search term to learn about the impacts of the transition in the initializer.

Yeah, those are some strong downsides to it. I also toyed a little yesterday with the idea of using wrapping the code that should happen after "initDone" in a defer block, but I think that has similar downsides.

Stewing on this a bit more last night, I keep coming back to this.complete(); I think I like that it both indicates "this is complete" and "complete this for me" (if I've left fields uninitialized).

this.completeType(); is a close second and I'm on the fence between my sense that "things that are inherent to the language should be short and sweet" vs. Michael's point about it preventing people from writing a class/record that wants to _both_ utilize the initDone() phase of computation _and_ have their own 0-argument method named complete(). (Alternatively, we could adopt special resolution rules for 0-arg calls to complete() within the body of init() routines to support even those users, but that starts to get a little special-casey).

signed,
the guy who regrets advocating for calling that variable dataParTasksPerLocale to this day...

I've been wondering lately about it being a method (vs a function).

Say it's named complete. If it's written this.complete(), it seems to me there might be more of an expectation that one could override/reimplement that method. But if it is simply complete(), it might be clearer that the action it takes is defined by the implementation. I suppose a method call could be written either way; so in a way what I'm proposing is that we don't allow this.complete() and only complete(). Or maybe even complete(this).

(And that's not to say complete is the final name, either).

I've been wondering lately about it being a method (vs a function).

I'm finding Michael's argument reasonably compelling. I think I was originally suggesting a method like this.complete() due to inertia, having used object.method() as the separator in the old initializers proposal. And in the class case, we could imagine this.complete() being a (final?) built-in method on the object type. But for records, where there is no inheritance, that might be weird-ish.

In contrast, the main downsides I can think of around complete([this]) is that (1) it seems to pollute the global namespace and (2) isn't a function that could be implemented as a library (which was also a downside of using a method-based syntax, and might suggest using a keyword-based syntax more along the lines of what BHarsh was brainstorming around).

What if it were:

  • this.type.complete()
  • complete(this.type)

Or what about

  • this.type = ClassName
  • this.type = complete();
  • this.type = type

this.type = ClassName

My worry with something like this is that it seems to indicate you could set the type to something other than the current one and we definitely don't want to allow that (realizing that I was one of the ones that originally tossed around using the type name, sorry :p).

I do agree that having it be a method or function would imply that you could write a function that would override it. Maybe this indicates we want something like a new keyword? E.g.

  complete type;

More brainstorming, in the category of "Can we abuse existing keywords" it could be:

  • select this.type;
  • with this.type;
  • this.type = return type;
  • where this.type;

    • label type;

All that said, I'm personally pretty happy with this.type.complete().

My main reactions to most of Michael's suggestions from his last few comments are "that feels like a lot of typing" (3 words where 1-2 seem like they should be sufficient) and "some of these provide easy ways to get things wrong" (like using the wrong class name).

My own thinking has been going more in the same direction as Lydia's complete type; or complete this; or possibly even simply complete; I think the main downside is that I could imagine complete being a good name for a boolean variable that a user might want to define, so reserving it as a keyword seems a little unfortunate.

I'm not sure I'm understanding what makes this.type.complete(); more compelling than this.complete(); (which I still feel reasonably supportive of in spite of the holes that Michael and I poke into it above).

I'd be pretty happy with this.complete() but I think of the concept we're trying to convey with the syntax as "The compiler now considers the type to be the current type" as with completeType in my earlier suggestions. So, for that reason, I prefer that the syntax include both the term "complete" and the term "type".

As far as 3 words vs 1-2, I'd expect that type.complete() or this.type.complete() would both work and mean the same thing if we went that direction (but I hadn't actually said that yet). I think that type.complete() is reasonably short. If you care about brevity here, maybe type.complete; is reasonable without introducing a new keyword.

As far as keyword vs. method, something about the argument for making it a keyword doesn't make sense to me. Earlier, @lydia-duncan pointed out:

I do agree that having it be a method or function would imply that you could write a function that would override it. Maybe this indicates we want something like a new keyword?

I don't think we've really explored this in the conversation yet, but I'd expect our reaction to a user expecting that one could write a complete method would be to simply generate a compiler error or warning if such a method (or a type method, in the case of type.complete() - or a no-parents type method, in the case of type.complete) was ever created. Thus, we'd be preventing users from creating a method named complete()/ a type method named complete() / a type method named complete (depending on the variant of the proposal).

But if we change it to a keyword, we're not only disallowing some sort of complete() method but also disallowing the use of complete as a local variable or argument name. Which seems worse to me.

I think we should reserve keywords for things that will either be used fairly frequently or for things that don't have a reasonable non-keyword-based alternative, and I don't think this complete type proposal fits into either category. I may be underestimating how frequently it is required, though.

(But I wouldn't be too upset if we ended up with complete type or something like it; it just seems a waste of a keyword to me).

What do others think about type.complete; being the marker, as it (perhaps?) addresses the concerns with the other proposals?

I don't think we've really explored this in the conversation yet, but I'd expect our reaction to a user expecting that one could write a complete method would be to simply generate a compiler error or warning if such a method (or a type method, in the case of type.complete() - or a no-parents type method, in the case of type.complete) was ever created. Thus, we'd be preventing users from creating a method named complete()/ a type method named complete() / a type method named complete (depending on the variant of the proposal).

But if we change it to a keyword, we're not only disallowing some sort of complete() method but also disallowing the use of complete as a local variable or argument name. Which seems worse to me.

I find this a compelling argument and think type.complete; is the best proposal so far.

The pause that [this.]type.complete gives for me is that it suggests that something about the type is changing. That is, given:

class D : C { ... }
var myD = new D(...);
proc init(...) {
  ...
  [this.]type.complete;
}

the implication seems to be "we're doing something to D" when in fact we want to be doing something to myD (auto-initializing any remaining fields and changing its dynamic type from C to D). To me this seems a bit like expecting that code like this:

var x: int;
x.type.foo();

is somehow going to affect x when it really would only be able to affect int — the connection back to x is lost once we query its type.

Am I missing something?

I see your point, but I think it wouldn't be clear that the type of x is what is being impacted (and x isn't what is being completed, it is really that the portion of x that corresponds to the type D is being completed). So maybe something like this.complete(type); would be more appropriate?

I think we're agreeing that it isn't the type (C and/or D) of x or this that's being impacted so much as the value of x / this: fields are getting set, the internal field representing its dynamic type is being updated. Its type is changing/evolving from C to D but the types themselves aren't being modified.

I think literally using the keyword type as the actual argument would be weird given that it's not a legal expression in Chapel. I could imagine saying this.complete(D); as a means of saying "make this into a D" except that it feels unnecessarily burdensome: it's more typing and there's only one legal type that could appear there for a given initializer, so it seems like setting up a stumbling block to trip over or an expression to maintain as class names evolve (this was also one of the reasons that we started naming initializers init rather than D).

I understand your point about this not being _totally_ complete, but only complete up to the current class in the hierarchy, but think I still prefer this.complete(); over type.complete() or this.complete(type) out of the proposals recently being discussed. My argument would be that it's complete up to the current class, just as proc C.init() doesn't necessarily _fully_ initialize an object, it just initializes it up to the point of being a C and subclasses may do further initialization.

just as proc C.init() doesn't necessarily fully initialize an object

The difference to me is that "complete" as a word is more final sounding. It implies there will be no more changes, no going back. That is the purpose behind the word. And while it is fair to say that a part has been completed (i.e. the current type), without clarifying what has been completed, the reasonable assumption is "everything". Leaving room for that interpretation will allow it to occur, and will confuse users.

I don't recall us outright rejecting this.completeType();, would that be acceptable? It addresses my concerns fully.

I expressed the concern that camel-case solutions, to me, look like something in the library or user space rather than something that has meaning in the language proper. Most things in the language seem to use single-word, all lower-case (init, deinit, these, this, int), but I suspect there are exceptions I'm not thinking of. It's also simply more typing. I prefer this.complete() over this.completeType().

For some levity, Google translate says the German translation of "finish type" is "Fertigstellungsart", which is one word :)

I think the clarity we gain in making this distinction outweighs the burden of typing four additional characters and I don't see any other way to avoid this source of confusion. postInit is also camel-case. Can you explain to me why avoiding this confusion is of lower priority to you than the reasons you have given? I think prioritization is where the disconnect here is happening and I'd like to understand.

Just to throw out a few words that don't suggest the finality of complete,

this.transition();
this.metamorphosis();
this.transfigure();
this.transmogrify();
this.transmute();
this.transubstantiate();
this.evolve();
this.type(); // as a "method"
this.retype();
this.bless();

+1 for type.complete;

Catching up on loose threads:

I think the clarity we gain in making this distinction outweighs the burden of typing four additional characters and I don't see any other way to avoid this source of confusion. Can you explain to me why avoiding this confusion is of lower priority to you than the reasons you have given? I think prioritization is where the disconnect here is happening and I'd like to understand.

To me, it's hard to see why this.completeType(); would be any more/less clear or imply more/less finality than this.complete(); or this.init() do. It's only four more characters, but even the 8-character complete is on the high end of my preference of identifier/keyword length for something defined by the language. It also requires typing another shift.

These are just my opinions, though, not dictates (and really, I think this is something that more than the ~4 of us will need to weigh in on, ultimately).

postInit is also camel-case.

I'd be really happy for it not be as well. E.g.., call it proc postinit() { ... } or some other name.

Offline, after much discussion and debate, we ended up settling on [this.]complete(); as the indicator for this, which is now implemented in PR #8865. For posterity, the runner-up option was [this.]bless(); but it received less support and stronger negative reactions. I consider this issue to be done and am closing it.

Was this page helpful?
0 / 5 - 0 ratings