[Pitch] `let` in protocols


#1

Hello Swift Evolution,

I’m bumping into an annoying problem with protocols. In a class or struct it is common to have a `let` instance variable and assign it in `init`. Unfortunately there is no way to translate this into a protocol with init in an extension. If attempting to set the variable in init in an extension, it must be of type { get set }, which means it cannot be a `let` constant in the conforming type. AFAIK there is no way around this — if you want to set an instance variable in an initializer in a protocol extension, it must be marked as { get set }. The alternative is to write the initializer separately for each adopting type, but this violates DRY.

Hence, I am proposing a third option to go along with `get` and `set` in a protocol. This would indicate that the variable can be a constant, but is settable in an initializer. In this case, the conforming type *must* use `let` to declare the variable.

Option 1: the keyword `let`. If present, it would need to be the only thing in the curly brackets because it simultaneously implies `get` and not `set`.

protocol P {
    var x: Int { let }
    init(_ x: Int)
    func modifyX()
}
extension P {
    init(_ x: Int) {
        self.x = x // This is ok; would not be ok if x were marked { get }
    }

    func modifyX() {
        self.x += 1 // Not allowed
    }
}

struct S: P {
    let x: Int // This is ok; would not be ok if x were marked { get set }
}

Option 2: `set(init)`. Can (and often will) coexist with `get`.

protocol P {
    var x: Int { get set(init) }
    init(_ x: Int)
    func modifyX()
}
extension P {
    init(_ x: Int) {
        self.x = x // This is ok; would not be ok if x were marked { get }
    }

    func modifyX() {
        self.x += 1 // Not allowed
    }
}

struct S: P {
    let x: Int // This is ok; would not be ok if x were marked { get set }
}

I’d like to hear all of your thoughts on this.

Best,
Robert


(David Moore) #2

I think there might be some merit to this pitch, since you brought up a particular weak spot. I’d have to see what other people say about this, but it would seem that having the ability to explicitly mark something as constant for a given protocol implementation.

However, I would propose a change to the syntax you gave as an example, since it isn’t quite clear when it comes to the implementation. I think just a simple `let foo: Bar` would suffice, unless I’m missing some particular component of your concept.

This would allow extensions to operate on known constant values, perhaps improving performance maybe.

···

On Jun 23, 2017, 5:49 PM -0400, Robert Bennett via swift-evolution <swift-evolution@swift.org>, wrote:

Hello Swift Evolution,

I’m bumping into an annoying problem with protocols. In a class or struct it is common to have a `let` instance variable and assign it in `init`. Unfortunately there is no way to translate this into a protocol with init in an extension. If attempting to set the variable in init in an extension, it must be of type { get set }, which means it cannot be a `let` constant in the conforming type. AFAIK there is no way around this — if you want to set an instance variable in an initializer in a protocol extension, it must be marked as { get set }. The alternative is to write the initializer separately for each adopting type, but this violates DRY.

Hence, I am proposing a third option to go along with `get` and `set` in a protocol. This would indicate that the variable can be a constant, but is settable in an initializer. In this case, the conforming type *must* use `let` to declare the variable.

Option 1: the keyword `let`. If present, it would need to be the only thing in the curly brackets because it simultaneously implies `get` and not `set`.

protocol P {
var x: Int { let }
init(_ x: Int)
func modifyX()
}
extension P {
init(_ x: Int) {
self.x = x // This is ok; would not be ok if x were marked { get }
}

func modifyX() {
self.x += 1 // Not allowed
}
}

struct S: P {
let x: Int // This is ok; would not be ok if x were marked { get set }
}

Option 2: `set(init)`. Can (and often will) coexist with `get`.

protocol P {
var x: Int { get set(init) }
init(_ x: Int)
func modifyX()
}
extension P {
init(_ x: Int) {
self.x = x // This is ok; would not be ok if x were marked { get }
}

func modifyX() {
self.x += 1 // Not allowed
}
}

struct S: P {
let x: Int // This is ok; would not be ok if x were marked { get set }
}

I’d like to hear all of your thoughts on this.

Best,
Robert
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Xiaodi Wu) #3

Hello Swift Evolution,

I’m bumping into an annoying problem with protocols. In a class or struct
it is common to have a `let` instance variable and assign it in `init`.
Unfortunately there is no way to translate this into a protocol with init
in an extension. If attempting to set the variable in init in an extension,
it must be of type { get set }, which means it cannot be a `let` constant
in the conforming type. AFAIK there is no way around this — if you want to
set an instance variable in an initializer in a protocol extension, it must
be marked as { get set }. The alternative is to write the initializer
separately for each adopting type, but this violates DRY.

Hence, I am proposing a third option to go along with `get` and `set` in a
protocol. This would indicate that the variable can be a constant, but is
settable in an initializer. In this case, the conforming type *must* use
`let` to declare the variable.

Option 1: the keyword `let`. If present, it would need to be the only
thing in the curly brackets because it simultaneously implies `get` and not
`set`.

protocol P {
    var x: Int { let }
    init(_ x: Int)
    func modifyX()
}
extension P {
    init(_ x: Int) {
        self.x = x // This is ok; would not be ok if x were marked { get }
    }

The example given here requires an initializer on every conforming type for
reasons other than the requirement `x`, and your idea could not make it not
require such initializers. This is because, even though `P` only has one
required property `x`, types that conform to `P` can have any number of
properties. Therefore, you must chain the protocol extension initializer to
another one in order to guarantee initialization of all stored properties.
You can demonstrate this to yourself if you try to write this example in
Swift with `var x: Int { get set }`.

Moreover, even if you chained your initializer to another one *and* if it
were possible to express a requirement for a gettable property that is
settable exactly once, the example given here still would not work. This is
because the conforming type can have another initializer that is chained to
the required initializer--keep in mind that if the default implementation
of the required initializer is vended by a library, it may or may not set
the required property, and it may do so in one version but not another of
the library, so neither the author nor the compiler can reason about it.
Or, since the required initializer's default implementation must be chained
to another initializer, that initializer may or may not attempt to set the
property, and again there is no way to reason about whether or not this
will happen because the requirement, concrete type, and default
implementation can each live in a distinct module. Therefore, `self.x = x`
could not be permitted, even if a syntax were created to express a
requirement that `x` must be gettable and must be settable exactly once.

···

On Fri, Jun 23, 2017 at 16:43 Robert Bennett via swift-evolution < swift-evolution@swift.org> wrote:

    func modifyX() {
        self.x += 1 // Not allowed
    }
}

struct S: P {
    let x: Int // This is ok; would not be ok if x were marked { get set }
}

Option 2: `set(init)`. Can (and often will) coexist with `get`.

