Inconsistent protocol Self problem

I have a protocol Copyable, that I would like to declare like this:

public protocol Copyable
{
  init(other: Self)
}


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

However, in implementing it, I get the ubiquitous (possibly iniquitous as well) error in code like this:

  public required convenience init(other: Property)
  {
    self.init()
    
    if let copyableValue = other.value as? Copyable // error: Protocol 'Copyable' can only be used as a generic constraint because it has Self or associated type requirements
    {
      value = copyableValue.copy() as! valueT
    }
    else
    {
      value = other.value
    }
  }

So, I change the protocol to:

public protocol Copyable
{
  init(other: Copyable)
}


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

… and that gets rid of the unusable Self problem in the implementation, but it does mean I have to manually cast the other parameter to the type of the implementing class in each and every implementation of the copy constructor, like this:

  public required convenience init(other: Copyable)
  {
    self.init()
    
    if let other = other as? Property
    {
      if let copyableValue = other.value as? Copyable
      {
        value = copyableValue.copy() as! valueT
      }
      else
      {
        value = other.value
      }
    }
  }

What is even more strange is that the copy() function declaration uses Self and the compiler doesn't complain, it is only if I use Self in the init(:_) declaration that I get the error.

This seems to be inconsistent as one part of a protocol can use Self without the problem but another causes it.

Any words of wisdom here? Or is this a bug?

The behavior is correct, however it's a little bit hard to tell what you're trying to build since the enclosing type is missing and I have no idea what valueT supposed to be (if it's a type it should be capitalized by Swift's conventions) nor what type value has.

The complete type is:

public class Property<valueT : DefaultValueProvider & Equatable> : Copyable
{
  public var value = valueT()

  public init() { }
  
  public required convenience init(other: Copyable)
  {
    self.init()
    
    if let other = other as? Property
    {
      if let copyableValue = other.value as? Copyable
      {
        value = copyableValue.copy() as! valueT
      }
      else
      {
        value = other.value
      }
    }
  }
}

As you can see, valueT is a generic parameter and it is my convention to write them like I have to make them easier to distinguish from "primary" types

But the question is not about that.

It is about why I can use Self, in the protocol, for the copy() function but not for the init(:_)

That is the inconsistency.

Well there is still some thing that is missing and I don't see the full picture you have in mind of the API you're trying to achieve.

Would something like this suit you? (I made Property class final to allow the conditional extension.)

protocol Copyable {
  init(other: Self)
}

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

protocol DefaultValueProvider {
  associatedtype T
  var value: T { get }
  init()
}

final class Property<T> : DefaultValueProvider
  where T : DefaultValueProvider & Equatable {
  var value: T = .init()
  init() {}
}

// Property is only copyable when T is copyable
extension Property : Copyable where T : Copyable {
  convenience init(other: Property) {
    self.init()
    value = other.value.copy()
  }
}

