(type: T.Type) makes generic method work whereas without it T cannot be inferred - swift

I'm using a simple method to run a fetch request on my Core Data DB. I'm housing most of my code within one protocol that will have default implementations that certain things will extend. My code is as follows:
protocol Modelable {
func testFetch<T>(type: T.Type) -> [T]?
}
extension Modelable {
func testFetch<T>(type: T.Type) -> [T]? {
let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "Data"),
result = try? context.fetch(fetchRequest)
return result as? [T]
}
}
Then I simply call it on a struct that conforms to Modelable
let modelableExtended = ModelableExtended()
print(modelableExtended.testFetch(type: SomeType.self)) // Prints
What's weird about this code is that if I remove the type: T.Type from the method definition and where I call it, I'll get the following error:
Generic parameter 'T' could not be inferred
How exactly does passing a type into this method make it so it works? Isn't that just a parameter on the method itself, passing or not shouldn't change anything.

If you don’t want to pass in the type, you can do something like this instead
let modelableExtended = ModelableExtended()
let result: [SomeType]? = modelableExtended.testFetch()
print(result)
At some point you have to say what T is by either passing in some parameter that refers to it or by assigning the result.
Generics are a compile-time feature. Perhaps you are thinking that they are like Any or AnyObject where you never have to say the type and then you can find out at runtime what you actually have. This isn’t how generics work.
A generic let’s you write code that can work with a lot of different types, but the code that actually runs is using a specific type that was inferred from its context. If it cannot be inferred, then the code will not compile and never be run.

Related

protocol annotation in var declaration breaks code

