[swift-evolution] [Proposal] Uniform Initialization Syntax

Xiaodi Wu xiaodi.wu at gmail.com
Fri Jun 9 18:48:37 CDT 2017


On Fri, Jun 9, 2017 at 5:32 PM, Gor Gyolchanyan <gor at gyolchanyan.com> wrote:

> Forked swift-evolution, created a draft proposal:
>
> 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 <xiaodi.wu at gmail.com> 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 <gor at gyolchanyan.com> 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 <xiaodi.wu at gmail.com> wrote:
>>
>> On Fri, Jun 9, 2017 at 07:33 Gor Gyolchanyan <gor at gyolchanyan.com> 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 <xiaodi.wu at gmail.com> wrote:
>>>
>>>
>>> On Fri, Jun 9, 2017 at 07:12 Gor Gyolchanyan <gor at gyolchanyan.com>
>>> 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 <xiaodi.wu at gmail.com> wrote:
>>>>
>>>>
>>>> On Fri, Jun 9, 2017 at 06:56 Gor Gyolchanyan <gor at gyolchanyan.com>
>>>> 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 <xiaodi.wu at gmail.com> 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 <
>>>>> swift-evolution at swift.org> 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 <gparker at apple.com> wrote:
>>>>>>
>>>>>>
>>>>>> On Jun 8, 2017, at 5:09 AM, Gor Gyolchanyan via swift-evolution <
>>>>>> swift-evolution at swift.org> 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     gparker at apple.com     Runtime Wrangler
>>>>>>
>>>>>>
>>>>>>
>>>>>> _______________________________________________
>>>>>> swift-evolution mailing list
>>>>>> swift-evolution at swift.org
>>>>>> https://lists.swift.org/mailman/listinfo/swift-evolution
>>>>>
>>>>>
>>>>
>>>
>>
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20170609/6d3f1682/attachment.html>


More information about the swift-evolution mailing list