Problem implementing generic method from protocol

In an attempt to circumvent the dreaded Self error when implementing a protocol, instead of using:

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

… which causes the error, I have had to do:

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

… but this involves having to cast the input parameter to the implementing type every time.

So, I thought I would try a generic method definition:

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

But, as of now, I don't seem to be able write the implementation of the init(:_) without the same need to cast from the input parameter to the implementing type thus:

  required convenience init<typeT>(other: typeT)
  {
    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 would be logical is if I could write a non-generic implementation that should satisfy the protocol's generic method definition.

Obviously, this is not yet possible in Swift but, logically, this should work:

class Property<valueT : DefaultValueProvider & Equatable> : Copyable
{
  var value = valueT()
  
  init() { }
  
  required convenience init(other: Property)
  {
    self.init()
    
    if let copyableValue = other.value as? Copyable
    {
      value = copyableValue.copy() as! valueT
    }
    else
    {
      value = other.value
    }
  }
}

I am porting my C# MVP framework to Swift and, still, I am finding it takes an awful lot of effort and ingenuity to circumvent a lack of language features that I have been used to for many years.

I don't really get what the issue is - I am probably missing something though.

Why can't you do something like:

protocol Copyable {
    init(other: Self)
}
extension Copyable {
    var copy: Self {
        return Self.init(other: self)
    }
}

class Property<T>: Copyable {
    var value: T
    
    init(_ value: T) {
        self.value = value
    }
    
    required init(other: Property<T>) {
        value = other.value
    }
}

let p = Property(0)
let p0Copy = p.copy
p.value = 1
p // 1
p0Copy // 0
1 Like

The problem is when you do things like:

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

let p: PropertyProtocol = Property(0)

if let c = p as? Copyable // error : Protocol 'Copyable' can only be used as a generic constraint because it has Self or associated type requirements
{
  …
}

And, since I need to hold a list of properties, of different value types, I need to use something like PropertyProtocol as a non-generic type eraser. In this case, I have surfaced a copy() method on the protocol but, if I didn't want to do that and, if I wanted to try to cast any type that implements Copyable to Copyable, I get the error.

There are other situations that don't "like" that way of declaring Copyable and Hamish Knight came up with why in this post Inconsistent protocol Self problem - #5 by Joanna_Carter

You can do what you want, but it is a bit of boiler plate unfortunately.

This is very different than C#/Java/Scala/Kotlin whose interfaces are a lot easier to use than Swift's protocols. When you declare a generic type in Swift, e.g. class MainTypeName<GenericTypeName>, you have two type names the main type name and the generic type name. To allow an implementation of a protocol, with Self or an associated type, to be stored in a general structure you have to erase the main type name and make the generic type name, instead of Self or the associated type, explicit. This is easier than it sounds, its just boiler plate. By convention this boiler plate structs/classes are called Any..., you will see plenty of them in the standard library.

Anyway, your example with type erasure:

protocol NotCopyable: CustomStringConvertible {
    associatedtype ValueT
    var value: ValueT { get set }
}
extension NotCopyable {
    var description: String { // You don't get a human-readable description for classes :(.
        return "\(value)"
    }
}
class AnyNotCopyable<T>: NotCopyable {
    let valueGet: () -> T
    let valueSet: (T) -> Void
    init(get: @escaping () -> T, set: @escaping (T) -> Void) {
        valueGet = get
        valueSet = set
    }
    convenience init<N>(_ notCopyable: N) where N: NotCopyable, N.ValueT == T {
        var n = notCopyable
        self.init(get: { n.value }, set: { newValue in n.value = newValue })
    }
    var value: T {
        get {
            return valueGet()
        }
        set {
            valueSet(newValue)
        }
    }
}

protocol Copyable: NotCopyable {
    init(other: Self)
}
extension Copyable {
    var copy: Self {
        return Self.init(other: self)
    }
}
class AnyCopyable<T>: AnyNotCopyable<T>, Copyable {
    let copyClosure: () -> AnyCopyable<T>
    init(get: @escaping () -> T, set: @escaping (T) -> Void, copy: @escaping () -> AnyCopyable<T>) {
        copyClosure = copy
        super.init(get: get, set: set)
    }
    required convenience init(other: AnyCopyable<T>) {
        let c = other.copyClosure()
        self.init(get: c.valueGet, set: c.valueSet, copy: c.copyClosure)
    }
    convenience init<C>(_ copyable: C) where C: Copyable, C.ValueT == T {
        var c = copyable
        self.init(get: { c.value }, set: { newValue in c.value = newValue }, copy: { AnyCopyable(c.copy) })
    }
    var copy: AnyCopyable<T> {
        return AnyCopyable(copyClosure())
    }
}

class Property<T>: Copyable {
    var value: T
    init(_ value: T) {
        self.value = value
    }
    required init(other: Property<T>) {
        value = other.value
    }
}

let p: AnyNotCopyable = AnyCopyable(Property(0))
if let c = p as? AnyCopyable {
    let p0Copy = c.copy
    p.value = 1
    p // 1
    p0Copy // 0
}

One further gotcha is that if you come from the Java world; type erasure will mean erasing the generic type and the compiler will do it for you. In Swift it is the opposite, type erasure means erasing the main type, not the generic, and you have to do it manually.

The problem with doing type erasure like that is that AnyCopyable can only ever be used with one type of T in situations like a list.

By writing the protocol with the copy() method in the protocol body and the init(_:) in the extension, there is nothing to stop you doing:

  let copies = [Copyable]()

Placing the init(_:) in the protocol itself raises the "can't use protocol because of Self, etc" error

If I do ever need to do type erasure on a generic type, I generally take the easy root on starting with a non-generic type with generic methods:

public protocol PropertyProtocol
{
  func getValue<valueType : DefaultValueType>() throws -> valueType
  
  func setValue<valueType : DefaultValueType>(_ value: valueType) throws
  
  func copy() -> PropertyProtocol
}


public class Property<valueT : DefaultValueType>
{
  public var value = valueT()

  public required init(value: valueT = valueT()) { }
}


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
  }
}


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

I couldn't get your code to compile!

Could you post a working version?

Perhaps with a working version of your example also, something like:

let p2: PropertyProtocol2 = Property2(value: 2)
if let p = p2 as? Copyable2 {
    let p2Copy = p.copy() as Property2
    p.value = 3
    p
    p2Copy
}

Note: I suffixed everything including types with 2 to distinguish your version from mine - that's where the '2's come from in the above.

Note 2: At first glance I thought you had just manually implemented Any and required casting everywhere, though I could be wrong because I couldn't get the code to work.

Note 3: The nearest I could get is:

public protocol DefaultValueType2 {
    init()
}

public protocol Copyable2 {
    func copy() -> Copyable2
}

public protocol PropertyProtocol2 {
    func getValue<ValueType : DefaultValueType2>() throws -> ValueType
    
    func setValue<ValueType : DefaultValueType2>(_ value: ValueType) throws
    
    func copy() -> PropertyProtocol2
}

public class Property2<ValueT : DefaultValueType2> {
    public var value : ValueT
    
    public required init(value: ValueT = ValueT()) {
        self.value = value
    }
}

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

extension Property2 : PropertyProtocol2 {
    public enum Error : Swift.Error {
        case invalidPropertyType
    }
    
    public func getValue<ValueType>() throws -> ValueType where ValueType : DefaultValueType2 {
        guard let value = value as? ValueType else {
            throw Error.invalidPropertyType
        }
        
        return value
    }
    
    public func setValue<ValueType>(_ value: ValueType) throws where ValueType : DefaultValueType2 {
        guard let newValue = value as? ValueT else {
            throw Error.invalidPropertyType
        }
        
        self.value = newValue
    }
    
    public func copy() -> PropertyProtocol2 {
        return Property2(value: self.value)
    }
}

extension Int: DefaultValueType2 {}
let p2: PropertyProtocol2 = Property2(value: 2)
if let p = p2 as? Copyable2 {
    let p2Copy = p.copy() as Property2
    p.value = 3
    p
    p2Copy
}