I am trying to call the method .enumerate() on an instance of a type which conforms to the protocol Sequence. According to Apple's documentation, this should be correct, since .enumerate is part of the Sequence protocol.
However, I receive this complaint from the compiler:
Member 'enumerated' cannot be used on value of type 'any Sequence<URL>'; consider using a generic constraint instead.
Yet, if I remove the type annotation, then it works.
Here is an example which reproduces the problem:
func example() -> URL? {
let fm : FileManager = FileManager.default
let appDir : FileManager.SearchPathDirectory = FileManager.SearchPathDirectory.applicationDirectory
let domMask : FileManager.SearchPathDomainMask = FileManager.SearchPathDomainMask.allDomainsMask
let appResourceValues : [URLResourceKey] = [URLResourceKey.localizedNameKey]
var appURLs : any Sequence<URL> = fm.urls(for: appDir, in: domMask)
//var appURLs : Sequence<URL> = fm.urls(for: appDir, in: domMask)
//var appURLs = fm.urls(for: appDir, in: domMask)
var appURL : URL? = appURLs.enumerated().first { (offset: Int, element: URL) in
try! element.resourceValues(forKeys: Set(appResourceValues)).localizedName!.contains("App Store")
}?.element
return appURL
}
There are two commented lines in the code above, which are alternate ways to instantiate appURLs. If I use the first commented line, which is the old Swift syntax apparently, then I receive an error telling me that in order to add a type annotation which enforces a protocol, I need to use any protocolName, and not protocolName. (According to a comment on another post, this was a recent change in Swift: Use of protocol 'YourProtocol' as a type must be written 'any YourProtocol' Error, https://github.com/apple/swift-evolution/blob/main/proposals/0335-existential-any.md)
If I use the second commented line, which removes the protocol annotation altogether, then the code works.
Is this a bug in Swift? How can I apply an annotation to indicate that it must conform to Sequence<URL> without breaking the code?
I tried to declare a generic type parameter, but Swift won't let me. None of these work:
associatedtype does exactly what I want: it creates a generic type parameter. But it doesn't work outside a protocol.
If you annotate appURLs with the existential type any Sequence<URL>, then that means that you don't know what concrete type it actually stores. This is problematic for calling enumerated, because enumerated returns EnumeratedSequence<Self>:
func enumerated() -> EnumeratedSequence<Self>
Self means "type on which this is called" - the exact thing that you don't know. Sometimes, having an unknown Self is fine. e.g. if methods with these signatures existed in Sequence:
func f() -> Self
func g() -> (Self, Int)
func h(p: (Self) -> Void)
func i() -> [Self]
func j() -> [Int: Self]
func k() -> Self?
All of these are covariant positions. It remains type safe to substitute Self in these positions with any Sequence<URL>, which is why you can still call these methods. However, it is not safe to do the same with EnumeratedSequence<Self>, because even though SomeConcreteImplementationOfSequence is a any Sequence<URL>, EnumeratedSequence<SomeConcreteImplementationOfSequence> is not a EnumeratedSequence<any Sequence<URL>>. Generics are invariant in Swift - the Self in EnumeratedSequence<Self> is in an invariant position.
You can see they talk about when functions involving Self can be called in this SE proposal:
[...] but references to Self-rooted associated types will for the
same reasons some Self references do today. As alluded to back in
Inconsistent Language Semantics, references to covariant Self
are already getting automatically replaced with the base object type,
permitting usage of Self-returning methods on existential values [...]
This way, a protocol or protocol extension member
(method/property/subscript/initializer) may be used on an existential
value unless:
The type of the invoked member (accessor — for storage declarations),
as viewed in context of the base type, contains references to Self
or Self-rooted associated types in non-covariant position. [...]
They even use enumerated() as an example further down!
extension Sequence {
public func enumerated() -> EnumeratedSequence<Self> {
return EnumeratedSequence(_base: self)
}
}
func printEnumerated(s: Sequence) {
// error: member 'enumerated' cannot be used on value of type protocol type 'Sequence'
// because it references 'Self' in invariant position; use a conformance constraint
// instead. [fix-it: printEnumerated(s: Sequence) -> printEnumerated<S: Sequence>(s: S)]
for (index, element) in s.enumerated() {
print("\(index) : \(element)")
}
}
Besides, EnumeratedSequence<any Sequence<URL>> isn't even a valid type! EnumeratedSequence requires its type parameter to be a Sequence, but any Sequence<URL> isn't one! Because Sequence has static requirements.
Responding to your comments,
It is bad practice to type hint something as [URL] when you only intend to use the qualities encapsulated by Sequence protocol
That is not bad practice. Rather, putting type annotations where they are not needed is considered not Swifty.
Example: I may want to use a method which compiles a enumerable list of URLs, but the way that these URLs are fetched will depend on runtime parameters (e.g., do I have internet access? Is an external drive currently mounted?). Depending on these parameters, it may be more efficient (or only be possible) to acquire the list of URLs as [URL] or as any other type which conforms to Sequence<URL>. In that case, the return type of such a function will be anything which conforms to Sequence<URL>
In that case, your function can return an AnySequence<URL>. Unlike any Sequence<URL>, this is a concrete type. You just need to do the extra step of wrapping other sequence types to it:
func fetchSomeURLs() -> AnySequence<URL> {
if someCondition {
return AnySequence([url1, url2]) // [URL]
} else {
return AnySequence(someOtherImplementationOfSequence)
}
}
I have concluded that the Swift compiler isn't sophisticated enough to check conformity of a variable to a protocol. (There are some limited cases where it will work.)
A work-around in this case is the following:
extension Sequence {
func enumerated_custom() -> any Sequence<(offset:Int, element:Iterator.Element)> {
var index : Int = -1
return self.lazy.map({ (el:Iterator.Element) in
index = index+1 ; return (index+1, el)
})
}
}
Then, we can do appURLs.enumerated_custom() to get a Sequence<(Int, URL)> which mimics the behaviour of appURLs.enumerated(). I can then use the annotation appURLs : Sequence<URL> in order to check for conformity, and the code does not break.
Related informational links:
https://belkadan.com/blog/2021/10/Swift-Regret-Generic-Parameters-are-not-Members/
https://github.com/apple/swift/pull/39492
https://github.com/apple/swift-evolution/blob/main/proposals/0309-unlock-existential-types-for-all-protocols.md
https://github.com/apple/swift/pull/41131

How can I use a protocol's type in method signature?

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.

Get element.type from generic object in swift

I want to write an generic method to read all items from a type in realm and delete them. I wrote this:
func deleteAllFromObject<T: Object>(_ object: T) {
let allUploadingObjects = realm.objects(T.self())
try! realm.write {
realm.delete(allUploadingObjects)
}
}
but i got an error that says:
Cannot convert value of type 'T' to expected argument type 'Object.Type'.
How can i do that?
The immediate issue is that you need to pass a Metatype to realm.objects and not an instance, so you shouldn't put the parentheses behind T.self, since the parentheses is the shorthand for init(), so instead of passing in the type you're passing in an instance of T.
However, you should also change the function signature to func deleteAllFromObject<T: Object>(_ object: T.Type), since you want to be able to call the function with an Object type and not an instance of an Object subclass.
func deleteAllFromObject<T: Object>(_ object: T.Type) {
let allUploadingObjects = realm.objects(T.self)
try! realm.write {
realm.delete(allUploadingObjects)
}
}
And then call the function like deleteAllFromObject(Dog.self) instead of deleteAllFromObject(Dog()).
Maybe problem in line
let allUploadingObjects = realm.objects(T.self())
It should be:
let allUploadingObjects = realm.objects(T.self) // removed needles parentheses

Is it possible to have an array of instances which take a generic parameter without knowing (or caring) what the parameter is?

Consider the following test case, which contains a 'factory' class which is able to call a closure it contains, providing a new instance of some 'defaultable' type:
protocol Defaultable {
init()
}
extension Int: Defaultable { }
extension Double: Defaultable { }
extension String: Defaultable { }
class Factory<T : Defaultable> {
let resultHandler: (T) -> ()
init(resultHandler: (T) -> ()) {
self.resultHandler = resultHandler
}
func callResultHandler() {
resultHandler(T.init())
}
}
Now, this works well when I use it on its own, where I can keep track of the generic type:
// Create Int factory variant...
let integerFactory = Factory(resultHandler: { (i: Int) in print("The default integer is \(i)") })
// Call factory variant...
integerFactory.callResultHandler()
Unfortunately, it doesn't work so well if I want to use factories in a way where I can't keep track of the generic type:
// Create a queue of factories of some unknown generic type...
var factoryQueue = [Factory]()
// Add factories to the queue...
factoryQueue.append(integerFactory)
factoryQueue.append(doubleFactory)
factoryQueue.append(stringFactory)
// Call the handler for each factory...
for factory in factoryQueue {
factory.callResultHandler()
}
I understand the error I get (Generic parameter 'T' could not be inferred), but I don't understand why I can't do this, because when I interact with the array, I don't need to know what the generic parameter is (I don't interact with any of the generic things in the Factory instance). Is there any way I can achieve the above?
Note that the above is a simplified example of what I'm trying to do; in actuality I'm designing a download manager where it can infer what type of file I want (JSON, image, etc.) using generics; the protocol actually contains an init(data:) throws initialiser instead. I want to be able to add the download objects to a queue, but I can't think of any way of adding them to a queue because of the generic nature of the download objects.
The problem is that Swift's strict type safety means you cannot mix two instances of the same class with different generic parameters. They are effectively seen as completely different types.
However in your case, all you're doing is passing a closure to a Factory instance that takes a T input, and then invoking it at any given time with T.init(). Therefore, you can create a closed system in order to contain the type of T, meaning that you don't actually need your generic parameter to be at the scope of your class. You can instead restrict it to just the scope of the initialiser.
You can do this by defining your resultHandler as a Void->Void closure, and create it by wrapping the passed closure in the initialiser with another closure – and then passing in T.init() into the closure provided (ensuring a new instance is created on each invocation).
Now whenever you call your resultHandler, it will create a new instance of the type you define in the closure that you pass in – and pass that instance to the closure.
This doesn't break Swift's type safety rules, as the result of T.init() is still known due to the explicit typing in the closure you pass. This new instance is then being passed into your closure that has a matching input type. Also, because you never pass the result of T.init() to the outside world, you never have to expose the type in your Factory class definition.
As your Factory class itself no longer has a generic parameter, you can mix different instances of it together freely.
For example:
class Factory {
let resultHandler: () -> ()
init<T:Defaultable>(resultHandler: (T) -> ()) {
self.resultHandler = {
resultHandler(T.init())
}
}
func callResultHandler() {
resultHandler()
}
}
// Create Int factory variant...
let integerFactory = Factory(resultHandler: { (i: Int) in debugPrint(i) })
// Create String factory variant...
let stringFactory = Factory(resultHandler: { (i: String) in debugPrint(i) })
// Create a queue of factories of some unknown generic type...
var factoryQueue = [Factory]()
// Add factories to the queue...
factoryQueue.append(integerFactory)
factoryQueue.append(stringFactory)
// Call the handler for each factory...
for factory in factoryQueue {
factory.callResultHandler()
}
// prints:
// 0
// ""
In order to adapt this to take an NSData input, you can simply modify the resultHandler closure & callResultHandler() function to take an NSData input. You then just have to modify the wrapped closure in your initialiser to use your init(data:) throws initialiser, and convert the result to an optional or do your own error handling to deal with the fact that it can throw.
For example:
class Factory {
let resultHandler: (NSData) -> ()
init<T:Defaultable>(resultHandler: (T?) -> ()) {
self.resultHandler = {data in
resultHandler(try? T.init(data:data)) // do custom error handling here if you wish
}
}
func callResultHandler(data:NSData) {
resultHandler(data)
}
}
I recently came back to needing a better answer for this question—as I was performing some refactoring—and thought that it would be really useful to have generic properties of the class, which of course would mean that the class itself would have to be generic as well.
I'm not sure why it didn't occur to me before, but I can simply create a protocol which mirrors the non-generic methods of the class. Using the example I originally had in my question, I could create a FactoryProtocol like so:
protocol FactoryProtocol {
func callResultHandler()
}
Make the class conform to it:
class Factory<T : Defaultable>: FactoryProtocol
And then use the protocol rather than the class when I define my array:
var factoryQueue = [FactoryProtocol]()
This allows me to add any type of specialised Factory to the array and interact with the non-generic methods as I please.
I am afraid this is not possible. The reason for this is that Swift doesn't have first class metatypes. I can imagine all sorts of Monads and Functors being built if this was possible. Unfortunately, this is a limitation. Welcome to Swift.
The golden rule is that in Swift, you cannot nail a type down to a protocol. Swift needs a concrete type.
Check this article out for more details around the subject.