protocol P {
    var x: Int { get set(init) }
    init(_ x: Int)
    func modifyX()
}
extension P {
    init(_ x: Int) {
        self.x = x // This is ok; would not be ok if x were marked { get }
    }

    func modifyX() {
        self.x += 1 // Not allowed
    }
}

struct S: P {
    let x: Int // This is ok; would not be ok if x were marked { get set }
}

I’d like to hear all of your thoughts on this.

Best,
Robert
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Karl) #4

What you want is some way to guarantee value semantics when writing generic code.

It’s a known hole, and admittedly quite a big one. I hope that there will be time for core language improvements like this in Swift 5. Be sure to raise the issue again once planning for that starts!

- Karl

···

On 23. Jun 2017, at 23:43, Robert Bennett via swift-evolution <swift-evolution@swift.org> wrote:

Hello Swift Evolution,

I’m bumping into an annoying problem with protocols. In a class or struct it is common to have a `let` instance variable and assign it in `init`. Unfortunately there is no way to translate this into a protocol with init in an extension. If attempting to set the variable in init in an extension, it must be of type { get set }, which means it cannot be a `let` constant in the conforming type. AFAIK there is no way around this — if you want to set an instance variable in an initializer in a protocol extension, it must be marked as { get set }. The alternative is to write the initializer separately for each adopting type, but this violates DRY.

Hence, I am proposing a third option to go along with `get` and `set` in a protocol. This would indicate that the variable can be a constant, but is settable in an initializer. In this case, the conforming type *must* use `let` to declare the variable.

Option 1: the keyword `let`. If present, it would need to be the only thing in the curly brackets because it simultaneously implies `get` and not `set`.

protocol P {
   var x: Int { let }
   init(_ x: Int)
   func modifyX()
}
extension P {
   init(_ x: Int) {
       self.x = x // This is ok; would not be ok if x were marked { get }
   }

   func modifyX() {
       self.x += 1 // Not allowed
   }
}

struct S: P {
   let x: Int // This is ok; would not be ok if x were marked { get set }
}

Option 2: `set(init)`. Can (and often will) coexist with `get`.

protocol P {
   var x: Int { get set(init) }
   init(_ x: Int)
   func modifyX()
}
extension P {
   init(_ x: Int) {
       self.x = x // This is ok; would not be ok if x were marked { get }
   }

   func modifyX() {
       self.x += 1 // Not allowed
   }
}

struct S: P {
   let x: Int // This is ok; would not be ok if x were marked { get set }
}

I’d like to hear all of your thoughts on this.

Best,
Robert
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Xiaodi Wu) #5

I think there might be some merit to this pitch, since you brought up a
particular weak spot. I’d have to see what other people say about this, but
it would seem that having the ability to explicitly mark something as
constant for a given protocol implementation.

However, I would propose a change to the syntax you gave as an example,
since it isn’t quite clear when it comes to the implementation. I think
just a simple `let foo: Bar` would suffice, unless I’m missing some
particular component of your concept.

This would allow extensions to operate on known constant values,

The way to express such a requirement is `var x { get }`. By contrast, what
Robert is asking for is a property that can be set exactly once
specifically during initialization.

For his proposed idea to work, `let x = 42` must in fact *not* satisfy the
requirement that Robert spells `var x { let }`, since the initializer could
not then set the value one more time. However, as I demonstrated in my
previous email, his idea also cannot work because initializers can be
chained (and, in protocol extension implementations, *must* be chained),
and the implementation of the chainer may be opaque to the chainee or vice
versa. Therefore, neither the compiler nor the author can guarantee that
the property is set once and not more or less.

perhaps improving performance maybe.

···

On Fri, Jun 23, 2017 at 17:19 David Moore via swift-evolution < swift-evolution@swift.org> wrote:

On Jun 23, 2017, 5:49 PM -0400, Robert Bennett via swift-evolution < > swift-evolution@swift.org>, wrote:

Hello Swift Evolution,

I’m bumping into an annoying problem with protocols. In a class or struct
it is common to have a `let` instance variable and assign it in `init`.
Unfortunately there is no way to translate this into a protocol with init
in an extension. If attempting to set the variable in init in an extension,
it must be of type { get set }, which means it cannot be a `let` constant
in the conforming type. AFAIK there is no way around this — if you want to
set an instance variable in an initializer in a protocol extension, it must
be marked as { get set }. The alternative is to write the initializer
separately for each adopting type, but this violates DRY.

Hence, I am proposing a third option to go along with `get` and `set` in a
protocol. This would indicate that the variable can be a constant, but is
settable in an initializer. In this case, the conforming type *must* use
`let` to declare the variable.

Option 1: the keyword `let`. If present, it would need to be the only
thing in the curly brackets because it simultaneously implies `get` and not
`set`.

protocol P {
var x: Int { let }
init(_ x: Int)
func modifyX()
}
extension P {
init(_ x: Int) {
self.x = x // This is ok; would not be ok if x were marked { get }
}

func modifyX() {
self.x += 1 // Not allowed
}
}

struct S: P {
let x: Int // This is ok; would not be ok if x were marked { get set }
}

Option 2: `set(init)`. Can (and often will) coexist with `get`.

protocol P {
var x: Int { get set(init) }
init(_ x: Int)
func modifyX()
}
extension P {
init(_ x: Int) {
self.x = x // This is ok; would not be ok if x were marked { get }
}

func modifyX() {
self.x += 1 // Not allowed
}
}

struct S: P {
let x: Int // This is ok; would not be ok if x were marked { get set }
}

I’d like to hear all of your thoughts on this.

Best,
Robert
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


#6

You’re right Xiaodi, I did not entirely think this through. However I think this could still work with another addition to the language: partial initializers. These would be treated as initializers are now (meaning, for instance, no reference to self until initialization is complete) except they would only need to initialize a subset of the instance variables before exiting. An initializer could then satisfy the requirement that all instance variables must be initialized by calling partial initializers.

By incorporating partial initializers, it would be possible to guarantee that a `let` variable in a protocol is only set once. This is because an init implemented in a protocol extension could delegate to a partial initializers required by the protocol, and because they are only partial initializers, they need not set the instance variables already set in the initializer; it would be up to a conforming type to ensure this is the caee. A confirming type would define the partial initializer to set everything not already set in the protocol extension’s init, and it would be a compiler error to set a variable in the partial initializer that is already set in the extension’s init without overriding the extension’s init. Example code:

protocol P {
    var x: Int { let } // or { get set(init) }, or whatever
    
    partialinit initializeRest()
    init()
}

extension P {
    init() {
        initializeRest()
        self.x = 1
    }
}

struct S: P {
    let x: Int
    var y: String

    // It would be a compiler error to set x here without also redefining init()
    partialinit initializeRest() {
        self.y = “P has no knowledge of me”
    }
    
