I'm am trying to implement a protocol method that has a generic argument, but then use the generic type for my entire class instead of just on the method, something like this
protocol FirstProtocol {
}
protocol SecondProtocol {
func foo<T: FirstProtocol>(argument: T)
}
class MyType<T: FirstProtocol>: SecondProtocol {
var value: T? = nil
func foo<T>(argument: T) {
value = argument // ERROR: Cannot assign value of type 'T' to type 'T?'
}
}
So the swift compiler accepts that foo<T>(argument:T) matches the method of SecondProtocol, if I comment out the error line it compiles fine, but it will not let me assign argument to value even though value and argument should be the same type, the compiler complains as if they are different types.
The type of argument and value are indeed different types. The T generic parameter in foo is just an identifier, and I can change it to anything else:
class MyType<T: FirstProtocol>: SecondProtocol {
var value: T? = nil
func foo<AnythingElse>(argument: AnythingElse) {
// MyType still conforms to SecondProtocol
}
}
The T in foo is a brand new generic parameter, different from the T in MyType. They just so happens to have the same name.
Note that when you declare a generic method, it's the caller that decides what the generic type is, not the generic method. What foo is trying to say here is "I want the T in foo to be the same type as the T in MyType", but it can't say that about its own generic parameters!
One way to fix it is to make SecondProtocol have an associated type:
protocol SecondProtocol {
// name this properly!
associatedtype SomeType: FirstProtocol
func foo(argument: SomeType)
}
class MyType<T: FirstProtocol>: SecondProtocol {
typealias SomeType = T // here is where it says "I want 'SomeType' to be the same type as 'T'!"
var value: T? = nil
func foo(argument: T) {
value = argument
}
}
it will not let me assign argument to value even though value and argument should be the same type, the compiler complains as if they are different types.
think about this case:
class A: FirstProtocol {
}
class B: FirstProtocol {
}
class A and B is the acceptable generic type for func foo(argument: T){}, but can you assign an instance of class A to class B?
class MyType<T: FirstProtocol>: SecondProtocol
remove ": FirstProtocol"should work, or use a base class to replace FirstProtocol
Related
I have broken down a more complex matter to this class. This way it doesn’t make sense, but it is easier to talk about:
class GenericClass<Type: Any> {
var object: Type?
enum DataType {
case string, integer
}
init(dataType: DataType) {
switch dataType {
case .string:
object = "string" // Cannot assign value of type 'String' to type 'Type'
case .integer:
object = 1 // Cannot assign value of type 'Int' to type 'Type'
default:
object = nil
}
}
}
How can I make this initializer infer the type Type when there is no reference in the function signature?
I asked a related question before (probably with to much cluttered detail): Make Generic Class Codable
Not sure if this answers your question but this is how you are supposed to create an initializer for a generic structure/class without a specific type in the function signature (generic initializer):
class GenericClass<T> {
let object: T
init(_ object: T) {
self.object = object
}
}
let stringClass = GenericClass("string")
print(stringClass.object)
let intClass = GenericClass(1)
print(intClass.object)
This will print
string1
The actual goal/benefit of generics is to get rid of type checks at runtime.
Referring and in addition to Leo's answer if you want to constrain the types to String and Int do it at compile time
protocol GenericClassCompatible {}
extension String : GenericClassCompatible {}
extension Int : GenericClassCompatible {}
class GenericClass<T : GenericClassCompatible> {
let object: T
init(_ object: T) {
self.object = object
}
}
If the type in the init method is not String or Int you'll get an error at compile time.
I'm not sure what where clause can restrict a generic parameter to be a protocol that inherits from a certain protocol.
protocol Edible {}
protocol PetFood: Edible {}
struct CatFood: PetFood {}
struct Rocks {}
func eat<T: Edible>(_ item: T) -> String {
return "Just ate some \(type(of: item))"
}
let food: CatFood = CatFood()
eat(food) //"Just ate some CatFood"
let moreFood: PetFood = CatFood()
//eat(moreFood) //Cannot invoke 'eat' with an argument list of type '(PetFood)'
func eatAnything<T>(_ item: T) -> String {
return "Just ate some \(type(of: item))"
}
eatAnything(moreFood) //This works, obviously
eatAnything(Rocks()) //But, of course, so does this...
Is there any way to restrict eatAnything() to allow protocol types, but only those that inherit from Edible?
In your example, the definition of a generic function does not make any sense because it can be replaced by:
func eat(_ item: Edible) -> String {
return "Just ate some \(type(of: item))"
}
But if you really want to use generic function then you should know:
Definition of generic function
func eat<T: Edible>(_ item: T) -> String { ... }
func eat<T>(_ item: T) -> String where T: Edible { ... }
func eat<T: Edible>(_ item: T) -> String where T: Equatable { ... }
Protocols are dynamic types, so they use late binding. Generic code is converted to normal during compilation and requires early binding
Early Binding (compile time): type is known before the variable is exercised during run-time, usually through a static, declarative means
Late Binding (runtime): type is unknown until the variable is exercised during run-time; usually through assignment but there are other means to coerce a type; dynamically typed languages call this an underlying feature
Generic functions can be defined with the type to be protocol-compatible, but this functions can't pass the this protocol as type, because the compiler doesn't know what that type is T. Passed to generic function type must be a specific type (class, struct, enum, ...)
let a: [Int] = [1,2,3]
let b: [CustomStringConvertible] = [1, "XYZ"]
a.index(of: 2) // 1
b.index(of: "XYZ") // error
I have protocol and his implementation written in Swift:
protocol P {
}
struct A: P {
}
Protocol is used as generic type for some function:
func foo<T: P>(param: T) {
}
func foo() {
foo(param: A())
}
Until now everything works properly. But I would like to set A() as a default parameter of given function:
func foo<T: P>(param: T = A()) {
}
Unfortunately with following error:
Default argument value of type 'A' cannot be converted to type 'T'.
Or
func foo<T: P>(param: T = A() as P) {
}
,
let a: P = A()
func foo<T: P>(param: T = a) {
}
Returns:
Default argument value of type 'P' cannot be converted to type 'T'
Or
func foo<T: P>(param: T = A() as T) {
}
Returns:
'A' is not convertible to 'T'; did you mean to use 'as!' to force downcast?
What I'm doing wrong? Where is the problem?
I do not want to use force cast like this:
func foo<T: P>(param: T = A() as! T) {
}
Thank you in advance.
You're trying to enforce a non-generic default argument in a generic function: you should probably think over what you're trying to achieve here.
For the sake of the discussion, you could include an attempted cast of A() to T in your function signature, but you'd need to change the argument type to optional to allow failed conversion (nil), e.g.
func foo<T: P>(param: T? = (A() as? T)) { }
A more sound alternative is including - in addition to your generic function - a concrete non-generic function for instances where T is A (concrete functions will take precedence over generic ones), in which case you can include the default argument of A() in the function signature of the concrete function. E.g.
protocol P { }
struct A: P { }
extension Int: P { }
func foo<T: P>(param: T) { print("called generic foo") }
func foo(param: A = A()) { print("called A specific foo") }
foo() // called A specific foo (making use of default arg)
foo(A()) // called A specific foo
foo(1) // called generic foo
Note that the non-generic foo is called even though A conforms to P (A could've made use of the generic foo): there's no conflict here as the concrete function takes precedence.
If you, on the other hand, just want your generic function to allow calling without the single argument (i.e., making use of a default argument), you can include a blueprint of a simple initializer in P, allowing you to initialize an instance of the generic type as default argument; see #Sulthan:s answer.
The only thing you need is to add a requirement for an initializer to the protocol:
protocol P {
init()
}
struct A: P {
var x: Int
init() {
x = 10
}
}
func foo<T: P>(param param: T = T()) {
}
However, you will have another problem. The type of the passed parameter decides the type of the generic so you will have to specify the generic type somehow else.
I can’t figure out how to perform a cast which would let me eventually to introduce some sort of dynamism working with generics.
Next piece of code does not compile. It shows this error:
Cannot invoke 'createContainer' with an argument list of type
'(FooProtocol)' Expected an argument list of type '(T)'
protocol FooProtocol {
func doSomething()
}
class Foo : FooProtocol {
func doSomething() {}
}
class Container<T : FooProtocol> {
let someDataConformingFooProtocol : T
init(someDataConformingFooProtocol : T) {
self.someDataConformingFooProtocol = someDataConformingFooProtocol
}
}
class AllTogether {
init () {
createContainer(Foo()) //So far, so good
let foo2Mask : AnyObject = Foo()
if let foo2MaskChecked = foo2Mask as? FooProtocol {
createContainer(foo2MaskChecked)
//ERROR: Cannot invoke 'createContainer' with an argument list of type '(FooProtocol)'
//Expected an argument list of type '(T)'
}
}
func createContainer<T : FooProtocol>(data: T){
Container<T>(someDataConformingFooProtocol: data)
}
}
Is this really the expected behaviour? Because personally I can’t understand what or why the compiler is complaining about it.
What would be the appropriate cast? Without referencing to the concrete class, I mean NOT like this:
if let foo2MaskChecked = foo2Mask as? Foo
Thanks!
What it comes down to is the difference between T: FooProtocol and FooProtocol as #Hamish mentioned in his comment.
When I have a function like this:
func foo(_ i: FooProtocol)
I'm taking in an instance of type FooProtocol, so I can pass in a value that conforms to FooProtocol or its type is FooProtocol. This is similar to subclassing where you can pass both SuperClass and SubClass into SuperClass.
But if you have a function like this:
func foo<T>(_ i: T) where T: FooProtocol
I can pass in any type that conforms to FooProtocol, but because FootProtocol does not conform to itself, you can't pass that in.
So in your question, you are trying to pass in a type FooProtocol where the types must conform to FooProtocol. You could probably fix this by doing:
class Container {
let someDataConformingFooProtocol: FooProtocol
init(someDataConformingFooProtocol: FooProtocol) {
self.someDataConformingFooProtocol = someDataConformingFooProtocol
}
}
// Later
func createContainer(data: FooProtocol){
Container(someDataConformingFooProtocol: data)
}
I have a generic class that can be initialised as any type. I would like to add a function with a single parameter that takes a value that is both of the class's generic type and conforms to the Comparable protocol. Type conformance should be enforced pre-compile.
I would like to do something like this:
class Object<T> {
let value: T!
init (value: T) {
self.value = value
}
func doSomething<U where U: Comparable, U == T>(otherValue: U) {
// do something
}
}
Is this possible to do?
Unfortunately, no. You can't further specialize a generic type in a method - you'd need to add a top-level function for the behavior you want.
This is the reason Array doesn't have a pure myArray.sort() function, since there's no way to guarantee that the members of any Array instance will be Comparable. Instead, there's a top-level function with this signature:
func sort<T : Comparable>(inout array: [T])
Your top-level function would have a similar structure:
func doSomething<T: Comparable)(obj: Object<T>, otherValue: T) {
// ...
}
For reference, this is possible since Swift 2.0:
extension Object where T : Comparable {
func doSomething(otherValue: T) {
// do something
}
}