[swift-evolution] [Proposal] Uniform Initialization Syntax

Xiaodi Wu xiaodi.wu at gmail.com
Fri Jun 9 07:24:48 CDT 2017


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/c1a6393d/attachment.html>


More information about the swift-evolution mailing list