    // Can use default init provided by P
}

···

On Jun 23, 2017, at 8:12 PM, Karl Wagner <razielim@gmail.com> wrote:

What you want is some way to guarantee value semantics when writing generic code.

It’s a known hole, and admittedly quite a big one. I hope that there will be time for core language improvements like this in Swift 5. Be sure to raise the issue again once planning for that starts!

- Karl

On 23. Jun 2017, at 23:43, Robert Bennett via swift-evolution <swift-evolution@swift.org> wrote:

Hello Swift Evolution,

I’m bumping into an annoying problem with protocols. In a class or struct it is common to have a `let` instance variable and assign it in `init`. Unfortunately there is no way to translate this into a protocol with init in an extension. If attempting to set the variable in init in an extension, it must be of type { get set }, which means it cannot be a `let` constant in the conforming type. AFAIK there is no way around this — if you want to set an instance variable in an initializer in a protocol extension, it must be marked as { get set }. The alternative is to write the initializer separately for each adopting type, but this violates DRY.

Hence, I am proposing a third option to go along with `get` and `set` in a protocol. This would indicate that the variable can be a constant, but is settable in an initializer. In this case, the conforming type *must* use `let` to declare the variable.

Option 1: the keyword `let`. If present, it would need to be the only thing in the curly brackets because it simultaneously implies `get` and not `set`.

protocol P {
  var x: Int { let }
  init(_ x: Int)
  func modifyX()
}
extension P {
  init(_ x: Int) {
      self.x = x // This is ok; would not be ok if x were marked { get }
  }

  func modifyX() {
      self.x += 1 // Not allowed
  }
}

struct S: P {
  let x: Int // This is ok; would not be ok if x were marked { get set }
}

Option 2: `set(init)`. Can (and often will) coexist with `get`.

protocol P {
  var x: Int { get set(init) }
  init(_ x: Int)
  func modifyX()
}
extension P {
  init(_ x: Int) {
      self.x = x // This is ok; would not be ok if x were marked { get }
  }

  func modifyX() {
      self.x += 1 // Not allowed
  }
}

struct S: P {
  let x: Int // This is ok; would not be ok if x were marked { get set }
}

I’d like to hear all of your thoughts on this.

Best,
Robert
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Xiaodi Wu) #7

You’re right Xiaodi, I did not entirely think this through. However I
think this could still work with another addition to the language: partial
initializers. These would be treated as initializers are now (meaning, for
instance, no reference to self until initialization is complete) except
they would only need to initialize a subset of the instance variables
before exiting. An initializer could then satisfy the requirement that all
instance variables must be initialized by calling partial initializers.

By incorporating partial initializers, it would be possible to guarantee
that a `let` variable in a protocol is only set once. This is because an
init implemented in a protocol extension could delegate to a partial
initializers required by the protocol, and because they are only partial
initializers, they need not set the instance variables already set in the
initializer; it would be up to a conforming type to ensure this is the
caee. A confirming type would define the partial initializer to set
everything not already set in the protocol extension’s init, and it would
be a compiler error to set a variable in the partial initializer that is
already set in the extension’s init without overriding the extension’s init.

How can the compiler raise this error when the implementation of the
partial initializer does not even have to exist at compile time?

Example code:

protocol P {
    var x: Int { let } // or { get set(init) }, or whatever

    partialinit initializeRest()
    init()
}

extension P {
    init() {
        initializeRest()

How can the compiler ensure that `initializeRest()` does not already set
`x`?

···

On Sun, Jun 25, 2017 at 2:24 PM, Robert Bennett via swift-evolution < swift-evolution@swift.org> wrote:

        self.x = 1
    }
}

struct S: P {
    let x: Int
    var y: String

    // It would be a compiler error to set x here without also redefining
init()
    partialinit initializeRest() {
        self.y = “P has no knowledge of me”
    }

    // Can use default init provided by P
}

> On Jun 23, 2017, at 8:12 PM, Karl Wagner <razielim@gmail.com> wrote:
>
> What you want is some way to guarantee value semantics when writing
generic code.
>
> It’s a known hole, and admittedly quite a big one. I hope that there
will be time for core language improvements like this in Swift 5. Be sure
to raise the issue again once planning for that starts!
>
> - Karl
>
>> On 23. Jun 2017, at 23:43, Robert Bennett via swift-evolution < > swift-evolution@swift.org> wrote:
>>
>> Hello Swift Evolution,
>>
>> I’m bumping into an annoying problem with protocols. In a class or
struct it is common to have a `let` instance variable and assign it in
`init`. Unfortunately there is no way to translate this into a protocol
with init in an extension. If attempting to set the variable in init in an
extension, it must be of type { get set }, which means it cannot be a `let`
constant in the conforming type. AFAIK there is no way around this — if you
want to set an instance variable in an initializer in a protocol extension,
it must be marked as { get set }. The alternative is to write the
initializer separately for each adopting type, but this violates DRY.
>>
>> Hence, I am proposing a third option to go along with `get` and `set`
in a protocol. This would indicate that the variable can be a constant, but
is settable in an initializer. In this case, the conforming type *must* use
`let` to declare the variable.
>>
>> Option 1: the keyword `let`. If present, it would need to be the only
thing in the curly brackets because it simultaneously implies `get` and not
`set`.
>>
>> protocol P {
>> var x: Int { let }
>> init(_ x: Int)
>> func modifyX()
>> }
>> extension P {
>> init(_ x: Int) {
>> self.x = x // This is ok; would not be ok if x were marked { get }
>> }
>>
>> func modifyX() {
>> self.x += 1 // Not allowed
>> }
>> }
>>
>> struct S: P {
>> let x: Int // This is ok; would not be ok if x were marked { get set }
>> }
>>
>> Option 2: `set(init)`. Can (and often will) coexist with `get`.
>>
>> protocol P {
>> var x: Int { get set(init) }
>> init(_ x: Int)
>> func modifyX()
>> }
>> extension P {
>> init(_ x: Int) {
>> self.x = x // This is ok; would not be ok if x were marked { get }
>> }
>>
>> func modifyX() {
>> self.x += 1 // Not allowed
>> }
>> }
>>
>> struct S: P {
>> let x: Int // This is ok; would not be ok if x were marked { get set }
>> }
>>
>>
>> I’d like to hear all of your thoughts on this.
>>
>> Best,
>> Robert
>> _______________________________________________
>> swift-evolution mailing list
>> swift-evolution@swift.org
>> https://lists.swift.org/mailman/listinfo/swift-evolution
>
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


#8

The compiler would only raise an error when a conforming type violated the requirement that the partial initializer not set x. The protocol itself would never fail to compile.

