I seem to be running into what appears to be a compiler inconsistency when passing arguments to a generic function that has a protocol restriction. I can pass a concrete argument, but not an argument as a protocol type
protocol Selectable {
func select()
}
protocol Log : Selectable {
func write()
}
class DefaultLog : Log {
func select() {
print("selecting")
}
func write() {
print("writing")
}
}
let concrete = DefaultLog()
let proto: Log = DefaultLog()
func myfunc<T: Selectable>(arg: T) {
arg.select()
}
myfunc(concrete) // <-- This works
myfunc(proto) // <-- This causes a compiler error
proto.write() // <-- This works fine
The compiler reports:
error: cannot invoke 'myfunc' with an argument list of type '(Log)'
myfunc(proto)
^
note: expected an argument list of type '(T)'
myfunc(proto)
^
If I restrict the function to the Selectable or Log protocol it still fails.
Is this a compiler bug? Any thoughts?
If you are using a protocol, it doesn't need to be generic:
func myfunc(arg: Selectable) {
arg.select()
}
I believe that T needs to be a concrete type for generics.
Related
I am trying to constraint a protocol extension to a generic class. My goal is to provide a default implementation of a protocol where Self is any instance that is subclass of some generic class. Consider the below example:
protocol Printable {
var value: String { get }
}
class Printer<P: Printable> {
let printable: P
init(printable: P) {
self.printable = printable
}
}
protocol Press {
func print()
}
// error: reference to generic type 'Printer' requires arguments in <...>
extension Press where Self: Printer {
func print() {
// Do Something with self.printable.value
}
}
The compiler gives error error: reference to generic type 'Printer' requires arguments in <...>.
I don't understand why this should not be allowed. As long as Press is some kind of Printer which always works with some kind of Printable things should work, right? Or I am missing something?
Can you point what could be the right way of achieving something like this?
This is because Printer<A> and Printer<B> are different types, even A & B are Printable, so due to possible ambiguity compiler generate error.
You need the following (tested with Xcode 11.4)
extension Press {
func print<P>() where Self: Printer<P>, P: Printable {
// Do Something with self.printable.value
}
}
I have a function like this:
// A generic function that can fetch values from the store given a type
// Corresponds to a table and it's rows
func fetch<T: FetchableRecord>(for theType: T.Type) -> [T] {
// ... fetch from a store using the type
// This compiles fine
}
How can I use this with a collection of types?
Ideally, if I have some conformers:
struct ModelA: FetchableRecord { }
struct ModelB: FetchableRecord { }
then I would like to be able to do:
let modelTypes: [FetchableRecord.Type] = [ModelA.self, ModelB.self]
modelTypes.forEach {
fetch(for: $0) // xxx: does not compile: "Cannot invoke 'fetch' with an argument list of type '(for: FetchableRecord.Type)'"
}
At the very least, I would like to figure why this would not be possible.
Thank you.
The reason for the error is FetchableRecord.Type is not the same as ModelA.Type or ModelB.Type. Even if both of the structs conform to FetchableRecord protocol, constraining the models (by conforming to a certain protocol) does not affect the "Types", more technically speaking:
ModelA.self == FetchableRecord.self OR ModelB.self == FetchableRecord.self is false.
In order to resolve this issue, you could implement the method's signiture as:
func fetch(for theType: FetchableRecord.Type) -> [FetchableRecord] {
// ...
}
therefore, your code:
let modelTypes: [FetchableRecord.Type] = [ModelA.self, ModelB.self]
modelTypes.forEach {
fetch(for: $0)
}
should work. At this point, you are dealing with it "Heterogeneously" instead of "Homogeneously".
Furthermore, if that makes it more sensible, note that when calling fetch method as per your implementation, it is:
the parameter type is FetchableRecord.Protocol. However, as per the above-mentioned implementation (in this answer), it is:
the parameter type is FetchableRecord.Type, which is the wanted result.
I have a class that takes a generic parameter T class CacheRepository<T> {}that contains a func transform(). In which I need to call another function with the following signature func transformCodable<U: Codable>(ofType: U.Type)-- if T conforms to Codable protocol. But I failed to do so. Below are my trials with their compilation errors.
Trial 1:
func transform() {
if T.self is Codable.Type {
// Error: In argument type 'CacheRepository<T>.T.Type' (aka 'T.Type'), 'T' does not conform to expected type 'Decodable'
sharedStorage!.transformCodable(ofType: T.self)
}
}
Trial 2:
func transform() {
if T.self is Codable.Type {
//Cannot invoke 'transformCodable' with an argument list of type '(ofType: (Codable))'
//Expected an argument list of type '(ofType: U.Type)'
sharedStorage!.transformCodable(ofType: (T as! Codable).self)
}
}
Trial 3: I tried to extend CacheRepository class if T is Codable, but the extension transform function never gets called, when called in the init() function. And if I called it after the instance is created, it doesn't get called in all cases.
class CacheRepository<T> {
func transform() {
print("non-codable transform")
}
}
extension CacheRepository where T: Codable {
func transform() {
sharedStorage!.transformCodable(ofType: T.self)
}
}
example: This works
let transform = CacheRepository<Int>().transform()
But if I added a shared instance inside the CacheRepository class, and tried to call transform after instantiation, it doesn't get called. And the really odd this is, if I called the tranform on the new instance in console the right transform is called.
How is this?
extension CacheRepository where T: Codable {
func transform() {
print("Codable")
sharedStorage!.transformCodable(ofType: T.self)
}
}
extension CacheRepository {
func transform() {
print("Non-codable")
}
}
This worked as expected in a very simplified CacheRepository. But not sure if it works in your actual CacheRepository, please try.
I have a protocol like this:
protocol Moveable {
var moveSpeed: Float { get set }
}
And in one of my classes that don't conform to "Moveable" I have a function like this:
var moveables: [Moveables]()
func checkMoveable(mov: Moveable) -> Bool {
for m in moveables {
if m === mov { // <- Error
return true
}
}
return false
}
I get this error:
Binary operator '===' cannot be applied to two 'Moveable' operands
also tried with "==" instead of "==="
And in another function that relies on the previous one:
func removeMoveable(mov: Moveable) {
if checkMoveable(mov) {
self.moveables = moveables.filter({$0 !== mov})// <- Error
}
}
I get this error:
Cannot convert value of type 'Moveable' to expected argument type
'AnyObject?'
If "Moveable" was a class instead of being a protocol none of these errors would appear.
The identity operator === applies only to reference types (classes). Your protocol can be adopted by either reference or value types, so something whose only type information is that protocol can't be used with the identity operator.
If you want your protocol to be adopted only by classes, declare it thusly:
protocol Movable: class { //...
Or make it extend a protocol that's already restricted to classes:
protocol Movable: NSObjectProtocol { //...
The equivalence operator == applies only to types that are Equatable. If you want to use that operator on two Movables, you'll need to declare that for a type to be Movable it must also conform to Equatable:
protocol Movable: Equatable { //...
Since it seems you're looking for a particular SKSpriteNode instance, the first (making === work) is probably what you want.
Let's say I have this code:
func hello<T>(thing: T) -> String {
return "hello \(thing)"
}
Can I write a version of the hello function that won't compile if it's passed an optional?
let foo = "some"
let bar: String? = nil
print(helloNoOptional(foo)) // should compile
print(helloNoOptional(bar)) // should not compile
I'm thinking maybe it's doable with a protocol conformance or where clause on T but I can't think of how exactly that would work.
The reason I want to do this is because I'm dealing with a actual function in legacy codebase that has no sensible behavior if thing is nil. So I'd rather prevent hello from being called on an optional rather than deal with unwrapping thing inside of hello and trying to figure out a sensible error behavior.
Update:
A possible path...I realized that the Optional enum conforms to the NilLiteralConvertible protocol. So if I can find a way to constrain my generic to not conform to a type, I can exclude optionals de facto. But I don't know if it's possible to do something like
<T where !T: NilLiteralConvertible>
Best I can think of is overload and check at runtime:
func hello<T>(thing: T) -> String {
return "hello \(thing)"
}
fun hello<T>(thing: T?) -> String {
fatalError("No optionals allowed!")
}
hello("swift") // fine
hello(2) // fine
hello(Int("2")) // fatal error
But I don't know of a way of generating a compile-time error instead.
Edited
You can create a dummy protocol (NotOfOptionalType below) and extend all types you expect to use in your generic functions by this protocol. Finally use the dummy protocol as a type constraint for the parameter(s) in your generic functions; optionals does not meet this type constraint and you'll be given an error at compile time if they are sent as parameters for these functions.
// dummy protocol
protocol NotOfOptionalType {}
extension String : NotOfOptionalType {}
extension Int : NotOfOptionalType {}
extension Double : NotOfOptionalType {}
// ... extend to the types you will use
func hello<T: NotOfOptionalType > (thing: T) -> String {
return "hello \(thing)"
}
let foo = "some"
var bar: String? = nil
print(hello(foo)) // compiles
print(hello(bar)) // fails at compile time
bar = "something"
print(hello(bar)) // fails at compile time
print(hello(bar!)) // compiles
Building on #Airspeed Velocity's answer, I personally prefer to keep everything in one place, like so...
func hello<T>(_ thing: T) -> String {
if T.self is ExpressibleByNilLiteral.Type {
fatalError("Optional types not supported")
}
return "hello \(thing)"
}