Question with calling a method which returns `Self` on super

Hi all,

here is the scenario, I want to implement a Copying protocol which can be used homogeneously

protocol Copying {
    func copy() -> Self
}

Because this protocol contains neither associated-type-requirements or self-requirements(requiring a method returning Self is different), it can surely be used homogeneously.

let objects: [Copying] = ....
for copyable in objects {
    .....
}

It will work if I make one of my base class conform to Copying

class Shape: Copying {
    var color: UIColor?

    required init() {}
    
    func copy() -> Self {
        let copied = type(of: self).init()
        copied.color = color
        return copied
    }
}

The implementation of `copy` above is forced by compiler to avoid any explicit specification of type `Shape`, so that the returning value will have a dynamic type, which is just what I want. Here, the type of `copied` is `Self`

However, if I try to make a subclass of Shape, I can't find a elegant way to implement this `copy` method in that subclass, the following code will not compile.

class Circle: Shape {
    var radius: Float = 5

    func copy() -> Self {
        let copied = super.copy()
        copied.radius = radius // compilation error
        return copied
    }
}

The compiler will complain that `copied` has no property `radius`. It turns out that calling copy() on super will yield a value of type Shape, rather than Self.

Swift now forbids explicit conversion to `Self` (I totally agree with this rule), and will automatically allow `Self` to be treated as a specific type in some circumstances. But for this case, I wonder whether this is the correct behavior or a bug? Why calling `super.copy()` not be able to get a `Self`?

I did find a work-around for this problem afterwards, but this question really haunts me...

Cheers,
Lincoln.

Hi Lincoln,

This is a known issue with ‘super’ method calls: [SR-1736] Super class method returning Self returns superclass type instead of Self · Issue #44345 · apple/swift · GitHub

A workaround is to cast the result of the supermethod call to Self explicitly:

let copied = super.copy() as! Self

Slava

···

On Aug 10, 2017, at 8:16 PM, 吴君恺 via swift-users <swift-users@swift.org> wrote:

Hi all,

here is the scenario, I want to implement a Copying protocol which can be used homogeneously

protocol Copying {
    func copy() -> Self
}

Because this protocol contains neither associated-type-requirements or self-requirements(requiring a method returning Self is different), it can surely be used homogeneously.

let objects: [Copying] = ....
for copyable in objects {
    .....
}

It will work if I make one of my base class conform to Copying

class Shape: Copying {
    var color: UIColor?

    required init() {}
    
    func copy() -> Self {
        let copied = type(of: self).init()
        copied.color = color
        return copied
    }
}

The implementation of `copy` above is forced by compiler to avoid any explicit specification of type `Shape`, so that the returning value will have a dynamic type, which is just what I want. Here, the type of `copied` is `Self`

However, if I try to make a subclass of Shape, I can't find a elegant way to implement this `copy` method in that subclass, the following code will not compile.

class Circle: Shape {
    var radius: Float = 5

    func copy() -> Self {
        let copied = super.copy()
        copied.radius = radius // compilation error
        return copied
    }
}

The compiler will complain that `copied` has no property `radius`. It turns out that calling copy() on super will yield a value of type Shape, rather than Self.

Swift now forbids explicit conversion to `Self` (I totally agree with this rule), and will automatically allow `Self` to be treated as a specific type in some circumstances. But for this case, I wonder whether this is the correct behavior or a bug? Why calling `super.copy()` not be able to get a `Self`?

I did find a work-around for this problem afterwards, but this question really haunts me...

Cheers,
Lincoln.
_______________________________________________
swift-users mailing list
swift-users@swift.org
https://lists.swift.org/mailman/listinfo/swift-users

Hi Lincoln,

This is a known issue with ‘super’ method calls: [SR-1736] Super class method returning Self returns superclass type instead of Self · Issue #44345 · apple/swift · GitHub

A workaround is to cast the result of the supermethod call to Self explicitly:

let copied = super.copy() as! Self

I’m sorry, but this is nonsense. We don’t allow ‘Self’ to refer to the dynamic Self type yet.

You could do something like this I guess,

func cast<T, U>(from: T, to: U.Type) -> U {
  return from as! U
}

let copied = cast(from: super.copy(), to: type(of: self))

Slava

···

On Aug 10, 2017, at 8:23 PM, Slava Pestov via swift-users <swift-users@swift.org> wrote:

Slava

On Aug 10, 2017, at 8:16 PM, 吴君恺 via swift-users <swift-users@swift.org <mailto:swift-users@swift.org>> wrote:

Hi all,

here is the scenario, I want to implement a Copying protocol which can be used homogeneously

protocol Copying {
    func copy() -> Self
}

Because this protocol contains neither associated-type-requirements or self-requirements(requiring a method returning Self is different), it can surely be used homogeneously.

let objects: [Copying] = ....
for copyable in objects {
    .....
}

It will work if I make one of my base class conform to Copying

class Shape: Copying {
    var color: UIColor?

    required init() {}
    
    func copy() -> Self {
        let copied = type(of: self).init()
        copied.color = color
        return copied
    }
}

The implementation of `copy` above is forced by compiler to avoid any explicit specification of type `Shape`, so that the returning value will have a dynamic type, which is just what I want. Here, the type of `copied` is `Self`

However, if I try to make a subclass of Shape, I can't find a elegant way to implement this `copy` method in that subclass, the following code will not compile.

class Circle: Shape {
    var radius: Float = 5

    func copy() -> Self {
        let copied = super.copy()
        copied.radius = radius // compilation error
        return copied
    }
}

The compiler will complain that `copied` has no property `radius`. It turns out that calling copy() on super will yield a value of type Shape, rather than Self.

Swift now forbids explicit conversion to `Self` (I totally agree with this rule), and will automatically allow `Self` to be treated as a specific type in some circumstances. But for this case, I wonder whether this is the correct behavior or a bug? Why calling `super.copy()` not be able to get a `Self`?

I did find a work-around for this problem afterwards, but this question really haunts me...

Cheers,
Lincoln.
_______________________________________________
swift-users mailing list
swift-users@swift.org <mailto:swift-users@swift.org>
https://lists.swift.org/mailman/listinfo/swift-users

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

Thank you, Slava.

I will use the trick you provided.
And knowing that this bug has already been under tracking is a great comfort to me, really :)

Lincoln

···

在 2017年8月11日,上午11:29,Slava Pestov <spestov@apple.com> 写道:

On Aug 10, 2017, at 8:23 PM, Slava Pestov via swift-users <swift-users@swift.org <mailto:swift-users@swift.org>> wrote:

Hi Lincoln,

This is a known issue with ‘super’ method calls: [SR-1736] Super class method returning Self returns superclass type instead of Self · Issue #44345 · apple/swift · GitHub

A workaround is to cast the result of the supermethod call to Self explicitly:

let copied = super.copy() as! Self

I’m sorry, but this is nonsense. We don’t allow ‘Self’ to refer to the dynamic Self type yet.

You could do something like this I guess,

func cast<T, U>(from: T, to: U.Type) -> U {
  return from as! U
}

let copied = cast(from: super.copy(), to: type(of: self))

Slava

Slava

On Aug 10, 2017, at 8:16 PM, 吴君恺 via swift-users <swift-users@swift.org <mailto:swift-users@swift.org>> wrote:

Hi all,

here is the scenario, I want to implement a Copying protocol which can be used homogeneously

protocol Copying {
    func copy() -> Self
}

Because this protocol contains neither associated-type-requirements or self-requirements(requiring a method returning Self is different), it can surely be used homogeneously.

let objects: [Copying] = ....
for copyable in objects {
    .....
}

It will work if I make one of my base class conform to Copying

class Shape: Copying {
    var color: UIColor?

    required init() {}
    
    func copy() -> Self {
        let copied = type(of: self).init()
        copied.color = color
        return copied
    }
}

The implementation of `copy` above is forced by compiler to avoid any explicit specification of type `Shape`, so that the returning value will have a dynamic type, which is just what I want. Here, the type of `copied` is `Self`

However, if I try to make a subclass of Shape, I can't find a elegant way to implement this `copy` method in that subclass, the following code will not compile.

class Circle: Shape {
    var radius: Float = 5

    func copy() -> Self {
        let copied = super.copy()
        copied.radius = radius // compilation error
        return copied
    }
}

The compiler will complain that `copied` has no property `radius`. It turns out that calling copy() on super will yield a value of type Shape, rather than Self.

Swift now forbids explicit conversion to `Self` (I totally agree with this rule), and will automatically allow `Self` to be treated as a specific type in some circumstances. But for this case, I wonder whether this is the correct behavior or a bug? Why calling `super.copy()` not be able to get a `Self`?

I did find a work-around for this problem afterwards, but this question really haunts me...

Cheers,
Lincoln.
_______________________________________________
swift-users mailing list
swift-users@swift.org <mailto:swift-users@swift.org>
https://lists.swift.org/mailman/listinfo/swift-users

_______________________________________________
swift-users mailing list
swift-users@swift.org <mailto:swift-users@swift.org>
https://lists.swift.org/mailman/listinfo/swift-users

here is the scenario, I want to implement a Copying protocol which can be used homogeneously

protocol Copying {
    func copy() -> Self
}

Because this protocol contains neither associated-type-requirements or self-requirements(requiring a method returning Self is different), it can surely be used homogeneously.

let objects: [Copying] = ....
for copyable in objects {
    .....
}

But Copying does contain a Self requirement. It is used the result of the copy() method

Here is how I implement Copying :

protocol Copyable
{
  func copy() -> Self
  
  init(other: Self)
}

// This extension provides a default implementation
extension Copyable
{
  func copy() -> Self
  {
    return type(of: self).init(other: self)
  }
}

implementing Copyable on a struct is simple :

struct Struct : Copyable
{
  var value: String
  
  init(value: String)
  {
    self.value = value
  }
  
  init(other: Struct)
  {
    value = other.value
  }
}

But implementing it on a class is slightly more troublesome.

class Shape : Copyable
{
  var color: NSColor
  
  init(color: NSColor)
  {
    self.color = color
  }
}

At this stage, the compiler (obviously) screams that Copyable is not implemented but the first "error" it notices it marks as being on the default implementation in the extension: Method 'copy()' in non-final class 'Shape' must return `Self` to conform to protocol 'Copyable'

Marking this error on the extension method, which obviously does exactly what the error says is missing ???

Implementing the copy() method in Shape kills this error message but leaves me wondering why I have to now repeat the exact same code in the implementing method that I wrote in the default method in the protocol extension ; something that the compiler is completely ignoring.

Implementing the protocol with a struct takes account of the default method but with a class ignores it. This smells remarkably like a bug !

Nonetheless, I now end up with the following code, complete with duplicate copy() method :

class Shape : Copyable
{
  var color: NSColor
  
  func copy() -> Self
  {
    return type(of: self).init(other: self)
  }

  init(color: NSColor)
  {
    self.color = color
  }
  
  required init(other: Shape)
  {
    color = other.color
  }
}

Then I can go on to declare subclasses :

class Circle : Shape
{
  var radius: Double = 0.0
  
  init(color: NSColor, radius: Double)
  {
    super.init(color: color)
    
    self.radius = radius
  }
    
  required init(other: Shape)
  {
    super.init(other: other)
    
    if let other = other as? Circle
    {
      radius = other.radius
    }
  }
}

class Square : Shape
{
  var side: Double = 0.0
  
  init(color: NSColor, side: Double)
  {
    super.init(color: color)
    
    self.side = side
  }
    
  required init(other: Shape)
  {
    super.init(other: other)
    
    if let other = other as? Square
    {
      side = other.side
    }
  }
}

And, the, if I create an extension to Array :

extension Array where Element : Copyable
{
  func copy() -> Array<Element>
  {
    var result = [Element]()
    
    self.forEach { result.append($0.copy()) }
    
    return result
  }
}

I can then use the following code to create an array of Shapes but, obviously not of Copyable, due to its restrictions on the Self requirements on the protocol.

{
  let shapes = [Circle(color: .black, radius: 5.0), Square(color: .black, side: 5.0)]
  
  let copies = shapes.copy()
  
  …
}

Thus, another possible solution to your problem is to declare the required init(other: Self) in the protocol.

Which still leaves my question of why I have to reimplement the default copy(other:) method from the protocol's extension in the base class ???

Joanna

···

--
Joanna Carter
Carter Consulting

I really must do more research before making accusations ;-)

Which still leaves my question of why I have to reimplement the default copy(other:) method from the protocol's extension in the base class ???

Apparently, the answer is to remove the declaration of the copy(other:) method from the protocol itself and to leave the default implementation in the extension !

So I now end up with :

protocol Copyable
{
  init(other: Self)
}

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

class Shape : Copyable
{
  var color: NSColor
  
  init(color: NSColor)
  {
    self.color = color
  }
  
  required init(other: Shape)
  {
    color = other.color
  }
}

… and all is well with the world :-)

Mind you, the compiler error messages were hardly helpful to diagnosing this particular problem :-(

Joanna

···

--
Joanna Carter
Carter Consulting