···

On Jun 25, 2017, at 3:33 PM, Xiaodi Wu <xiaodi.wu@gmail.com> wrote:

On Sun, Jun 25, 2017 at 2:24 PM, Robert Bennett via swift-evolution <swift-evolution@swift.org> wrote:
You’re right Xiaodi, I did not entirely think this through. However I think this could still work with another addition to the language: partial initializers. These would be treated as initializers are now (meaning, for instance, no reference to self until initialization is complete) except they would only need to initialize a subset of the instance variables before exiting. An initializer could then satisfy the requirement that all instance variables must be initialized by calling partial initializers.

By incorporating partial initializers, it would be possible to guarantee that a `let` variable in a protocol is only set once. This is because an init implemented in a protocol extension could delegate to a partial initializers required by the protocol, and because they are only partial initializers, they need not set the instance variables already set in the initializer; it would be up to a conforming type to ensure this is the caee. A confirming type would define the partial initializer to set everything not already set in the protocol extension’s init, and it would be a compiler error to set a variable in the partial initializer that is already set in the extension’s init without overriding the extension’s init.

How can the compiler raise this error when the implementation of the partial initializer does not even have to exist at compile time?

Example code:

protocol P {
    var x: Int { let } // or { get set(init) }, or whatever

    partialinit initializeRest()
    init()
}

extension P {
    init() {
        initializeRest()

How can the compiler ensure that `initializeRest()` does not already set `x`?

        self.x = 1
    }
}

struct S: P {
    let x: Int
    var y: String

    // It would be a compiler error to set x here without also redefining init()
    partialinit initializeRest() {
        self.y = “P has no knowledge of me”
    }

    // Can use default init provided by P
}

> On Jun 23, 2017, at 8:12 PM, Karl Wagner <razielim@gmail.com> wrote:
>
> What you want is some way to guarantee value semantics when writing generic code.
>
> It’s a known hole, and admittedly quite a big one. I hope that there will be time for core language improvements like this in Swift 5. Be sure to raise the issue again once planning for that starts!
>
> - Karl
>
>> On 23. Jun 2017, at 23:43, Robert Bennett via swift-evolution <swift-evolution@swift.org> wrote:
>>
>> Hello Swift Evolution,
>>
>> I’m bumping into an annoying problem with protocols. In a class or struct it is common to have a `let` instance variable and assign it in `init`. Unfortunately there is no way to translate this into a protocol with init in an extension. If attempting to set the variable in init in an extension, it must be of type { get set }, which means it cannot be a `let` constant in the conforming type. AFAIK there is no way around this — if you want to set an instance variable in an initializer in a protocol extension, it must be marked as { get set }. The alternative is to write the initializer separately for each adopting type, but this violates DRY.
>>
>> Hence, I am proposing a third option to go along with `get` and `set` in a protocol. This would indicate that the variable can be a constant, but is settable in an initializer. In this case, the conforming type *must* use `let` to declare the variable.
>>
>> Option 1: the keyword `let`. If present, it would need to be the only thing in the curly brackets because it simultaneously implies `get` and not `set`.
>>
>> protocol P {
>> var x: Int { let }
>> init(_ x: Int)
>> func modifyX()
>> }
>> extension P {
>> init(_ x: Int) {
>> self.x = x // This is ok; would not be ok if x were marked { get }
>> }
>>
>> func modifyX() {
>> self.x += 1 // Not allowed
>> }
>> }
>>
>> struct S: P {
>> let x: Int // This is ok; would not be ok if x were marked { get set }
>> }
>>
>> Option 2: `set(init)`. Can (and often will) coexist with `get`.
>>
>> protocol P {
>> var x: Int { get set(init) }
>> init(_ x: Int)
>> func modifyX()
>> }
>> extension P {
>> init(_ x: Int) {
>> self.x = x // This is ok; would not be ok if x were marked { get }
>> }
>>
>> func modifyX() {
>> self.x += 1 // Not allowed
>> }
>> }
>>
>> struct S: P {
>> let x: Int // This is ok; would not be ok if x were marked { get set }
>> }
>>
>>
>> I’d like to hear all of your thoughts on this.
>>
>> Best,
>> Robert
>> _______________________________________________
>> swift-evolution mailing list
>> swift-evolution@swift.org
>> https://lists.swift.org/mailman/listinfo/swift-evolution
>
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Matthew Johnson) #9

I wrote a draft of a partial initializer proposal back in January or February of last year. If you're thinking of re-introducing that topic you might want to take a look at it. You can find it here: https://github.com/anandabits/swift-evolution/blob/partial-initializers/proposals/NNNN-partial-initializers.md. There was some discussion on the list as well. You might want to look up that conversation in the archives.

···

Sent from my iPad

On Jun 25, 2017, at 2:24 PM, Robert Bennett via swift-evolution <swift-evolution@swift.org> wrote:

You’re right Xiaodi, I did not entirely think this through. However I think this could still work with another addition to the language: partial initializers. These would be treated as initializers are now (meaning, for instance, no reference to self until initialization is complete) except they would only need to initialize a subset of the instance variables before exiting. An initializer could then satisfy the requirement that all instance variables must be initialized by calling partial initializers.

By incorporating partial initializers, it would be possible to guarantee that a `let` variable in a protocol is only set once. This is because an init implemented in a protocol extension could delegate to a partial initializers required by the protocol, and because they are only partial initializers, they need not set the instance variables already set in the initializer; it would be up to a conforming type to ensure this is the caee. A confirming type would define the partial initializer to set everything not already set in the protocol extension’s init, and it would be a compiler error to set a variable in the partial initializer that is already set in the extension’s init without overriding the extension’s init. Example code:

protocol P {
   var x: Int { let } // or { get set(init) }, or whatever

   partialinit initializeRest()
   init()
}

extension P {
   init() {
       initializeRest()
       self.x = 1
   }
}

struct S: P {
   let x: Int
   var y: String

   // It would be a compiler error to set x here without also redefining init()
   partialinit initializeRest() {
       self.y = “P has no knowledge of me”
   }

   // Can use default init provided by P
}

On Jun 23, 2017, at 8:12 PM, Karl Wagner <razielim@gmail.com> wrote:

What you want is some way to guarantee value semantics when writing generic code.

It’s a known hole, and admittedly quite a big one. I hope that there will be time for core language improvements like this in Swift 5. Be sure to raise the issue again once planning for that starts!

- Karl

On 23. Jun 2017, at 23:43, Robert Bennett via swift-evolution <swift-evolution@swift.org> wrote:

Hello Swift Evolution,

