[swift-evolution] Proposal - Allow properties in Extensions

Matthew Johnson matthew at anandabits.com
Sat Dec 26 09:10:16 CST 2015

> On Dec 25, 2015, at 12:02 AM, John McCall <rjmccall at apple.com> wrote:
>> On Dec 24, 2015, at 12:10 PM, Matthew Johnson <matthew at anandabits.com> wrote:
>>> On Dec 24, 2015, at 2:05 PM, John McCall <rjmccall at apple.com> wrote:
>>>> On Dec 24, 2015, at 11:48 AM, Matthew Johnson <matthew at anandabits.com> wrote:
>>>>> On Dec 24, 2015, at 1:31 PM, John McCall <rjmccall at apple.com> wrote:
>>>>>> On Dec 23, 2015, at 10:51 AM, Matthew Johnson <matthew at anandabits.com> wrote:
>>>>>>>> On Dec 23, 2015, at 12:50 PM, John McCall via swift-evolution <swift-evolution at swift.org> wrote:
>>>>>>>> On Dec 23, 2015, at 7:05 AM, Paul Cantrell <cantrell at pobox.com> wrote:
>>>>>>>>> On Dec 22, 2015, at 10:45 PM, John McCall via swift-evolution <swift-evolution at swift.org> wrote:
>>>>>>>>> when you stuff a lot of functionality into a single class in most OO languages, there’s no real way to enforce its division into subsystems, because every method has direct access to every property and every other method.  In contrast, in Swift you can divide that class into distinct components with their own interface, state, and invariants, essentially making each component as good as its own type as far as encapsulation goes.
>>>>>>>> Can you elaborate on this, John? Extensions and protocols in Swift today still don’t solve the problem that shared _private_ class state has to be centralized. Or were you speaking as if this “properties in extensions” proposal were already implemented?
>>>>>>> Yes, that’s right.  I’m explaining why I think it makes sense to limit stored instance properties in extensions to class types: especially with some solution for private initialization of extensions, they enable intra-class encapsulation in a way that matters for classes and not really for other types.
>>>>>> How would private initialization of extensions work?
>>>>> Just spit-balling, but something like:
>>>>> class A {
>>>>> init(numbers: [Int]) {
>>>>>   // Definitive initialization requires initialization of all the extensions
>>>>>   // in the module that declare partial inits before the call to super.init.
>>>>>   self.init(counts: numbers)
>>>>>   // Okay, now we super.init.
>>>>>   super.init()
>>>>>   // Fully initialized now.
>>>>> }
>>>>> }
>>>>> extension A {
>>>>> let counts: [Int]
>>>>> partial init(counts: [Int]) {
>>>>>  // Definitive initialization requires a partial init to initialize all the stored properties
>>>>>  // in this extension.  This all happens prior to the complete initialization of self,
>>>>>  // so unlike a normal init, there is no point in this initializer when unrestricted
>>>>>  // use of self is allowed.  If that’s required, it can be done with an ordinary method
>>>>>  // call.
>>>>>  //
>>>>>  // To start, partial initializers would not be allowed to use properties from other
>>>>>  // extensions or the main class.  We can consider ways to lift that restriction later.
>>>>>  self.counts = counts
>>>>> }
>>>>> }
>>>> I'm really not sure I like this very much.  It seems to be exactly what I was concerned about.  The primary advantage this seems to provide is encapsulation for the extension.  Unfortunately it also means the extension has a ripple effect requiring initializers elsewhere in the project to be updated.  This doesn't feel like an "extension" as it actually seems to be pretty invasive.  It gives me an uneasy feeling at first glance.
>>> Any proposal which allows stored properties with non-default initialization to be added in an extension is going to require source changes to the main class initializer(s).  My suggestion of partial inits achieves that without breaking encapsulation, which in my judgment would not be acceptable.  You could also just require all stored properties in extensions to have a default initializer, which is the rule we’d be forced to use outside the module anyway.  These are your options if you actually care about this feature.
>> Understood.  I’m still trying to asses how I feel about the idea in general.  There are certainly advantages, but there are also drawbacks, particularly when the stored properties aren’t required to have defaults.
> Here’s the key argument for me.  What is the point of an extension of a concrete type within the defining module? Extending a *protocol* has a lot of expressive power, sure.  Extending a *different* module’s type lets you avoid awkward workarounds and makes code feel more consistent, sure.  But extending your own type, when you could have just written that code in the main definition?  It would surely reduce language complexity if we disallowed that. And yet, people still want to do it, even though the only reason to do so is code organization.   Something about the code in the extension feels like a logical grouping, which is verging on saying that it acts like a sub-system; and a sub-system is something that should be encapsulated.
> I don’t think requiring properties in extensions to have default initializers is an acceptable end-game, although it might be okay as an incremental step.  It’s just like any other expressivity gap with initialization: it forces the programmer to compromise the invariants on their state, e.g. by making the property optional when it shouldn’t be. Improving encapsulation isn’t really all that helpful if it leads to inferior invariants.

If there are compelling uses for same-module extensions with non-default properties I agree with this wholeheartedly.  I’m just trying to think of with concrete examples of where this would be a better design than the alternatives and not coming up with any yet.

> I feel like the strongest argument against allowing stored properties in extensions is the argument that it's poor design to have very complex types.  I have two responses to that; I apologize if this seems like straw-manning, because I know you haven’t raised this objection.
> The first is that I’m always uncomfortable with abstract arguments against the complexity of other people’s code. Judging technical designs on that level requires an awful lot of knowledge about the problem and the technical and implementation constraints on its solution.  I do not have that information.  (I have also worked with, and designed, many systems that feature complex types that would benefit from better internal encapsulation.  Swift’s parser, type checker, SIL generator, and IR generator are all like that.  Clang is similar, although that’s not a coincidence; still, most compilers seem to end up this way.)
> The second is that this is a valuable tool for incrementally reducing that complexity.  No amount of language design is going to get programmers to always reach the best code design the first time.  So, great, now you've got a complicated type.  It’s a simple first step to organize that complexity into separate extensions.  Once you’ve got those, it’s natural to want to make those extensions better encapsulated.  Once you’ve done that, maybe you recognize that some of those extensions can be extracted into an independent type.  It’s a lot harder to make the leap from extensions to independent types without encapsulating the state first.

Hi John, thanks for responding.  

The example of using this as an intermediate state during a complex refactoring is interesting.  I can certainly see how a feature like this could be useful in that way.  That said, I’m not sure this use case alone is compelling to me.

I have a pretty open mind about this feature at the moment and am still trying to get my mind around the implications it might have.  I see very attractive aspects to it, but also some implications that make me a little bit uneasy as it might be an easy feature to over use.  So I’m not exactly making the argument you imply, but I you are on the right track in terms of what I’m thinking about.

I would really like to see some motivating examples of how same-module extensions with non-defaulted stored properties could enable better designs.  Without those I’m really not sure whether the added complexity is worth it.  I’ll continue to think about it.  If you know of any please share those as well.

I know you didn’t think too deeply about the initialization syntax yet.  One thing I think we would need to resolve is how the self.init calls are disambiguated if there are several extensions in the same module that all need to be initialized.

class A {
  init(numbers: [Int]) {
    // Definitive initialization requires initialization of all the extensions
    // in the module that declare partial inits before the call to super.init.
    // How do we disambiguate when there are multiple extensions 
    // we need to initialize?
    // Hopefully something more than just the initializer signature is used...
    self.init(counts: numbers)
    // Okay, now we super.init.

    // Fully initialized now.

-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20151226/a0c9d6d9/attachment.html>

More information about the swift-evolution mailing list