Protocol extending class and generic

Hi there, I stumbled upon some weird restriction with protocols and generics.

I can create a protocol that "extends" a class (and i'm not meaning the opposite) like that :

public protocol MyProtocol:SKNode
{
    var myProperty:String{get set}
}

I can, with this "conformance" (i'm not quite sure it's that here) I can now create a class that implement this protocol and requires to extend the class SKNode like this :

public class MyClass:SKNode, MyProtocol
{
    public var myProperty: String = "my property!"
}

Now, if i create a generic class that encapsulate MyClass I end up with :

public class MyGenericClass<T>
{
    public var myObj:T
    init(myObj:T)
    {
		self.myObj = myObj
    }
}

If i use this generic like that :

let t = MyGenericClass<MyProtocol>(myObj: MyClass())
print(t.myObj.myProperty)
print(t.myObj.position)

Everything works fine.
Now, If i add a constraint on the generic to be sure that T is a SKNode :

public class MyGenericClass<T:SKNode> //Only ":SKNode" has been added. 
{
	public var myObj:T
 	init(myObj:T)
	{
		self.myObj = myObj
	}
}    

the previous code doesn't compile with this error :

'MyGenericClass' requires that 'MyProtocol' inherit from 'SKNode'

It looks like a bug to me, or am I missing something?
Because MyProtocol does extends SKNode, isn't it?

The generic T needs be a concrete type to match the <T: SKNode> requirement.
MyProtocol is a protocol but not a subclass of SKNode class.

Use your MyClass to fix the issue.

let t = MyGenericClass<MyClass>(myObj: MyClass())

Thank you for your answer. Let me rephrase my question differently.

A Type can be :

  • a Class
  • an Enum
  • a Struct
  • a Protocol

The syntax Type1:Type2 can mean different things depending on the context.

If Type1 is a class and Type2 is a class :
Type1:Type2 means class1 extends class2

If Type1 is a class and Type2 is a Protocol :
Type1:Type2 means class1 implement protocol2

If Type1 is a protocol and Type2 is a protocol
Type1:Type2 means Protocol1 extends Protocol2

If Type1 is a protocol and Type2 is a class,
Type1:Type2 means The class that implement Protocol1 should extend Class2

The problem is that in generic Generic<T> got all the definitions of a type but Generic<Type1:Type2> doesn't.

The question is why is that and should it does?

From my understanding,
When you use MyProtocol as generic T parameter.
MyProtocol is an existential type according to the definition I found in the official documentation.

Protocols don’t actually implement any functionality themselves. 
Nonetheless, you can use protocols as a fully fledged types in your code. 
Using a protocol as a type is sometimes called an  *existential type* , 
which comes from the phrase “there exists a type  *T*  such that  *T* conforms to the protocol”.

from Protocols as Types

But the problem is that existential type is not an actual subclass of SKNode.
So the compiler complains about it

'MyGenericClass' requires that 'MyProtocol' inherit from 'SKNode'

Actually, I'm not sure my explanation is 100% correct. So please feel free to correct me if I mistaken something. =)

When you add the T: SKNode constraint, you require that T is a subclass of SKNode (because SKNode is a class). In MyGenericClass<MyProtocol>, MyProtocol is not a valid substitution for T because a protocol is not a class and therefore cannot possibly satisfy the constraint (be a subclass of SKNode).

protocol MyProtocol: SKNode is actually not an inheritance clause, but shorthand syntax for a constraint that says "anything that implements MyProtocol must be a subclass of SKNode".
In other words, "only SKNode or types that extend SKNode are allowed to implement MyProtocol".

protocol MyProtocol: SKNode {}

// Shorthand for and equivalent to

// Self (the implementer) must be a subclass of SKNode.
protocol MyProtocol where Self: SKNode {}
4 Likes

Which is exactly what I stated here :

I'll try to rephrase my question because no-one seems to understand what i'm trying to convey :

Since Protocol:SKNode is a constraint on the class that implement it and garanties that the actual implementation extends SKNode ; Why can't we have the generic with the same contract working?

I understand perfectly what's happening currently in the generic, however, it doesn't seems exhaustive to me .

And I think that Generic<Protocol:Class>, when protocol is defined like here, should work as well, because the generic and the protocol have the same contract :

"The object stored/passed is an instance of an actual class that extends the class required" (in this case SKNode)

Also I don't see any restriction that would prevent theoretically this to work and to be implemented, so if you see any i would be glad to read them.

Generics by concept are only about types. So, Protocol: SKNode means conformers (types, not values) of Protocol must inherit from SKNode, correct? If you replace T with MyProtocol, T: SKNode is not satisfied simply because MyProtocol as a type is not a subclass of SKNode, even though only SKNode subclasses can implement it. The dynamic type of some MyProtocol value is not taken into account, because generics are an abstraction at the type level. Only the fact that the replacement type itself does not satisfy the constraint matters.

The same thing does work with values:

class Super {}

protocol P: Super {}

class Sub: Super, P {}

func foo(arg: Super) {}

func test(proto: P) {
  // Passing in a value of type P is OK because the compiler knows
  // its dynamic type inherits from Super.
  foo(arg: proto)
}

P.S. Excuse the excessive editing, I am not quite a native speaker and I tend to find better ways of expressing myself after the fact :)

Any type that conforms to the protocol will also be a subclass of the class used as a constraint.

However, a protocol used as a type (an existential type) does not—and cannot—conform to the protocol itself.

You can search these forums for explanations as to why protocols do not conform to themselves.

No worry for the editing, it's the same for man :slight_smile:

MyProtocol as a type is not a subclass of SKNode

Yes of course, And i'm not arguing with that. I'm arguing with what is the current implementation of the generic and why the other definition of Type:Type are not implemented as well.

Knowing that generics are not dynamic they are all compiled (at compile time of course) into a concrete implementation where T is replaced with the type passed and every different call to Generic with a different T ends up into a different concrete implementation in the compiled code.

Therefore :

Generic<T>{
    var v:T
}

Generic<Protocol>()
Generic<Class>()

will be compiled into :

Generic_protocol{ //it's not 100% like that but that for the idea
    var v:Protocol
}
Generic_class{
    var v:Class
}
generic_protocol()
generic_class()

Hence the compiler could totally check for the contract of the protocol to verify that it satisfies the contract of the generic and if yes continue to compile since we know the value that will be passed into the compiled class will have the right requirement. And the question is : Why is that not the case?

Now, I think our diverging point of view can be summarised like that :
You explain to me what is the current implementation and how it works. (which I know)
I'm asking you why this implementation is restricted to a certain interpretation of Type:Type.
Hence our misunderstanding.

Yes.

Yes as well. But I don't see how this is relevant here. I'm not asking the protocol to conform to itself. I'm asking the compiler to understand that "Protocol:Class" in a generic means "the protocol needs to have the same constraint than the generic and if yes there are compatible" instead of "Type:Class means Type extends Class".

PS : I noted that I wrote something wrong on my post and that might be what send you of the rail.

What I meant, and should have written is :

Now, If i add a constraint on the generic to be sure that either T is a SKNode or T as a constraint on SKNode :

Swift generics are actually not compiled to concrete implementations in general. Specialization can sometimes be internally requested or selectively decided upon by the optimizer.

You are misunderstanding Swift generics because you are trying to involve values in to what is a type-level abstraction. In Swift, generics do not make assumptions based on values by design.
The contract is exactly the type T must be a subclass of SKNode, not the actual type of some value passed as T must be a subclass of SKNode (note how values get involved). The type that fully satisfies the latter contract is SKNode, without any generics – if you want value-level polymorphism, you should use protocols and classes directly as I demonstrated earlier.

Often times the best way to understand these concepts is by looking into solutions, so feel free to share the task at hand.

2 Likes

I'm unsure what the technical definition is, but my understanding a protocol is not a full Type. Thinking about them as one may be where your hangup/frustration lies.

Why isn't it a Type? Because in Swift it's (currently) only a contract. It's an ephemeral concept used to abstract functionality/definitions to multiple Types. At runtime it has no storage, and really no actual value once compiled.

Theoretically Swift could be compiled in a way that completely strips out any information about protocols even existing. (I'm sure that's not how it works in practice, but I think the point is still valid).

So with your specific use case:

  • Your Generic is looking for a concrete object. e.g. a DogGroomer is asking for a protocol Dog to groom.
  • The DogGroomer doesn't care what kind of Dog, but it has to be a real Dog, e.g. DogGroomer<Poodle>, DogGroomer<Beagle>.
  • DogGroomer<Dog> is analogous to giving the DogGroomer the concept or idea of a Dog. A dog groomer can't groom a concept, so it refuses your request.

In writing let t = MyGenericClass<MyProtocol>(myObj: MyClass()), you are using a concrete type MyProtocol. This concrete type is an existential type; it conforms to no protocols, not even MyProtocol, and it is not a subclass of SKNode because it is not a class.

2 Likes

For a more concrete reason why the code shouldn't work, imagine if SKNode had a class function

class func randomFunction() -> Int

And MyGenericClass used this function:

static var thing: Int { return T.randomFunction() }

What should MyGenericClass<MyProtocol>.thing be?

Terms of Service

Privacy Policy

Cookie Policy