I’m bumping into an annoying problem with protocols. In a class or struct it is common to have a `let` instance variable and assign it in `init`. Unfortunately there is no way to translate this into a protocol with init in an extension. If attempting to set the variable in init in an extension, it must be of type { get set }, which means it cannot be a `let` constant in the conforming type. AFAIK there is no way around this — if you want to set an instance variable in an initializer in a protocol extension, it must be marked as { get set }. The alternative is to write the initializer separately for each adopting type, but this violates DRY.

Hence, I am proposing a third option to go along with `get` and `set` in a protocol. This would indicate that the variable can be a constant, but is settable in an initializer. In this case, the conforming type *must* use `let` to declare the variable.

Option 1: the keyword `let`. If present, it would need to be the only thing in the curly brackets because it simultaneously implies `get` and not `set`.

protocol P {
var x: Int { let }
init(_ x: Int)
func modifyX()
}
extension P {
init(_ x: Int) {
     self.x = x // This is ok; would not be ok if x were marked { get }
}

func modifyX() {
     self.x += 1 // Not allowed
}
}

struct S: P {
let x: Int // This is ok; would not be ok if x were marked { get set }
}

Option 2: `set(init)`. Can (and often will) coexist with `get`.

protocol P {
var x: Int { get set(init) }
init(_ x: Int)
func modifyX()
}
extension P {
init(_ x: Int) {
     self.x = x // This is ok; would not be ok if x were marked { get }
}

func modifyX() {
     self.x += 1 // Not allowed
}
}

struct S: P {
let x: Int // This is ok; would not be ok if x were marked { get set }
}

I’d like to hear all of your thoughts on this.

Best,
Robert
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Xiaodi Wu) #10

So your proposed definition of a partial initializer is one that does not
set any protocol requirement?

···

On Sun, Jun 25, 2017 at 14:54 Robert Bennett <rltbennett@icloud.com> wrote:

The compiler would only raise an error when a conforming type violated the
requirement that the partial initializer not set x. The protocol itself
would never fail to compile.

On Jun 25, 2017, at 3:33 PM, Xiaodi Wu <xiaodi.wu@gmail.com> wrote:

On Sun, Jun 25, 2017 at 2:24 PM, Robert Bennett via swift-evolution < > swift-evolution@swift.org> wrote:

You’re right Xiaodi, I did not entirely think this through. However I
think this could still work with another addition to the language: partial
initializers. These would be treated as initializers are now (meaning, for
instance, no reference to self until initialization is complete) except
they would only need to initialize a subset of the instance variables
before exiting. An initializer could then satisfy the requirement that all
instance variables must be initialized by calling partial initializers.

By incorporating partial initializers, it would be possible to guarantee
that a `let` variable in a protocol is only set once. This is because an
init implemented in a protocol extension could delegate to a partial
initializers required by the protocol, and because they are only partial
initializers, they need not set the instance variables already set in the
initializer; it would be up to a conforming type to ensure this is the
caee. A confirming type would define the partial initializer to set
everything not already set in the protocol extension’s init, and it would
be a compiler error to set a variable in the partial initializer that is
already set in the extension’s init without overriding the extension’s init.

How can the compiler raise this error when the implementation of the
partial initializer does not even have to exist at compile time?

Example code:

protocol P {
    var x: Int { let } // or { get set(init) }, or whatever

    partialinit initializeRest()
    init()
}

extension P {
    init() {
        initializeRest()

How can the compiler ensure that `initializeRest()` does not already set
`x`?

        self.x = 1
    }
}

struct S: P {
    let x: Int
    var y: String

    // It would be a compiler error to set x here without also redefining
init()
    partialinit initializeRest() {
        self.y = “P has no knowledge of me”
    }

    // Can use default init provided by P
}

> On Jun 23, 2017, at 8:12 PM, Karl Wagner <razielim@gmail.com> wrote:
>
> What you want is some way to guarantee value semantics when writing
generic code.
>
> It’s a known hole, and admittedly quite a big one. I hope that there
will be time for core language improvements like this in Swift 5. Be sure
to raise the issue again once planning for that starts!
>
> - Karl
>
>> On 23. Jun 2017, at 23:43, Robert Bennett via swift-evolution < >> swift-evolution@swift.org> wrote:
>>
>> Hello Swift Evolution,
>>
>> I’m bumping into an annoying problem with protocols. In a class or
struct it is common to have a `let` instance variable and assign it in
`init`. Unfortunately there is no way to translate this into a protocol
with init in an extension. If attempting to set the variable in init in an
extension, it must be of type { get set }, which means it cannot be a `let`
constant in the conforming type. AFAIK there is no way around this — if you
want to set an instance variable in an initializer in a protocol extension,
it must be marked as { get set }. The alternative is to write the
initializer separately for each adopting type, but this violates DRY.
>>
>> Hence, I am proposing a third option to go along with `get` and `set`
in a protocol. This would indicate that the variable can be a constant, but
is settable in an initializer. In this case, the conforming type *must* use
`let` to declare the variable.
>>
>> Option 1: the keyword `let`. If present, it would need to be the only
thing in the curly brackets because it simultaneously implies `get` and not
`set`.
>>
>> protocol P {
>> var x: Int { let }
>> init(_ x: Int)
>> func modifyX()
>> }
>> extension P {
>> init(_ x: Int) {
>> self.x = x // This is ok; would not be ok if x were marked { get
}
>> }
>>
>> func modifyX() {
>> self.x += 1 // Not allowed
>> }
>> }
>>
>> struct S: P {
>> let x: Int // This is ok; would not be ok if x were marked { get set
}
>> }
>>
>> Option 2: `set(init)`. Can (and often will) coexist with `get`.
>>
>> protocol P {
>> var x: Int { get set(init) }
>> init(_ x: Int)
>> func modifyX()
>> }
>> extension P {
>> init(_ x: Int) {
>> self.x = x // This is ok; would not be ok if x were marked { get
}
>> }
>>
>> func modifyX() {
>> self.x += 1 // Not allowed
>> }
>> }
>>
>> struct S: P {
>> let x: Int // This is ok; would not be ok if x were marked { get set
}
>> }
>>
>>
>> I’d like to hear all of your thoughts on this.
>>
>> Best,
>> Robert
>> _______________________________________________
>> swift-evolution mailing list
>> swift-evolution@swift.org
>> https://lists.swift.org/mailman/listinfo/swift-evolution
>
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


#11

For a concrete type, a partial initializer is a function that:
Can set `let` instance variables
May not refer to `self` in an rvalue
Must set the same subset of instance variables regardless of the code path taken

For a protocol, a partial initializer is simply a function that sets *any* subset of the instance variables. A protocol could provide a default implementation of one. In order for this `let` proposal to work, though, if a `let` variable is set in the protocol extension’s implementation of a (full) initializer, the protocol would need to provide at least one partial initializer that it does not provide an implementation for, so that a conforming type can fill in the gaps for all of the instance variables it defines that the protocol doesn’t require/know about. That way the compiler can have faith that a conforming type will be able to fully initialize itself with the default implementation of the required full initializer.