To answer the other question. I think is it's allowed because you're forcing it with as!. (Please correct me if I'm wrong here.)

Thanks for trying Adrian but, no it doesn't help.

Here's the complete playground:

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


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


protocol DefaultValueProvider
{
  init()
}


extension Int : DefaultValueProvider { }


extension String : DefaultValueProvider { }


protocol PropertyProtocol
{
  func getValue<valueType : DefaultValueProvider & Equatable>() throws -> valueType
  
  func setValue<valueType : DefaultValueProvider & Equatable>(_ value: valueType) throws
  
  func copy() -> PropertyProtocol
}


final class Property<valueT : DefaultValueProvider & Equatable> : Copyable
{
  var value = valueT()
  
  init() { }
  
  required convenience init(other: Property)
  {
    self.init()
    
    value = other.value
  }
}


extension Property where valueT : Copyable
{
  convenience init(other: Property)
  {
    self.init()
    
    value = other.value.copy()
  }
  
  func copy() -> Property
  {
    return .init(other: self)
  }
}


extension Property : PropertyProtocol
{
  public enum Error : Swift.Error
  {
    case invalidPropertyType
  }
  
  public func getValue<valueType>() throws -> valueType where valueType : DefaultValueProvider & Equatable
  {
    guard let value = value as? valueType else
    {
      throw Error.invalidPropertyType
    }
    
    return value
  }
  
  public func setValue<valueType>(_ value: valueType) throws where valueType : DefaultValueProvider & Equatable
  {
    guard let newValue = value as? valueT else
    {
      throw Error.invalidPropertyType
    }
    
    self.value = newValue
  }
  
  public func copy() -> PropertyProtocol
  {
    return Property(other: self)
  }
}


class Thing : DefaultValueProvider, Equatable, Copyable
{
  public static func == (lhs: Thing, rhs: Thing) -> Bool
  {
    return lhs.value == rhs.value
  }
  
  public var value: Int
  
  public required convenience init()
  {
    self.init(value: 0)
  }
  
  public init(value: Int)
  {
    self.value = value
  }
  
  public required convenience init(other: Thing)
  {
    self.init()
    
    value = other.value
  }
}

let stringProperty: PropertyProtocol = Property<String>()

let copyStringProperty = (stringProperty).copy()

try stringProperty.setValue("Joanna")

let stringValue: String = try stringProperty.getValue()

print(stringValue)

let copyStringValue: String = try copyStringProperty.getValue()

print(copyStringValue)

let thingProperty: PropertyProtocol = Property<Thing>()

let copyThingProperty = thingProperty.copy()

let thingValue: Thing = try thingProperty.getValue()

thingValue.value = 123

print(thingValue.value)

let copyThingValue: Thing = try copyThingProperty.getValue()

print(copyThingValue.value)

PropertyProtocol is necessary because I am holding a dictionary of Property objects and use the PropertyProtocol as a non-generic "type eraser"

And the problem with the conditional extension is that, addressing the Property objects through the PropertyProtocol, neither the copy() or the init(:_) in the extension get called.

If I address the Property objects directly, then all is fine and the extension methods get called.

So, I resorted to doing away with the extension and putting conditional code in the one init(:_) method in Property, which then meant that I then had to use a version of init(:_) in the protocol that took a Copyable parameter, to avoid the Self error.

I have no problem getting around this, it just means a different way of coding.

But, the question remains - why do I get the Self error in the protocol for the init(:_) declaration but not for the copy() method declaration?

The reason that

protocol Copyable {  
  func copy() -> Self
}

is usable as a type, whereas

protocol Copyable {  
  init(_ other: Self)
}

isn't currently usable as a type is because the former allows for covariance – that is, when calling copy() on an arbitrary Copyable value, Swift can erase a value of type Self to a Copyable value without the caller having to know the concrete underlying type.

The same cannot be applied to the second definition of Copyable, we cannot erase a Self parameter to a Copyable parameter, as function parameters are contravariant with respect to their arguments. That is, we cannot pass an arbitrary Copyable value to a parameter that carries the guarantee that the passed value has the exact same type as the conforming type. There's currently no way we can make that guarantee – as a Copyable could contain an arbitrary underlying conforming value (though once we get generalised existentials we'll be able to open a Copyable value in order to access the underlying concrete type and perform a typecast with it).

Though, I don't see why you need init(_ other: Self) to be a requirement in this case, how about just making it an extension member, with func copy() -> Self being the customisation point?

For example:

public protocol Copyable {
  func copy() -> Self
}

extension Copyable {
  public init(_ other: Self) {
    self = other.copy()
  }
}

Now just get conforming types to implement func copy() -> Self and not only do they get init(_ other: Self) for free, but Copyable is now usable as a type.

I'm not entirely sure what your end-goal is with this API, but it's worth noting that there's a reason why the standard library doesn't come with a "default initialisable" protocol (which appears to be what DefaultValueProvider is trying to achieve) – that reason being that protocols should come with semantic requirements in addition to 'member requirements' (what you actually state in the protocol body) that enable you to write useful generic algorithms with them. Check out Ole Begemann's great blog post on this subject.

5 Likes

OK Hamish, it's that Homer Simpson moment - Dooohhh!!!

And all it took was to "reverse" the protocol :blush:

Talk about not seeing the wood for the trees; you'd have thought that, after more than 25 years of consulting and designing for others, I missed that?

DefaultValueProvider is there because the Property class requires that its value is initialised to a default value. This, of course, means extending any "standard" type, including Int, String, Double, Optional, etc to comply but that's not exactly rocket science :wink:

And valueT is optionally Copyable in the case of properties that hold a value with reference semantics.

I shall find a darkened room and peruse the reading you have linked to.

Thank you so much :smiley:

1 Like

A protocol with Self requirements can only be used as an existential type if replacing Self with the protocol produces something that's type safe. So in your case, when you call copy() on a value of Copyable type, it erases Self to Copyable. This is type safe because the conforming type is always a subtype of Copyable.

However, if we did the same transformation to init(_:), it would not be type safe, because it's not safe to take an arbitrary Copyable value and pass it as Self.

The general concept is called covariance and contravariance. The copy() method has Self in covariant position, and init(_:) has Self in contravariant position. Only covariant Self requirements can be used with existential types.

6 Likes

Hi Slava

Yes, I know all about variance, both co and contra :wink:

We went through hell getting a grip on it around the time of C# 2, or should I say the then lack of it but, at least, Microsoft dealt with it and it seems to be better integrated.

I still think there is a major problem with "generic" protocols, which gives the lie to the statement found in the book "The Swift Programming Language"

Protocols as Types

Protocols don’t actually implement any functionality themselves. Nonetheless, any protocol you create will become a fully-fledged type for use in your code.

Because it’s a type, you can use a protocol in many places where other types are allowed, including:

  • As a parameter type or return type in a function, method, or initializer
  • As the type of a constant, variable, or property
  • As the type of items in an array, dictionary, or other container

Note the wording any protocol you create will become a fully-fledged type for use in your code

This is simply not true. As soon as you violate the rules for Self or declare an associated type, you then have to waste hours writing boilerplate code for type erasure or finding another workaround.

In C#, protocols are simple to define and use:

interface IProperty
{
  string name { get; set; }
}

IProperty<valueT> : IProperty
{
  valueT value { get; set; }
}

IProperty can be used as the "type eraser" and there are no limitations on the parameter types that you can declare for the generic version. And there is no need to create yet another name like BaseProperty because, in C#, a generic type can have the same name as a non-generic type; they are regarded as two distinct types.

No, I am not saying that Swift should blindly copy C# but, at times, it does seem that Swift is trying to be different for the sake of being different, which is causing a lot of highly experienced developers to have to throw away years of coding experience and start again, just to satisfy a "fashion"

This looks like an oversight in the documentation. Do you mind filing a bug?

I agree it is frustrating to run into these limitations. Generalized existentials will address these problems but this is a complex feature requiring design and implementation. I'm hopeful it will happen eventually.

Hi Slava

Can do. Where should I do this?

I hope eventually doesn't mean much longer. I, along with many others are having to waste an awful lot of time overcoming limitations instead of just getting on with our real work.

Swift might appear fine for the average code monkey, churning out quick and dirty apps for iOS but, for those of us who are into creating frameworks and such, it is just painful. One of the reasons I've never really been keen on open source projects is the inordinate amount of time it seems to take to 1. make decisions, 2. implement them, especially if nobody is being paid :wink:

That’s a pretty high horse you’re sitting on! I’m sure you mean no harm, but telling people that you are “wasting an awful lot of time” becase of them is not really the pinnacle of empathy. Let’s be nice, please.

My apologies, no harm intended. It's just that I do tend to get paid for real working code, not to have to find workarounds or to participate in open source work. I can't really charge a client for research time and, working long days to make the time for that research is something that does have a cost that the client can never be aware of.

I'm part of the team that maintains that document. Can you file a bug on https://bugreport.apple.com and post the bug number here? Thanks!

1 Like

Hi Alex

Bug filed as : 43009032 :grinning:

Thank you!

Open Source or not, also C# and other languages were once upon a time version 1.0. And at that time carried a lot of limitations and issues that frustrated developers. Swift is still relatively young language and patience is required until it matures and fleshes out all the more complicated features.

I wonder if you are just trying to copy in verbatim a convention from another language with your Copyable, without really making a proper assessment on whether it makes sense at all in Swift... For example, since Int is a value type, not reference type, you don't need to "copy()" it. Passing it around always makes a copy, and implementing it yourself can cause much worse performance.

For example, just to match the API of your app you could do

extension Int {
   func copy() -> Int { return self } 
}

Hi Mox

The problem I find is that Swift is already at version 4.2 and still doesn't have the functionality that C# had at version 2.0 :face_with_raised_eyebrow:

Copyable is not intended for all types, only those which are reference types. See the conditional code I use to copy the "value" of a property; it's only reference types that implement Copyable, all other value types do not need to.

extension Property : Copyable
{
  public func copy() -> Self
  {
    let result = type(of: self).init()
    
    if let value = value as? Copyable
    {
      result.value = value.copy() as! valueT
    }
    else
    {
      result.value = value
    }
    
    return result
  }
}

If it's only for reference types then you should probably define it as:

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

That way the compiler knows it's a reference type and doesn't have to do any extra work to refcount it. Plus, it keeps one from accidentally conforming a value type to the protocol.

Unfortunately, if I do that, I get an error on the default copy initialiser in the extension

public protocol Copyable : AnyObject
{
  func copy() -> Self
}


extension Copyable
{
  public init(other: Self)
  {
    self = other.copy() // error: Cannot assign to value: 'self' is immutable
  }
}

Never mind, everything working fine as it is without the AnyObject :yum: