[Pitch] Tweak `Self` and introduce `Current`


(Braeden Profile) #1

One could workaround the problem and use final, but what if the class
meant to be subtypeable? Self simply does not work in this scenario.

It works exactly as it should in this scenario. If A isn't final, then by
definition it's impossible for A to make a copy of type Self. I see,
however, what you mean, which is that you wish you could write a protocol
requirement for a function that returns #Self.

What do you mean? Even with the limited `Self` support in Swift 3, I can write a very effective copying paradigm.
protocol Copyable
{
  func copy() -> Self
}

class Building: Copyable
{
  var floors = 1
  
  required init()
    { }
  
  func copy() -> Self
  {
    let dynamicType = type(of: self)
    let newObject = dynamicType.init()
    newObject.floors = self.floors
    
    return newObject
  }
}

class Mansion: Building
{
  var butlers = 3
  
  override func copy() -> Self
  {
  // let newObject = super.copy()
    let newObject = super.copy() as! Mansion
    
    newObject.butlers = self.butlers
    
  // return newObject
    return unsafeBitCast(newObject, to: type(of: self))
  }
}

let hoboHouse = Building()
hoboHouse.floors = 0

let beggarHouse = hoboHouse.copy()
print(beggarHouse.floors) // "0"

let myHouse = Mansion()
myHouse.floors = 4

let yourHouse = myHouse.copy()
print(yourHouse.floors) // “4”

Besides the poor support for `Self` in the function body (SE–0068 fixes that), it seems like an acceptable way of doing it.

Of course, I would love being able to use an initializer setup, but there are serious bugs in the implementation.

protocol Clonable
{
  init(other: Self)
}

extension Clonable
{
  func clone() -> Self
    { return type(of: self).init(other: self) }
}

class Base: Clonable
{
  var x: Int
  
  init(x: Int)
    { self.x = x }
  
  required init(other: Base)
    { self.x = other.x }
}

class Derived: Base
{
  var y: String
  
  init(x: Int, y: String)
  {
    self.y = y
    super.init(x: x)
  }
  
  // Should be required by the Clonable protocol, but it isn't.
  required init(other: Derived)
  {
    self.y = other.y
    super.init(other: other)
  }
  
  // Required because it was `required` in Base. Even a `Derived` calls this initializer to clone, which is wrong. Bugs abound.
  required init(other: Base)
    { fatalError("init(other:) is wrong.") }
}

let me = Derived(x: 1, y: "food")
let alienClone = me.clone() // "init(other:) is wrong."


(Xiaodi Wu) #2

* One could workaround the problem and use final, but what if the class
** meant to be subtypeable? Self simply does not work in this scenario.
*
It works exactly as it should in this scenario. If A isn't final, then by
definition it's impossible for A to make a copy of type Self. I see,
however, what you mean, which is that you wish you could write a protocol
requirement for a function that returns #Self.

What do you mean? Even with the limited `Self` support in Swift 3, I can
write a very effective copying paradigm.

That was poor writing on my part. What I meant was that, exactly as you
show below, `Building.copy()` needs to invoke a method on a subclass to
obtain an instance of type `Self`. There is nothing that `Building` can do
without relying on its subclasses in order to obtain such an instance (I
can't imagine a use case _why_ one couldn't do that, but I understood
Adrian as saying that he has that problem).

protocol Copyable

{
  func copy() -> Self
}

class Building: Copyable
{
  var floors = 1
  
  required init()
    { }
  
  func copy() -> Self
  {
    let dynamicType = type(of: self)
    let newObject = dynamicType.init()
    newObject.floors = self.floors
    
    return newObject
  }
}

class Mansion: Building
{
  var butlers = 3
  
  override func copy() -> Self
  {
  // let newObject = super.copy()
    let newObject = super.copy() as! Mansion
    
    newObject.butlers = self.butlers
    
  // return newObject
    return unsafeBitCast(newObject, to: type(of: self))
  }
}

let hoboHouse = Building()
hoboHouse.floors = 0

let beggarHouse = hoboHouse.copy()
print(beggarHouse.floors) // "0"

let myHouse = Mansion()
myHouse.floors = 4

let yourHouse = myHouse.copy()
print(yourHouse.floors) // “4”

Besides the poor support for `Self` in the function body (SE–0068 fixes
that), it seems like an acceptable way of doing it.

Agree, it's a shame that one needs to jump through that hoop with
`unsafeBitCast`. With SE-0068, I'd hope that `super.copy() as! Self` would
be sufficient.

Of course, I would love being able to use an initializer setup, but there
are serious bugs in the implementation.

protocol Clonable
{
  init(other: Self)
}

extension Clonable
{
  func clone() -> Self
    { return type(of: self).init(other: self) }
}

class Base: Clonable
{
  var x: Int
  
  init(x: Int)
    { self.x = x }
  
  required init(other: Base)
    { self.x = other.x }
}

class Derived: Base
{
  var y: String
  
  init(x: Int, y: String)
  {
    self.y = y
    super.init(x: x)
  }
  
  // Should be required by the Clonable protocol, but it isn't.
  required init(other: Derived)
  {
    self.y = other.y
    super.init(other: other)
  }
  
  // Required because it was `required` in Base. Even a `Derived` calls this initializer to clone, which is wrong. Bugs abound.
  required init(other: Base)
    { fatalError("init(other:) is wrong.") }
}

let me = Derived(x: 1, y: "food")
let alienClone = me.clone() // "init(other:) is wrong."

Agree. That seems wrong. Great example.

···

On Sat, Jan 7, 2017 at 4:00 PM, Braeden Profile via swift-evolution < swift-evolution@swift.org> wrote:

_______________________________________________

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


(Braeden Profile) #3

Of course, I would love being able to use an initializer setup, but there are serious bugs in the implementation.

protocol Clonable
{
  init(other: Self)
}

extension Clonable
{
  func clone() -> Self
    { return type(of: self).init(other: self) }
}

class Base: Clonable
{
  var x: Int
  
  init(x: Int)
    { self.x = x }
  
  required init(other: Base)
    { self.x = other.x }
}

class Derived: Base
{
  var y: String
  
  init(x: Int, y: String)
  {
    self.y = y
    super.init(x: x)
  }
  
  // Should be required by the Clonable protocol, but it isn't.
  required init(other: Derived)
  {
    self.y = other.y
    super.init(other: other)
  }
  
  // Required because it was `required` in Base. Even a `Derived` calls this initializer to clone, which is wrong. Bugs abound.
  required init(other: Base)
    { fatalError("init(other:) is wrong.") }
}

let me = Derived(x: 1, y: "food")
let alienClone = me.clone() // "init(other:) is wrong."

Agree. That seems wrong. Great example.

So, is this odd behavior intentional, a bug, or a design deficiency? I would think that when a protocol has a method or initializer has `Self` parameters—like in Clonable—every subclass would be required to implement its own specialized version (much like a required initializer). That would be a special case of the protocol system, though.

As it sits, even fixing the calling behavior of my example leaves us with the problem of subclasses inheriting inapplicable required initializers from superclasses that actually don’t make any sense.

Does this deserve its own thread?


(Xiaodi Wu) #4

Dunno, maybe best to have its own thread. It's not mentioned as part of
SE-0068, but IMO a complete design that respects the spirit of that
proposal *should* involve allowing you to write:

class Base : Clonable {
  required init(other: Self) { ... }
}

class Derived : Base {
  required init(other: Self) { ... }
}
···

On Sat, Jan 7, 2017 at 5:33 PM, Braeden Profile <jhaezhyr12@gmail.com> wrote:

Of course, I would love being able to use an initializer setup, but there
are serious bugs in the implementation.

protocol Clonable
{
  init(other: Self)
}

extension Clonable
{
  func clone() -> Self
    { return type(of: self).init(other: self) }
}

class Base: Clonable
{
  var x: Int
  
  init(x: Int)
    { self.x = x }
  
  required init(other: Base)
    { self.x = other.x }
}

class Derived: Base
{
  var y: String
  
  init(x: Int, y: String)
  {
    self.y = y
    super.init(x: x)
  }
  
  // Should be required by the Clonable protocol, but it isn't.
  required init(other: Derived)
  {
    self.y = other.y
    super.init(other: other)
  }
  
  // Required because it was `required` in Base. Even a `Derived` calls this initializer to clone, which is wrong. Bugs abound.
  required init(other: Base)
    { fatalError("init(other:) is wrong.") }
}

let me = Derived(x: 1, y: "food")
let alienClone = me.clone() // "init(other:) is wrong."

Agree. That seems wrong. Great example.

So, is this odd behavior intentional, a bug, or a design deficiency? I
would think that when a protocol has a method or initializer has `Self`
parameters—like in Clonable—every subclass would be required to implement
its own specialized version (much like a required initializer). That would
be a special case of the protocol system, though.

As it sits, even fixing the calling behavior of my example leaves us with
the problem of subclasses inheriting inapplicable required initializers
from superclasses that actually don’t make any sense.

Does this deserve its own thread?


(Daniel Leping) #5

IMO the most problematic Self becomes in a generic context, i.e.:

(pseudo code below)

protocol Monad {
associatedtype A

func flatMap<B>(f:(A)->Monad<B>) -> ErasedSelf<B>
}

I would really like to have this ErasedSelf.

···

On Sun, 8 Jan 2017 at 5:21 Xiaodi Wu via swift-evolution < swift-evolution@swift.org> wrote:

On Sat, Jan 7, 2017 at 5:33 PM, Braeden Profile <jhaezhyr12@gmail.com> > wrote:

Of course, I would love being able to use an initializer setup, but there
are serious bugs in the implementation.

protocol Clonable
{
  init(other: Self)
}

extension Clonable
{
  func clone() -> Self
    { return type(of: self).init(other: self) }
}

class Base: Clonable
{
  var x: Int
  
  init(x: Int)
    { self.x = x }
  
  required init(other: Base)
    { self.x = other.x }
}

class Derived: Base
{
  var y: String
  
  init(x: Int, y: String)
  {
    self.y = y
    super.init(x: x)
  }
  
  // Should be required by the Clonable protocol, but it isn't.
  required init(other: Derived)
  {
    self.y = other.y
    super.init(other: other)
  }
  
  // Required because it was `required` in Base. Even a `Derived` calls this initializer to clone, which is wrong. Bugs abound.
  required init(other: Base)
    { fatalError("init(other:) is wrong.") }
}

let me = Derived(x: 1, y: "food")
let alienClone = me.clone() // "init(other:) is wrong."

Agree. That seems wrong. Great example.

So, is this odd behavior intentional, a bug, or a design deficiency? I
would think that when a protocol has a method or initializer has `Self`
parameters—like in Clonable—every subclass would be required to implement
its own specialized version (much like a required initializer). That would
be a special case of the protocol system, though.

As it sits, even fixing the calling behavior of my example leaves us with
the problem of subclasses inheriting inapplicable required initializers
from superclasses that actually don’t make any sense.

Does this deserve its own thread?

Dunno, maybe best to have its own thread. It's not mentioned as part of
SE-0068, but IMO a complete design that respects the spirit of that
proposal *should* involve allowing you to write:

class Base : Clonable {
  required init(other: Self) { ... }
}

class Derived : Base {
  required init(other: Self) { ... }
}

_______________________________________________

swift-evolution mailing list

swift-evolution@swift.org

https://lists.swift.org/mailman/listinfo/swift-evolution


(Adrian Zubarev) #6

There are a few good points made here. It’s an interesting workaround to use type(of:) to get a similar behavior I wanted. My point is, that Current or call it static Self should exist to fill some gaps in our design patterns. Using Self in protocols should not always mean that you shall use the dynamic behavior. Yes on value types the dynamic type is the same conforming type.

// All these protocol do not compile
// This is not real world example, but IMO should be possible
protocol P : class {
     
    associatedtype StaticSelf : AnyObject
    func castOrTrap<T : StaticSelf>() -> T
}

protocol P : class {
     
    associatedtype StaticSelf : Self
    func castOrTrap<T : StaticSelf>() -> T
}

protocol P : class {
     
    func castOrTrap<T : Self>() -> T
}
The absence of static Self does hurt the flexibility of the language to design a specific but yet clear behavior. The P protocol is meant to have a static Self, where conforming to it would result in a shorthand version using StaticSelf/Current or the conforming type:

class A : P {
     
    func castOrTrap<T : Current>() -> T { … }
    // or
    func castOrTrap<T : A>() -> T { … }
}
I cannot think of a possible workaround here except of defining that method on the base type itself. If other not derived classes need to have this pattern, I cannot create an ancestor protocol as I might would like.

The forced required init might be a good workaround for the issue from some previous post, but is it really what we always wanted? This restriction lives only on the non-final classes because Self there is dynamic, which feels kinda inconsistent. As I already mentioned, I’d be happy if we could drop the restriction on the conforming type to allow the user to decide if we want to follow the contract of using self (lowercased) and type(of: self) or could simply override Self with ContainingTypeName. That would solve that issue, if I’m not totally missing here something.

But the issue from above remans unsolved. Furthermore, does dynamic Self make any sense on non-final classes as a parameter type? Can anyone show me a plausible code snippet for that?

Sure my arguments are more like might, want etc. and not that much of a weight, but that’s my honest opinion that some restrictions makes the language less flexible as it could be.

Self is exactly like .Type which might also be magically .Protocol. The static and dynamic behaviors are baked into one place. :confused:

···

--
Adrian Zubarev
Sent with Airmail

Am 8. Januar 2017 um 00:51:32, Xiaodi Wu via swift-evolution (swift-evolution@swift.org) schrieb:

On Sat, Jan 7, 2017 at 5:33 PM, Braeden Profile <jhaezhyr12@gmail.com> wrote:

Of course, I would love being able to use an initializer setup, but there are serious bugs in the implementation.

protocol Clonable
{
init(other: Self)
}

extension Clonable
{
func clone() -> Self
{ return type(of: self).init(other: self) }
}

class Base: Clonable
{
var x: Int

init(x: Int)
{ self.x = x }

required init(other: Base)
{ self.x = other.x }
}

class Derived: Base
{
var y: String

init(x: Int, y: String)
{
self.y = y
super.init(x: x)
}

// Should be required by the Clonable protocol, but it isn't.
required init(other: Derived)
{
self.y = other.y
super.init(other: other)
}

// Required because it was `required` in Base. Even a `Derived` calls this initializer to clone, which is wrong. Bugs abound.
required init(other: Base)
{ fatalError("init(other:) is wrong.") }
}

let me = Derived(x: 1, y: "food")
let alienClone = me.clone() // "init(other:) is wrong."

Agree. That seems wrong. Great example.

So, is this odd behavior intentional, a bug, or a design deficiency? I would think that when a protocol has a method or initializer has `Self` parameters—like in Clonable—every subclass would be required to implement its own specialized version (much like a required initializer). That would be a special case of the protocol system, though.

As it sits, even fixing the calling behavior of my example leaves us with the problem of subclasses inheriting inapplicable required initializers from superclasses that actually don’t make any sense.

Does this deserve its own thread?

Dunno, maybe best to have its own thread. It's not mentioned as part of SE-0068, but IMO a complete design that respects the spirit of that proposal *should* involve allowing you to write:

class Base : Clonable {
  required init(other: Self) { ... }
}

class Derived : Base {
  required init(other: Self) { ... }
}

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


(Xiaodi Wu) #7

There are a few good points made here. It’s an interesting workaround to
use type(of:) to get a similar behavior I wanted. My point is, that
Current or call it static Self should exist to fill some gaps in our
design patterns. Using Self in protocols should not always mean that you
shall use the dynamic behavior. Yes on value types the dynamic type is the
same conforming type.

I'm not sure in what way you intend to distinguish between static and
dynamic Self in protocols.

- If class `Base` conforms to protocol `P`, then final class `Derived :
Base` also conforms to protocol `P`. This is, AFAIK, non-negotiable.

- In Swift, a type can only conform to a protocol in one way. This is
unlikely to change in Swift 4 or 5.

- If `P` guarantees a method called `frobnicate()` and we have `let foo =
Derived()`, then `foo.frobnicate()` and `(foo as Base).frobnicate()` invoke
the same method. See: <
https://www.raizlabs.com/dev/2016/12/swift-method-dispatch/> about dispatch
rules.

- If `frobnicate()` is guaranteed by `P` to return a value of type `Self`,
`Base.frobnicate()` must provide an implementation that returns a value of
type `Self` and `Derived.frobnicate()` must provide an implementation that
returns a value of type `Derived`. Both `foo.frobnicate()` and `(foo as
Base).frobnicate()` invoke the implementation on `Derived`, which returns a
value of type `Derived`.

- As Anton wrote, one improvement is to make it possible to write
`Base.frobnicate()` by spelling out the return value as `Base` instead of
`Self`, with the consequence that `Derived` _must_ also provide its own
implementation that spells out `Derived`. However, unless I'm mistaken,
since Swift allows overloading by return type, `Derived.frobnicate()` would
be an overload and not an override of `Base.frobnicate()`, and yet `(foo as
Base).frobnicate()` must dispatch to the implementation on `Derived`.

- If `frobnicate()` could be guaranteed to return a value of type
`StaticSelf`, `Base.frobnicate()` must provide an implementation that
returns a value of type `Base` and `Derived.frobnicate()` must still
provide an implementation that returns a value of type `Derived`. Both
`foo.frobnicate()` and `(foo as Base).frobnicate()` must still invoke the
implementation on `Derived`, which still returns a value of type `Derived`.

- If Anton's suggestion is adopted and the issues with dispatch overcome,
then the above would be possible, but both the `StaticSelf` and `Self`
versions appear to do the same thing. If Anton's suggestion is not adopted,
then the above would not be possible.

So, why do you think you need `StaticSelf` in the context of protocols?

// All these protocol do not compile

···

On Sun, Jan 8, 2017 at 4:09 AM, Adrian Zubarev < adrian.zubarev@devandartist.com> wrote:

// This is not real world example, but IMO should be possible
protocol P : class {

    associatedtype StaticSelf : AnyObject
    func castOrTrap<T : StaticSelf>() -> T
}

protocol P : class {

    associatedtype StaticSelf : Self
    func castOrTrap<T : StaticSelf>() -> T
}

protocol P : class {

    func castOrTrap<T : Self>() -> T
}

The absence of static Self does hurt the flexibility of the language to
design a specific but yet clear behavior. The P protocol is meant to have
a static Self, where conforming to it would result in a shorthand version
using StaticSelf/Current or the conforming type:

class A : P {

    func castOrTrap<T : Current>() -> T { … }
    // or
    func castOrTrap<T : A>() -> T { … }
}

I cannot think of a possible workaround here except of defining that
method on the base type itself. If other not derived classes need to have
this pattern, I cannot create an ancestor protocol as I might would like.
------------------------------

The forced required init might be a good workaround for the issue from
some previous post, but is it really what we always wanted? This
restriction lives only on the non-final classes because Self there is
dynamic, which feels kinda inconsistent. As I already mentioned, I’d be
happy if we could drop the restriction on the conforming type to allow the
user to decide if we want to follow the contract of using self
(lowercased) and type(of: self) or could simply override Self with
ContainingTypeName. That would solve that issue, if I’m not totally
missing here something.

But the issue from above remans unsolved. Furthermore, does dynamic Self
make any sense on non-final classes as a parameter type? Can anyone show me
a plausible code snippet for that?
------------------------------

Sure my arguments are more like *might*, *want* etc. and not that much of
a weight, but that’s my honest opinion that some restrictions makes the
language less flexible as it could be.

Self is exactly like .Type which might also be magically .Protocol. The
static and dynamic behaviors are baked into one place. :confused:

--
Adrian Zubarev
Sent with Airmail

Am 8. Januar 2017 um 00:51:32, Xiaodi Wu via swift-evolution (
swift-evolution@swift.org) schrieb:

On Sat, Jan 7, 2017 at 5:33 PM, Braeden Profile <jhaezhyr12@gmail.com> > wrote:

Of course, I would love being able to use an initializer setup, but
there are serious bugs in the implementation.

protocol Clonable
{
init(other: Self)
}

extension Clonable
{
func clone() -> Self
{ return type(of: self).init(other: self) }
}

class Base: Clonable
{
var x: Int

init(x: Int)
{ self.x = x }

required init(other: Base)
{ self.x = other.x }
}

class Derived: Base
{
var y: String

init(x: Int, y: String)
{
self.y = y
super.init(x: x)
}

// Should be required by the Clonable protocol, but it isn't.
required init(other: Derived)
{
self.y = other.y
super.init(other: other)
}

// Required because it was `required` in Base. Even a `Derived` calls
this initializer to clone, which is wrong. Bugs abound.
required init(other: Base)
{ fatalError("init(other:) is wrong.") }
}

let me = Derived(x: 1, y: "food")
let alienClone = me.clone() // "init(other:) is wrong."

Agree. That seems wrong. Great example.

So, is this odd behavior intentional, a bug, or a design deficiency? I
would think that when a protocol has a method or initializer has `Self`
parameters—like in Clonable—every subclass would be required to implement
its own specialized version (much like a required initializer). That would
be a special case of the protocol system, though.

As it sits, even fixing the calling behavior of my example leaves us with
the problem of subclasses inheriting inapplicable required initializers
from superclasses that actually don’t make any sense.

Does this deserve its own thread?

Dunno, maybe best to have its own thread. It's not mentioned as part of
SE-0068, but IMO a complete design that respects the spirit of that
proposal *should* involve allowing you to write:

class Base : Clonable {
  required init(other: Self) { ... }
}

class Derived : Base {
  required init(other: Self) { ... }
}

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