···

On Jun 25, 2017, at 3:57 PM, Xiaodi Wu <xiaodi.wu@gmail.com> wrote:

So your proposed definition of a partial initializer is one that does not set any protocol requirement?
On Sun, Jun 25, 2017 at 14:54 Robert Bennett <rltbennett@icloud.com <mailto:rltbennett@icloud.com>> wrote:
The compiler would only raise an error when a conforming type violated the requirement that the partial initializer not set x. The protocol itself would never fail to compile.

On Jun 25, 2017, at 3:33 PM, Xiaodi Wu <xiaodi.wu@gmail.com <mailto:xiaodi.wu@gmail.com>> wrote:

On Sun, Jun 25, 2017 at 2:24 PM, Robert Bennett via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:
You’re right Xiaodi, I did not entirely think this through. However I think this could still work with another addition to the language: partial initializers. These would be treated as initializers are now (meaning, for instance, no reference to self until initialization is complete) except they would only need to initialize a subset of the instance variables before exiting. An initializer could then satisfy the requirement that all instance variables must be initialized by calling partial initializers.

By incorporating partial initializers, it would be possible to guarantee that a `let` variable in a protocol is only set once. This is because an init implemented in a protocol extension could delegate to a partial initializers required by the protocol, and because they are only partial initializers, they need not set the instance variables already set in the initializer; it would be up to a conforming type to ensure this is the caee. A confirming type would define the partial initializer to set everything not already set in the protocol extension’s init, and it would be a compiler error to set a variable in the partial initializer that is already set in the extension’s init without overriding the extension’s init.

How can the compiler raise this error when the implementation of the partial initializer does not even have to exist at compile time?

Example code:

protocol P {
    var x: Int { let } // or { get set(init) }, or whatever

    partialinit initializeRest()
    init()
}

extension P {
    init() {
        initializeRest()

How can the compiler ensure that `initializeRest()` does not already set `x`?

        self.x = 1
    }
}

struct S: P {
    let x: Int
    var y: String

    // It would be a compiler error to set x here without also redefining init()
    partialinit initializeRest() {
        self.y = “P has no knowledge of me”
    }

    // Can use default init provided by P
}

> On Jun 23, 2017, at 8:12 PM, Karl Wagner <razielim@gmail.com <mailto:razielim@gmail.com>> wrote:
>
> What you want is some way to guarantee value semantics when writing generic code.
>
> It’s a known hole, and admittedly quite a big one. I hope that there will be time for core language improvements like this in Swift 5. Be sure to raise the issue again once planning for that starts!
>
> - Karl
>
>> On 23. Jun 2017, at 23:43, Robert Bennett via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:
>>
>> Hello Swift Evolution,
>>
>> I’m bumping into an annoying problem with protocols. In a class or struct it is common to have a `let` instance variable and assign it in `init`. Unfortunately there is no way to translate this into a protocol with init in an extension. If attempting to set the variable in init in an extension, it must be of type { get set }, which means it cannot be a `let` constant in the conforming type. AFAIK there is no way around this — if you want to set an instance variable in an initializer in a protocol extension, it must be marked as { get set }. The alternative is to write the initializer separately for each adopting type, but this violates DRY.
>>
>> Hence, I am proposing a third option to go along with `get` and `set` in a protocol. This would indicate that the variable can be a constant, but is settable in an initializer. In this case, the conforming type *must* use `let` to declare the variable.
>>
>> Option 1: the keyword `let`. If present, it would need to be the only thing in the curly brackets because it simultaneously implies `get` and not `set`.
>>
>> protocol P {
>> var x: Int { let }
>> init(_ x: Int)
>> func modifyX()
>> }
>> extension P {
>> init(_ x: Int) {
>> self.x = x // This is ok; would not be ok if x were marked { get }
>> }
>>
>> func modifyX() {
>> self.x += 1 // Not allowed
>> }
>> }
>>
>> struct S: P {
>> let x: Int // This is ok; would not be ok if x were marked { get set }
>> }
>>
>> Option 2: `set(init)`. Can (and often will) coexist with `get`.
>>
>> protocol P {
>> var x: Int { get set(init) }
>> init(_ x: Int)
>> func modifyX()
>> }
>> extension P {
>> init(_ x: Int) {
>> self.x = x // This is ok; would not be ok if x were marked { get }
>> }
>>
>> func modifyX() {
>> self.x += 1 // Not allowed
>> }
>> }
>>
>> struct S: P {
>> let x: Int // This is ok; would not be ok if x were marked { get set }
>> }
>>
>>
>> I’d like to hear all of your thoughts on this.
>>
>> Best,
>> Robert
>> _______________________________________________
>> swift-evolution mailing list
>> swift-evolution@swift.org <mailto:swift-evolution@swift.org>
>> https://lists.swift.org/mailman/listinfo/swift-evolution
>
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution


(Xiaodi Wu) #12

The protocol extension does not have to be in the same module as the
protocol itself. How will the compiler know what variables are set in the
partial initializer when it is compiling the protocol or the struct?

···

On Sun, Jun 25, 2017 at 15:13 Robert Bennett <rltbennett@icloud.com> wrote:

For a concrete type, a partial initializer is a function that:
Can set `let` instance variables
May not refer to `self` in an rvalue
Must set the same subset of instance variables regardless of the code path
taken

For a protocol, a partial initializer is simply a function that sets *any*
subset of the instance variables. A protocol could provide a default
implementation of one. In order for this `let` proposal to work, though, if
a `let` variable is set in the protocol extension’s implementation of a
(full) initializer, the protocol would need to provide at least one partial
initializer that it does not provide an implementation for, so that a
conforming type can fill in the gaps for all of the instance variables it
defines that the protocol doesn’t require/know about. That way the compiler
can have faith that a conforming type will be able to fully initialize
itself with the default implementation of the required full initializer.

On Jun 25, 2017, at 3:57 PM, Xiaodi Wu <xiaodi.wu@gmail.com> wrote:

So your proposed definition of a partial initializer is one that does not
set any protocol requirement?
On Sun, Jun 25, 2017 at 14:54 Robert Bennett <rltbennett@icloud.com> > wrote:

The compiler would only raise an error when a conforming type violated
the requirement that the partial initializer not set x. The protocol itself
would never fail to compile.

On Jun 25, 2017, at 3:33 PM, Xiaodi Wu <xiaodi.wu@gmail.com> wrote:

On Sun, Jun 25, 2017 at 2:24 PM, Robert Bennett via swift-evolution < >> swift-evolution@swift.org> wrote:

You’re right Xiaodi, I did not entirely think this through. However I
think this could still work with another addition to the language: partial
initializers. These would be treated as initializers are now (meaning, for
instance, no reference to self until initialization is complete) except
they would only need to initialize a subset of the instance variables
before exiting. An initializer could then satisfy the requirement that all
instance variables must be initialized by calling partial initializers.

By incorporating partial initializers, it would be possible to guarantee
that a `let` variable in a protocol is only set once. This is because an
init implemented in a protocol extension could delegate to a partial
initializers required by the protocol, and because they are only partial
initializers, they need not set the instance variables already set in the
initializer; it would be up to a conforming type to ensure this is the
caee. A confirming type would define the partial initializer to set
everything not already set in the protocol extension’s init, and it would
be a compiler error to set a variable in the partial initializer that is
already set in the extension’s init without overriding the extension’s init.

How can the compiler raise this error when the implementation of the
partial initializer does not even have to exist at compile time?

Example code:

protocol P {
    var x: Int { let } // or { get set(init) }, or whatever

    partialinit initializeRest()
    init()
}

extension P {
    init() {
        initializeRest()

How can the compiler ensure that `initializeRest()` does not already set
`x`?

        self.x = 1
    }
}

struct S: P {
    let x: Int
    var y: String

    // It would be a compiler error to set x here without also
redefining init()
    partialinit initializeRest() {
        self.y = “P has no knowledge of me”
    }

    // Can use default init provided by P
}

> On Jun 23, 2017, at 8:12 PM, Karl Wagner <razielim@gmail.com> wrote:
>
> What you want is some way to guarantee value semantics when writing
generic code.
>
> It’s a known hole, and admittedly quite a big one. I hope that there
will be time for core language improvements like this in Swift 5. Be sure
to raise the issue again once planning for that starts!
>
> - Karl
>
>> On 23. Jun 2017, at 23:43, Robert Bennett via swift-evolution < >>> swift-evolution@swift.org> wrote:
>>
>> Hello Swift Evolution,
>>
>> I’m bumping into an annoying problem with protocols. In a class or
struct it is common to have a `let` instance variable and assign it in
`init`. Unfortunately there is no way to translate this into a protocol
with init in an extension. If attempting to set the variable in init in an
extension, it must be of type { get set }, which means it cannot be a `let`
constant in the conforming type. AFAIK there is no way around this — if you
want to set an instance variable in an initializer in a protocol extension,
it must be marked as { get set }. The alternative is to write the
initializer separately for each adopting type, but this violates DRY.
>>
>> Hence, I am proposing a third option to go along with `get` and `set`
in a protocol. This would indicate that the variable can be a constant, but
is settable in an initializer. In this case, the conforming type *must* use
`let` to declare the variable.
>>
>> Option 1: the keyword `let`. If present, it would need to be the only
thing in the curly brackets because it simultaneously implies `get` and not
`set`.
>>
>> protocol P {
>> var x: Int { let }
>> init(_ x: Int)
>> func modifyX()
>> }
>> extension P {
>> init(_ x: Int) {
>> self.x = x // This is ok; would not be ok if x were marked {
get }
>> }
>>
>> func modifyX() {
>> self.x += 1 // Not allowed
>> }
>> }
>>
>> struct S: P {
>> let x: Int // This is ok; would not be ok if x were marked { get
set }
>> }
>>
>> Option 2: `set(init)`. Can (and often will) coexist with `get`.
>>
>> protocol P {
>> var x: Int { get set(init) }
>> init(_ x: Int)
>> func modifyX()
>> }
>> extension P {
>> init(_ x: Int) {
>> self.x = x // This is ok; would not be ok if x were marked {
get }
>> }
>>
>> func modifyX() {
>> self.x += 1 // Not allowed
>> }
>> }
>>
>> struct S: P {
>> let x: Int // This is ok; would not be ok if x were marked { get
set }
>> }
>>
>>
>> I’d like to hear all of your thoughts on this.
>>
>> Best,
>> Robert
>> _______________________________________________
>> swift-evolution mailing list
>> swift-evolution@swift.org
>> https://lists.swift.org/mailman/listinfo/swift-evolution
>
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Karl) #13

I’ll say again: I think you’re approaching this from the wrong angle. You want stronger guarantees about mutability of properties, but the answer is not to make protocols be more restrictive about how conformers are written.

What you are really asking for is the guarantee that some type which conforms to SomeProtocol has value-semantics. Once you have that guarantee, you know that you have a unique instance whose properties will only mutate if they are stored in a “var” property and you yourself mutate it.

For example, an Array’s “count” property is technically a computed property, but in a “let”-declared Array you can treat it as a “let” constant because Arrays have value semantics. Your guarantees about properties only being set in the initialiser would automatically be fulfilled without the need of any additional alternate initialisation patterns.

- Karl

···

On 25. Jun 2017, at 22:13, Robert Bennett <rltbennett@icloud.com> wrote:

For a concrete type, a partial initializer is a function that:
Can set `let` instance variables
May not refer to `self` in an rvalue
Must set the same subset of instance variables regardless of the code path taken

For a protocol, a partial initializer is simply a function that sets *any* subset of the instance variables. A protocol could provide a default implementation of one. In order for this `let` proposal to work, though, if a `let` variable is set in the protocol extension’s implementation of a (full) initializer, the protocol would need to provide at least one partial initializer that it does not provide an implementation for, so that a conforming type can fill in the gaps for all of the instance variables it defines that the protocol doesn’t require/know about. That way the compiler can have faith that a conforming type will be able to fully initialize itself with the default implementation of the required full initializer.


(Karl) #14

Just to expand on this a little bit, as I see it there are two parts to your problem. First, you were asking for some kind of partial-initialisation. Second, you (or somebody) mentioned that there might be optimisation benefits.

Here’s a kind of partial initialisation you can use right now:

public protocol SomeProtocol {
    var neverReallyMutatesAfterInit: String { get }
}

public extension SomeProtocol {
    static func withProtocolDefaults() -> SomeProtocol? {
        return (Self.self as? PartiallyInitializableSomeProtocol.Type).map { $0.withProtocolDefaults() }
    }
}

internal protocol PartiallyInitializableSomeProtocol: SomeProtocol {

    /// Creates an object with type-specific defaults.
    init()

    /// Allows customisation by the protocol after init.
    var neverReallyMutatesAfterInit: String { get set }
}

internal extension PartiallyInitializableSomeProtocol {
     static func withProtocolDefaults() -> SomeProtocol {
        var newValue = Self()
        newValue.neverReallyMutatesAfterInit = "ProtocolDefault"
        return newValue
    }
}

public struct SomeConformer: PartiallyInitializableSomeProtocol {
    public internal(set) var neverReallyMutatesAfterInit = "TypeDefault"
    internal init() {}
}

(SomeConformer.self as SomeProtocol.Type).withProtocolDefaults()?.neverReallyMutatesAfterInit // "ProtocolDefault"

With this example, the only way users of your library can obtain an instance is via the protocol function, which knows that it can construct a default instance and specialise the values.

As far as optimisation goes - I have to correct myself, because even in a protocol existential whose underlying type is known to have value semantics, computed properties themselves might not. For all the compiler knows, they could be reading/writing from global state. So the compiler can’t really eliminate repeat accesses unless it knows the underlying implementation (or we give it more information about how the content mutates). People have asked about marking stored/computed properties in protocols before, and I’ve always been against it. Protocols should contain abstract, semantic information with as little implementation-constraining stuff as possible whilst retaining the contract. That’s what makes them so powerful.

- Karl

···

On 26. Jun 2017, at 13:44, Karl Wagner <razielim@gmail.com> wrote:

On 25. Jun 2017, at 22:13, Robert Bennett <rltbennett@icloud.com> wrote:

For a concrete type, a partial initializer is a function that:
Can set `let` instance variables
May not refer to `self` in an rvalue
Must set the same subset of instance variables regardless of the code path taken

For a protocol, a partial initializer is simply a function that sets *any* subset of the instance variables. A protocol could provide a default implementation of one. In order for this `let` proposal to work, though, if a `let` variable is set in the protocol extension’s implementation of a (full) initializer, the protocol would need to provide at least one partial initializer that it does not provide an implementation for, so that a conforming type can fill in the gaps for all of the instance variables it defines that the protocol doesn’t require/know about. That way the compiler can have faith that a conforming type will be able to fully initialize itself with the default implementation of the required full initializer.

I’ll say again: I think you’re approaching this from the wrong angle. You want stronger guarantees about mutability of properties, but the answer is not to make protocols be more restrictive about how conformers are written.

What you are really asking for is the guarantee that some type which conforms to SomeProtocol has value-semantics. Once you have that guarantee, you know that you have a unique instance whose properties will only mutate if they are stored in a “var” property and you yourself mutate it.

For example, an Array’s “count” property is technically a computed property, but in a “let”-declared Array you can treat it as a “let” constant because Arrays have value semantics. Your guarantees about properties only being set in the initialiser would automatically be fulfilled without the need of any additional alternate initialisation patterns.

- Karl


(Jay) #15

I think if we start to discuss composition as part of the language, it
could solve this issue.

In my composition pitch I didn’t go into great detail (and nothing about
properties in components at this early stage - I figured I should start
simple and we can think it through further as a group), but if we had a
syntax for composing types out of components (implementations of
protocols), you could include a guarantee that a component were initialised
in init. This would also solve the partial initialisation issue - as a
component is a complete implementation of a particular protocol, which can
be fully initialised itself, before the composed type is fully initialised.

···

On Tue, 27 Jun 2017 at 00:00 Karl Wagner via swift-evolution < swift-evolution@swift.org> wrote:

On 26. Jun 2017, at 13:44, Karl Wagner <razielim@gmail.com> wrote:

On 25. Jun 2017, at 22:13, Robert Bennett <rltbennett@icloud.com> wrote:

For a concrete type, a partial initializer is a function that:
Can set `let` instance variables
May not refer to `self` in an rvalue
Must set the same subset of instance variables regardless of the code path
taken

For a protocol, a partial initializer is simply a function that sets *any*
subset of the instance variables. A protocol could provide a default
implementation of one. In order for this `let` proposal to work, though, if
a `let` variable is set in the protocol extension’s implementation of a
(full) initializer, the protocol would need to provide at least one partial
initializer that it does not provide an implementation for, so that a
conforming type can fill in the gaps for all of the instance variables it
defines that the protocol doesn’t require/know about. That way the compiler
can have faith that a conforming type will be able to fully initialize
itself with the default implementation of the required full initializer.

I’ll say again: I think you’re approaching this from the wrong angle. You
want stronger guarantees about mutability of properties, but the answer is
not to make protocols be more restrictive about how conformers are written.

What you are really asking for is the guarantee that some type which
conforms to SomeProtocol has value-semantics. Once you have that guarantee,
you know that you have a unique instance whose properties will only mutate
if they are stored in a “var” property and you yourself mutate it.

For example, an Array’s “count” property is technically a computed
property, but in a “let”-declared Array you can treat it as a “let”
constant because Arrays have value semantics. Your guarantees about
properties only being set in the initialiser would automatically be
fulfilled without the need of any additional alternate initialisation
patterns.

- Karl

Just to expand on this a little bit, as I see it there are two parts to
your problem. First, you were asking for some kind of
partial-initialisation. Second, you (or somebody) mentioned that there
might be optimisation benefits.

Here’s a kind of partial initialisation you can use right now:

public protocol SomeProtocol {
    var neverReallyMutatesAfterInit: String { get }
}

public extension SomeProtocol {
    static func withProtocolDefaults() -> SomeProtocol? {
        return (Self.self as? PartiallyInitializableSomeProtocol.Type).map
{ $0.withProtocolDefaults() }
    }
}

internal protocol PartiallyInitializableSomeProtocol: SomeProtocol {

    /// Creates an object with type-specific defaults.
    init()

    /// Allows customisation by the protocol after init.
    var neverReallyMutatesAfterInit: String { get set }
}

internal extension PartiallyInitializableSomeProtocol {
     static func withProtocolDefaults() -> SomeProtocol {
        var newValue = Self()
        newValue.neverReallyMutatesAfterInit = "ProtocolDefault"
        return newValue
    }
}

public struct SomeConformer: PartiallyInitializableSomeProtocol {
    public internal(set) var neverReallyMutatesAfterInit = "TypeDefault"
    internal init() {}
}

(SomeConformer.self as
SomeProtocol.Type).withProtocolDefaults()?.neverReallyMutatesAfterInit //
"ProtocolDefault"

With this example, the only way users of your library can obtain an
instance is via the protocol function, which knows that it can construct a
default instance and specialise the values.

As far as optimisation goes - I have to correct myself, because even in a
protocol existential whose underlying type is known to have value
semantics, computed properties themselves might not. For all the compiler
knows, they could be reading/writing from global state. So the compiler
can’t really eliminate repeat accesses unless it knows the underlying
implementation (or we give it more information about how the content
mutates). People have asked about marking stored/computed properties in
protocols before, and I’ve always been against it. Protocols should contain
abstract, semantic information with as little implementation-constraining
stuff as possible whilst retaining the contract. That’s what makes them so
powerful.

- Karl
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution