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?
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”.
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 {}
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 MyProtocolvalue 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 :)
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 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".
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.
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.
I understand your point, and I agree with the argument that a protocol is "just" a contract. However, you could have the same argumentation that you have for generic about the whole concept of protocol. when you type a variable with a protocol, you define a contract and the compiler accept it and verify that nothing infringe upon it.
The question is (and remains) why suddenly in generics the compiler is not able to enforce this contract and validate that the part are indeed respected?
Maybe it's me not seeing the problem here, also since protocol are supposed to be first class citizens in swift, they should be incorporated in generic as well.
protocol Protocol: Super { }
class Super { }
class Sub: Super, Protocol { }
//class Independent: Protocol { }
func moo<T: Protocol>(_ m: T) {
print(type(of: m))
}
moo(Sub())
The commented line does not work, because Independent is not a subclass of Super.
If we remove the protocol constraint:
class Independent { }
And try to invoke moo() with
moo(Independent())
We get the expected error
Argument type 'Independent' does not conform to expected type 'Protocol'
What we see is that the compiler is enforcing that T is of type Super or one of its subclasses.
Your code doesn't work for the reason already given up-thread, but you can get what you want by asking the compiler in the correct way -- by constraining T to a protocol that must be implemented by SKNode or its subclasses.
What you don't get is the compiler understanding that the value is an SKNode, so you can't invoke methods or properties of SKNode without casting.