Array of generics with metatypes (using AlamofireObjectMapper)

I suspect I may be making the same mistake as described by Rob in this post here in that I should be doing this whole thing another way, but with that in mind:
I'm trying to use AlamofireObjectMapper in a generic way. It has a protocol
public protocol Mappable
I then have various model classes that adopt it
class Dog: Mappable
class Cat: Mappable
class Bird: Mappable
I have this method
func loadEntityArray<T: Mappable>(type: T.Type)
and the reason this is generic is because it calls a function load() that needs a completion block that uses this generic param. The 'type' argument is never actually used, but you can't make a func generic without the generic type being in the func's parameter list.
func load(completion:(Response<T, NSError> -> Void))
loadEntityArray is called from another method
func letsgo() { loadEntityArray(Dog.self); loadEntityArray(Cat.self) }
So far so good, this all works. But I want to pass an array of which models to load to letsgo() and I can't work out how to do this. If I change letsgo() to
func letsgo<T:Mappable>(models: [T.Type]) {
for mod in models {
loadEntityArray(mod)
}
}
and then call letsgo() with 1 param like
letsgo([Dog.self])
it works, but as soon as I have an array of 2 or more, I get a compiler error 'cannot convert value of type NSArray to expected argument type [_.Type]' I don't now how I would explicitly type this array either.
letsgo([Dog.self, Cat.self])
I've tried various permutations and nothing seems to work. Am I doing something impossible here? It seems to me the compiler has enough information at compile time for this to work, so I'm not sure if this is a syntax thing or I'm doing something wrong here with generics.
Looking at your function :
func letsgo<T:Mappable>(models: [T.Type])
Its model parameter should be an Array of all the same Type. So an array of only Dog Types for example. That's why
letsgo([Dog.self])
works but
letsgo([Dog.self, Cat.self])
won't as it has multiple Types.
The solution :
Use Mappable.Type directly :
func loadEntityArray(type: Mappable.Type) {}
func letsgo(models: [Mappable.Type]) {
for mod in models {
loadEntityArray(mod.self)
}
}
And cast your array of Types as an array of Mappable Types :
letsgo([Dog.self, Cat.self] as [Mappable.Type])
Hope this achieves what you're looking for !
So in the end I came to the conclusion that this is not possible. I changed the code such that I pass in an enum to letsgo() and in a switch statement on that enum I call loadEntityArray(Dog.self) etc. etc. with an explicitly coded call, for each of my possible types. Then the compiler can see all the possible types and is happy.