Hi, Riley! I think that's a great idea! We can merge the second part of my proposal (the `indirect init`) into your one and refine and consolidate the prerequisite proposal (about returning from `init` and possibly in-place member initializers) and bunch them up into a proposal cluster (the way swift coders did). Feel free to tear out any chunks from my proposal, while I think about a more in-depth rationale about revamping initialization syntax. 🙂
> On Jun 10, 2017, at 8:36 PM, Riley Testut <[email protected]> wrote: > > Hi Gor 👋 > > I’m very much in fan of a unified initialization syntax. I submitted my own > proposal for factory initializers a while back, but since it wasn’t a focus > of Swift 3 or 4 I haven’t followed up on it recently. In the time since last > working on it, I came to my own conclusion that rather than focusing on > factory initialization, the overall initialization process should be > simplified, which I’m glad to see someone else has realized as well :-) > > Here’s my proposal for reference: > https://github.com/apple/swift-evolution/pull/247/commits/58b5a93b322aae998eb40574dee15fe54323de2e > > <https://github.com/apple/swift-evolution/pull/247/commits/58b5a93b322aae998eb40574dee15fe54323de2e> > Originally I used the “factory” keyword, but I think your “indirect” keyword > may be a better fit (since it has precedent in the language and is not > limited to “just” being about factory initialization). To divide your > proposal up into smaller pieces for review, maybe we could update my proposal > to use your indirect keyword, and then start a separate topic/proposal for > the remaining aspects of your proposal? I agree that splitting it into > smaller chunks may be better for the process. > > Let me know what you think! > > Riley > > >> On Jun 10, 2017, at 3:33 AM, Gor Gyolchanyan via swift-evolution >> <[email protected] <mailto:[email protected]>> wrote: >> >>> >>> This is a very interesting read. >>> >> >> Thanks you! I tried to make it as clear and detailed as possible. 🙂 >> >>> >>> We did not discuss the 'indirect' idea at all on this list. Did you come up >>> with it just now? In any case, my suggestion as to moving forward would be >>> this: >>> >> I was writing the proposal and was just about to write `factory init`, when >> it occurred to me: enums already have a keyword that does something very >> similar. It seemed to me that an initializer that doesn't initialize the >> instance in-place, but returns a completely separate instance from somewhere >> else, is kinda "indirectly" initializing the instance. Plus, the already >> established keyword and its semantic would reduce the learning curve for >> this new feature and separate it from a single specific use case (the >> "factory method" pattern). >> >>> >>> - Do you feel that both halves of your draft (expanding `return` in >>> initializers, and `indirect` initializers) should absolutely be one >>> proposal, or can they be separated? >>> >> I think the `return` can be easily implemented first, while opening up an >> opportunity to later implement `indirect init`. The reason why I unified >> them was that the `return` idea on its own has very limited merit and could >> the thought of as a low-priority cosmetic enhancement. I wouldn't want it to >> be viewed that way because the primary purpose of that idea is to enable >> `indirect init` (which Cocoa and Cocoa Touch developers would be very happy >> about). >> >>> >>> a) If they can be separated because each half has individual merit, then >>> these ideas may be more likely to succeed as separate proposals, as each >>> can be critiqued fully and judged independently as digestible units. >>> >> >> Very good point. The challenge is to correctly separate them, without losing >> context in their respective proposals and without bleeding the proposals >> into each other. >> >> >>> >> >>> b) If you intend to tackle all your ideas all at once, that's going to be a >>> much bigger change--in terms of review effort, likely bikeshedding, and >>> implementation effort. It'll probably be best to solicit initial feedback >>> on this list first about `indirect` initializers, even if just to >>> familiarize the community with the idea, before launching into a pitch of >>> the whole proposal. >>> >> >> I'd never send a pull request to swift-evolution without thoroughly >> discussing it here. I just though, if I'm going to write a whole proposal >> with examples and motivation, it would be easier to demonstrate it and >> discuss in with the community If I just went ahead and wrote the whole thing >> and sent the link. This way it would be clearer to the reader and the >> discussed changes would be accurately reflected by the commits I'd make to >> my proposal. >> >> Original Message >> >>> On Jun 10, 2017, at 2:38 AM, Daryle Walker via swift-evolution >>> <[email protected] <mailto:[email protected]>> wrote: >>> >>> On Fri, Jun 9, 2017 at 5:32 PM, Gor Gyolchanyan <[email protected] >>> <mailto:[email protected]>> wrote: >>> Forked swift-evolution, created a draft proposal: >>> >>> https://github.com/technogen-gg/swift-evolution/blob/master/proposals/NNNN-uniform-initialization.md >>> >>> <https://github.com/technogen-gg/swift-evolution/blob/master/proposals/NNNN-uniform-initialization.md> >>> >>> This is my first proposal, so I might have missed something or composed it >>> wrong, so please feel free to comment, fork and send pull requests. 🙂 >>> >>> >>> This is a very interesting read. We did not discuss the 'indirect' idea at >>> all on this list. Did you come up with it just now? In any case, my >>> suggestion as to moving forward would be this: >>> >>> - Do you feel that both halves of your draft (expanding `return` in >>> initializers, and `indirect` initializers) should absolutely be one >>> proposal, or can they be separated? >>> >>> a) If they can be separated because each half has individual merit, then >>> these ideas may be more likely to succeed as separate proposals, as each >>> can be critiqued fully and judged independently as digestible units. >>> >>> b) If you intend to tackle all your ideas all at once, that's going to be a >>> much bigger change--in terms of review effort, likely bikeshedding, and >>> implementation effort. It'll probably be best to solicit initial feedback >>> on this list first about `indirect` initializers, even if just to >>> familiarize the community with the idea, before launching into a pitch of >>> the whole proposal. >>> >>> >>>> On Jun 9, 2017, at 3:24 PM, Xiaodi Wu <[email protected] >>>> <mailto:[email protected]>> wrote: >>>> >>>> Cool. I have reservations about idea #3, but we can tackle that another >>>> day. (Real life things beckon.) But suffice it to say that I now really, >>>> really like your idea #2. >>>> On Fri, Jun 9, 2017 at 08:06 Gor Gyolchanyan <[email protected] >>>> <mailto:[email protected]>> wrote: >>>> You know, come to think of it, I totally agree, and here's why: >>>> A normal initializer (if #2 is accepted) would *conceptually* have the >>>> signature: >>>> >>>> mutating func `init`(...) -> Self >>>> >>>> Which would mean that both `self` and the returned result are non-optional. >>>> A failable initializer could then have the signature: >>>> >>>> mutating func `init`() -> Self? >>>> >>>> Which would make the returned result optional, but leave `self` >>>> non-optional. >>>> This would make `return nil` less out-of-place, like you said, while still >>>> leaving `self` as a set-exactly-once `inout Self`. >>>> A factory initializer would then have the signature: >>>> >>>> static func `init`(...) -> Self >>>> >>>> or in case of a failable factory initializer: >>>> >>>> static func `init`(...) -> Self? >>>> >>>> Which would still make sense with the now legal `return ...` syntax, while >>>> adding the restriction of not having any `self` at all. >>>> So, annotating the initializer with the keyword `factory` would cause it >>>> to change the signature as well as remove any compiler assumptions about >>>> the dynamic type of the returned instance. >>>> >>>> In addition, idea #3 would be available for more deterministic in-place >>>> initialization. >>>> >>>>> On Jun 9, 2017, at 2:47 PM, Xiaodi Wu <[email protected] >>>>> <mailto:[email protected]>> wrote: >>>>> >>>>> On Fri, Jun 9, 2017 at 07:33 Gor Gyolchanyan <[email protected] >>>>> <mailto:[email protected]>> wrote: >>>>> So far, we've discussed two ways of interpreting `self = nil`, both of >>>>> which have a sensible solution, in my opinion: >>>>> >>>>> 1. It's a special rule like you said, which can be seen as >>>>> counter-intuitive, but recall that `return nil` is just as much of a >>>>> special rule and is also largely counter-intuitive. >>>>> >>>>> `return nil` is “special,” but it doesn’t conflict with any other syntax >>>>> because the initializer notionally has no return value. Personally, I >>>>> have always disliked `return nil` in failable initializers for that >>>>> reason, but I couldn’t come up with a better alternative. >>>>> >>>>> Your proposed idea to allow returning any value is interesting because, >>>>> in the case of a failable initializer, `return nil` continues to have the >>>>> same meaning if we consider the return value of the initializer to be of >>>>> type `Self?`. For that reason, I think your idea #2 is quite clever, and >>>>> it would go a long way in making `return nil` a lot less odd. It also >>>>> increases the expressivity of initializers because it allows one to set >>>>> the value of self and also return in one statement, clearly demonstrating >>>>> the intention that no other code in the initializer should be run before >>>>> returning. >>>>> >>>>> For all of those reasons, I think idea #2 is a winning idea. >>>>> >>>>> The benefit of `self = nil` is that it's much more in line with >>>>> initialization semantics, it provides more uniform syntax and it's a bit >>>>> less restrictive. >>>>> >>>>> 2. It's an `inout Self!`, like Greg said, which can be seen as more >>>>> cumbersome. Implicitly unwrapped optionals are a bit difficult, but this >>>>> "variation" of it is much more restrictive then the normal ones, because >>>>> unlike normal implicitly unwrapped optionals, this one cannot be accessed >>>>> after being assigned nil (and it also cannot be indirectly assigned >>>>> `nil`, because escaping `self` is not allowed before full >>>>> initialization), so there is only one possible place it can be set to nil >>>>> and that's directly in the initializer. This means that `self` can be >>>>> safely treated as `inout Self` before being set to nil (and after being >>>>> set to nil, it doesn't matter any more because you aren't allowed to >>>>> access it, due to not being fully initialized). >>>>> >>>>> I have to say, I don’t like either of these explanations at all. I think >>>>> having a “special” IUO is a difficult sell; it is just conceptually too >>>>> complicated, and I don’t agree that it gains you much. >>>>> >>>>> By your own admission, `self = nil` is wonky, and making the language >>>>> wonkier because it currently has a parallel wonky feature in `return nil` >>>>> seems like the wrong way to go. In addition, there’s nothing gained here >>>>> that cannot be done with a defer statement; of course, defer statements >>>>> might not be very elegant, but it would certainly be less wonky than >>>>> inventing a new variation on an IUO to allow assignment of nil to self... >>>>> For those reasons, I conclude that I’m not excited about your idea #1. >>>>> >>>>> Overall, I'd go with #2 because it involves much less confusing magic and >>>>> the restrictions of `self as inout Self!` are imposed by already existing >>>>> and well-understood initialization logic, so the provided guarantees >>>>> don't really come at the cost of much clarity. >>>>> >>>>>> On Jun 9, 2017, at 2:23 PM, Xiaodi Wu <[email protected] >>>>>> <mailto:[email protected]>> wrote: >>>>>> >>>>>> >>>>>> On Fri, Jun 9, 2017 at 07:12 Gor Gyolchanyan <[email protected] >>>>>> <mailto:[email protected]>> wrote: >>>>>> I think a good approach would be to have `self = nil` only mean `the >>>>>> initializer is going to fail` because if your type is >>>>>> ExpressibleByNilLiteral, it means that the `nil` of your type already >>>>>> carries the same meaning as if your type was not ExpressibleByNilLiteral >>>>>> and was an optional instead, so having a failable initializer doesn't >>>>>> really make sense in that case (since you could've initialized `self` to >>>>>> its own `nil` in case of failure). Still, some valid use cases may >>>>>> exist, so the natural (and quite intuitive) way to circumvent this would >>>>>> be to call `self.init(nilLiteral: ())` directly. >>>>>> >>>>>> So you would create a special rule that `self = nil` means a different >>>>>> thing in an initializer than it does in a function? Essentially, then, >>>>>> you’re creating your own variation on an implicitly unwrapped optional, >>>>>> where `self` is of type `inout Self?` for assignment in initializers >>>>>> only but not for any other purpose. Implicitly unwrapped optionals are >>>>>> hard to reason about, and having a variation on it would be even harder >>>>>> to understand. I don’t think this is a workable design. >>>>>> >>>>>> It might be possible to have `self` be of type `inout Self?`; however, I >>>>>> do think Greg is right that it would create more boilerplate than the >>>>>> current situation. >>>>>> >>>>>>> On Jun 9, 2017, at 2:07 PM, Xiaodi Wu <[email protected] >>>>>>> <mailto:[email protected]>> wrote: >>>>>>> >>>>>>> >>>>>>> On Fri, Jun 9, 2017 at 06:56 Gor Gyolchanyan <[email protected] >>>>>>> <mailto:[email protected]>> wrote: >>>>>>> The type of `self` could remain `inout Self` inside the failable >>>>>>> initializer. The ability to assign nil would be a compiler magic (much >>>>>>> like `return nil` is compiler magic) that is meant to introduce >>>>>>> uniformity to the initialization logic. >>>>>>> >>>>>>> The idea is to define all different ways initialization can take place >>>>>>> and expand them to be used uniformly on both `self` and all its >>>>>>> members, as well as remove the ways that do not make sense for their >>>>>>> purpose. >>>>>>> >>>>>>> Currently, there are 3 ways of initializing self as a whole: >>>>>>> 1. delegating initializer >>>>>>> 2. assigning to self >>>>>>> 3. returning nil >>>>>>> >>>>>>> #1: The delegating initializer is pretty much perfect at this point, in >>>>>>> my opinion, so no changes there. >>>>>>> >>>>>>> #2: The only exception in assigning to self is the `nil` inside >>>>>>> failable initializers. >>>>>>> >>>>>>> #3: The only thing that can be returned from an initializer is `nil`, >>>>>>> which is compiler magic, so we can thing of it as a misnomer (because >>>>>>> we aren't really **returning** anything). >>>>>>> >>>>>>> If, for a second, we forget about potential factory initializers, >>>>>>> returning anything from an initializer doesn't make much sense, because >>>>>>> an initializer is conceptually meant to bring an existing object in >>>>>>> memory to a type-specific valid state. This semantic was very >>>>>>> explicitly in Objective-C with `[[MyType alloc] init]`. Especially >>>>>>> since even syntactically, the initializer does not specify any return >>>>>>> type, the idea of returning from an initializer is counter-intuitive >>>>>>> both syntactically and semantically. >>>>>>> >>>>>>> The actual *behavior* of `return nil` is very sensible, so the >>>>>>> behavior, I imagine `self = nil`, would largely mean the same (except >>>>>>> not needed to return immediately and allowing non-self-accessing code >>>>>>> to be executed before return). Being able to assign `nil` to a >>>>>>> non-optional (ExpressibleByNilLiteral doesn't count) may feel a bit >>>>>>> wonky, >>>>>>> >>>>>>> What happens when Self is ExpressibleByNilLiteral and you want to >>>>>>> initialize self to nil? That is what `self = nil` means if `self` is of >>>>>>> type `inout Self`. If `self` is of type `inout Self` and Self is not >>>>>>> ExpressibleByNilLiteral, then it must be an error to assign nil to >>>>>>> self. Anything else does not make sense, unless `self` is of type >>>>>>> `inout Self?`. >>>>>>> >>>>>>> but not as wonky as returning nil from something that is meant to >>>>>>> initialize an object in-place and doesn't look like it should return >>>>>>> anything. >>>>>>> >>>>>>> # Factory Initializers >>>>>>> >>>>>>> In case of factory initializers, the much discussed `factory init` >>>>>>> syntax could completely flip this logic, but making the initializer >>>>>>> essentially a static function that returns an object. In this case the >>>>>>> initializer could be made to specify the return type (that is the >>>>>>> supertype of all possible factory-created objects) and assigning to >>>>>>> self would be forbidden because there is not self yet: >>>>>>> >>>>>>> extension MyProtocol { >>>>>>> >>>>>>> public factory init(weCool: Bool) -> MyProtocol { >>>>>>> self = MyImpl() // error: cannot assign to `self` in a >>>>>>> factory initializer >>>>>>> self.init(...) // error: cannot make a delegating >>>>>>> initializer call in a factory initializer >>>>>>> if weCool { >>>>>>> return MyCoolImpl() >>>>>>> } else { >>>>>>> return MyUncoolImpl() >>>>>>> } >>>>>>> } >>>>>>> >>>>>>> } >>>>>>> >>>>>>> # In-place Member Initializers >>>>>>> >>>>>>> In addition, member initialization currently is only possible with #2 >>>>>>> (as in `self.member = value`), which could be extended in a non-factory >>>>>>> initializer to be initializable in-place like this: >>>>>>> >>>>>>> self.member.init(...) >>>>>>> >>>>>>> This would compliment the delegating initialization syntax, while >>>>>>> giving a more reliable performance guarantee that this member will not >>>>>>> be copy-initialized. >>>>>>> >>>>>>>> On Jun 9, 2017, at 1:32 PM, Xiaodi Wu <[email protected] >>>>>>>> <mailto:[email protected]>> wrote: >>>>>>>> >>>>>>>> If `self` is not of type `inout Self?`, then what is the type of >>>>>>>> `self` such that you may assign it a value of `nil`? >>>>>>>> >>>>>>>> It certainly cannot be of type `inout Self`, unless `Self` conforms to >>>>>>>> `ExpressibleByNilLiteral`, in which case you are able to assign `self >>>>>>>> = nil` an unlimited number of times–but that has a totally different >>>>>>>> meaning. >>>>>>>> >>>>>>>> Could `self` be of type `inout Self!`? Now that implicitly unwrapped >>>>>>>> optionals are no longer their own type, I’m not sure that’s possible. >>>>>>>> But even if it were, that seems unintuitive and potentially >>>>>>>> error-prone. >>>>>>>> >>>>>>>> So I think Greg is quite right that, to enable this feature, `self` >>>>>>>> would have to be of type `inout Self?`–which is intriguing but >>>>>>>> potentially more boilerplatey than the status quo. >>>>>>>> On Fri, Jun 9, 2017 at 05:24 Gor Gyolchanyan via swift-evolution >>>>>>>> <[email protected] <mailto:[email protected]>> wrote: >>>>>>>> Good point, but not necessarily. >>>>>>>> Since you cannot access `self` before it being fully initialized and >>>>>>>> since `self` can only be initialized once, this would mean that after >>>>>>>> `self = nil`, you won't be allowed to access `self` in your >>>>>>>> initializer at all.You'll be able to do any potential, cleanup though. >>>>>>>> Also, since there can be only one `self = nil`, there's no reason to >>>>>>>> treat `self` as `inout Self?`, because the only place it can be `nil` >>>>>>>> is the place it cannot be accessed any more. >>>>>>>> >>>>>>>> >>>>>>>>> On Jun 9, 2017, at 7:45 AM, Greg Parker <[email protected] >>>>>>>>> <mailto:[email protected]>> wrote: >>>>>>>>> >>>>>>>>> >>>>>>>>>> On Jun 8, 2017, at 5:09 AM, Gor Gyolchanyan via swift-evolution >>>>>>>>>> <[email protected] <mailto:[email protected]>> wrote: >>>>>>>>>> >>>>>>>>>> 1. Arbitrary `self` Assignments In Intializers >>>>>>>>>> >>>>>>>>>> The first ideas is to allow `self = nil` inside failable >>>>>>>>>> initializers (essentially making `self` look like `inout Self?` >>>>>>>>>> instead of `inout Self` with magical `return nil`), so that all >>>>>>>>>> initializers uniformly can be written in `self = ...` form for >>>>>>>>>> clarity and convenience purposes. This should, theoretically, be >>>>>>>>>> nothing but a `defer { return nil }` type of rewrite, so I don't see >>>>>>>>>> any major difficulties implementing this. This is especially useful >>>>>>>>>> for failable-initializing enums where the main switch simply assigns >>>>>>>>>> to self in all cases and the rest of the initializer does some >>>>>>>>>> post-processing. >>>>>>>>> >>>>>>>>> I don't see how to avoid source incompatibility and uglification of >>>>>>>>> failable initializer implementations here. Allowing `self = nil` >>>>>>>>> inside a failable initializer would require `self` to be an optional. >>>>>>>>> That in turn would require every use of `self` in the initializer to >>>>>>>>> be nil-checked or forced. I don't think that loss everywhere >>>>>>>>> outweighs the gain of `self = nil` in some places. >>>>>>>>> >>>>>>>>> >>>>>>>>> -- >>>>>>>>> Greg Parker [email protected] <mailto:[email protected]> >>>>>>>>> Runtime Wrangler >>>>>>>>> >>>>>>>>> >>>>>>>> >>>>>>>> _______________________________________________ >>>>>>>> swift-evolution mailing list >>>>>>>> [email protected] <mailto:[email protected]> >>>>>>>> https://lists.swift.org/mailman/listinfo/swift-evolution >>>>>>>> <https://lists.swift.org/mailman/listinfo/swift-evolution> >>>>> >>>> >>> >>> >> >> _______________________________________________ >> swift-evolution mailing list >> [email protected] <mailto:[email protected]> >> https://lists.swift.org/mailman/listinfo/swift-evolution >
_______________________________________________ swift-evolution mailing list [email protected] https://lists.swift.org/mailman/listinfo/swift-evolution
