Swift: Generic's type protocol not being recognized - swift

Long time listener, first time caller.
I'm getting the following error:
Cannot convert value of type MyClass<Model<A>, OtherClass> to expected argument type MyClass<Protocol, OtherClass>
Despite the fact that MyClass<T> conforms to Protocol
I've attached a snippet that can be run in Playgrounds that resembles what I am actually trying to achieve.
protocol DisplayProtocol {
var value: String { get }
}
class DataBundle<T: CustomStringConvertible>: DisplayProtocol {
var data: T
var value: String {
return data.description
}
init(data: T) {
self.data = data
}
}
class Mapper<DisplayProtocol, Data> {
// ...
}
class MapperViewModel<Data> {
let mapper: Mapper<DisplayProtocol, Data>
init(mapper: Mapper<DisplayProtocol, Data>) {
self.mapper = mapper
}
}
let dataBundle = DataBundle<Int>(data: 100)
let mapper = Mapper<DataBundle<Int>, Bool>()
let viewModel = MapperViewModel<Bool>(mapper: mapper) // <- This fails w/error
Is this the expected behavior? If it is it feels like its breaking the contract of allowing me to have the DisplayProtocol as a type in Mapper.

This is caused by the fact that Swift generics are invariant in respect to their arguments. Thus MyClass<B> is not compatible with MyClass<A> even if B is compatible with A (subclass, protocol conformance, etc). So yes, unfortunately the behaviour is the expected one.
In your particular case, if you want to keep the current architecture, you might need to use protocols with associated types and type erasers.

Related

Swift: Same-Type requirement makes generic parameters equivalent?

I'm using swift 5 and try to compile the following code:
protocol BasicProtocol {
associatedtype T
var str: T {get set}
}
struct AItem<U>: BasicProtocol {
typealias T = U
var str: T
init<G: StringProtocol>(str: G) where G == T {
self.str = str
}
}
I got compilation error:
error: Test.playground:10:45: error: same-type requirement makes generic parameters 'G' and 'U' equivalent
init<G: StringProtocol>(str: G) where G == T {
^
How to make them equivalent? or I can't?
Thanks.
Update 1:
This is the problem I encountered: I want to declare struct "AItem", hoping it has a generic type "T". And this generic type will have some restrictions, such as: "T: StringProtocol". Then for some reason, I need to use an array to load these structs, and ensure that the generics of each structure can be set at will.
I learned that there is "type-erase" might can solve this. So I tried this way, but it seemed unsuccessful. The problems mentioned above have occurred.
Update 2:
struct AItem<T: StringProtocol> {
var aStr: T
}
var array: [AItem<Any>] = [AItem(aStr: "asdfasdf")]
Look,If you compile this code, you will get a compilation error:
error: Test.playground:5:13: error: type 'Any' does not conform to protocol 'StringProtocol'
var array: [AItem<Any>] = [AItem(aStr: "asdfasdf")]
^
If I use "var array: [AItem<String>]", I will not be able to put any other non-"String" but implemented "StringProtocol" instance in the array.
This is why I said I want "ensure that the generics of each structure can be set at will".
Update 3:
very thanks for #jweightman, now I update my question again.
protocol ConstraintProtocol {}
extension String: ConstraintProtocol{}
extension Data: ConstraintProtocol{}
extension Int: ConstraintProtocol{}
.......
struct AItem<T = which class has Implemented "ConstraintProtocol"> {
var aPara: T
init(aPara:T) {
self.aPara = aPara
}
}
// make a array to contain them
var anArray: [AItem<Any class which Implemented "ConstraintProtocol">] = [AItem(aPara: "String"), AItem(aPara: 1234), AItem(aPara: Data("a path")), …]
// then I can use any item which in anArray. Maybe I will implement a method to judge these generics and perform the appropriate action.
for curItem in anArray {
var result = handleItem(curItem)
do something...
}
func handleItem<T: ConstraintProtocol>(item: AItem<T>) -> Any? {
if (item.T is ...) {
do someThing
return ......
} else if (item.T is ...) {
do someThing
return ...
}
return nil
}
This is my whole idea, but all of which are pseudo-code.
It seems like type erasure is the answer to your problem. The key idea to the type erasure pattern is to put your strongly typed but incompatible data (like an AItem<String> and an AItem<Data>) inside of another data structure which stores them with "less precise" types (usually Any).
A major drawback of type erasure is that you're discarding type information—if you need to recover it later on to figure out what you need to do with each element in your array, you'll need to try to cast your data to each possible type, which can be messy and brittle. For this reason, I've generally tried to avoid it where possible.
Anyways, here's an example of type erasure based on your pseudo code:
protocol ConstraintProtocol {}
extension String: ConstraintProtocol{}
extension Data: ConstraintProtocol{}
extension Int: ConstraintProtocol{}
struct AItem<T: ConstraintProtocol> {
var aPara: T
init(aPara: T) {
self.aPara = aPara
}
}
struct AnyAItem {
// By construction, this is always some kind of AItem. The loss of type
// safety here is one of the costs of the type erasure pattern.
let wrapped: Any
// Note: all the constructors always initialize `wrapped` to an `AItem`.
// Since the member variable is constant, our program is "type correct"
// even though type erasure isn't "type safe."
init<T: ConstraintProtocol>(_ wrapped: AItem<T>) {
self.wrapped = wrapped
}
init<T: ConstraintProtocol>(aPara: T) {
self.wrapped = AItem(aPara: aPara);
}
// Think about why AnyAItem cannot expose any properties of `wrapped`...
}
var anArray: [AnyAItem] = [
AnyAItem(aPara: "String"),
AnyAItem(aPara: 1234),
AnyAItem(aPara: "a path".data(using: .utf8)!)
]
for curItem in anArray {
let result = handleItem(item: curItem)
print("result = \(result)")
}
// Note that this function is no longer generic. If you want to try to "recover"
// the type information you erased, you will have to do that somewhere. It's up
// to you where you want to do this.
func handleItem(item: AnyAItem) -> String {
if (item.wrapped is AItem<String>) {
return "String"
} else if (item.wrapped is AItem<Data>) {
return "Data"
} else if (item.wrapped is AItem<Int>) {
return "Int"
}
return "unknown"
}
An alternative to type erasure you could consider, which works well if there's a small, finite set of concrete types your generic could take on, would be to use an enum with associated values to define a "sum type". This might not be a good choice if the protocol you're interested in is from a library that you can't change. In practice, the sum type might look like this:
enum AItem {
case string(String)
case data(Data)
case int(Int)
}
var anArray: [AItem] = [
.string("String"),
.int(1234),
.data("a path".data(using: .utf8)!)
]
for curItem in anArray {
let result = handleItem(item: curItem)
print("result = \(result)")
}
func handleItem(item: AItem) -> String {
// Note that no casting is required, and we don't need an unknown case
// because we know all types that might occur at compile time!
switch item {
case .string: return "String"
case .data: return "Data"
case .int: return "Int"
}
}

Cannot use protocol to define common initializers

I'm trying to create a generic class that is able to convert models to their associated view models. Unfortunately the compiler does not allow it and fails with the following error:
Cannot convert value of type 'Model' (generic parameter of generic class 'Store') to expected argument type 'ViewModel.Model' (associated type of protocol 'StoreViewModel')
I'm using the following simplified code:
protocol StoreViewModel {
associatedtype Model
init(model: Model)
}
class Store<Model, ViewModel: StoreViewModel> {
var models = [Model]()
var results = [ViewModel]()
func update() {
results = models.map {
ViewModel(model: $0)
// Cannot convert value of type 'Model' (generic parameter of generic class 'Store') to expected argument type 'ViewModel.Model' (associated type of protocol 'StoreViewModel')
}
}
}
class Foo: NSManagedObject {}
class FooViewModel: StoreViewModel {
var model: Foo
required init(model: Foo) {
self.model = model
}
}
let store = Store<Foo, FooViewModel>()
I've already read about type erasure, but I'm wondering of there is no simpler solution for this problem.
As it is, your current code allows things like:
let store = Store<Int, FooViewModel>()
Clearly that makes no sense, but your code allows it because there is no constraint on Model. Model can be anything, can't it?
Let's put a constraint on:
class Store<Model, ViewModel: StoreViewModel> where Model == ViewModel.Model {
Now we see that there is no point of having two generic parameters! Why use Model, when we can just use ViewModel.Model?
class Store<ViewModel: StoreViewModel> {
var models = [ViewModel.Model]()
or, if you hate long type names, use a typealias:
typealias Model = ViewModel.Model
var models = [Model]()

Swift generics init: Non-nominal type 'T' does not support explicit initialization

I'm using Realm in my iOS app and because of the thread safety issues I have opted to create a realm layer for my data model, as well as a layer that can be converted to and from the realm objects.
There is a lot more complexity to what I'm actually doing, but I was able to create a playground that demonstrates the unexpected issues I'm running into.
Basically I created a protocol called RealmObject. In my actual app this is a protocol which Realm.Object types can conform to which requires all the necessary properties.
I'm just including the UUID part of this protocol in the playground.
//the realm layer
protocol RealmObject {
var uuid: String { get }
}
Next I have my Realm layer model. They all have UUID, but have some other properties that correspond to their non-Realm relatives.
struct NoteObject: RealmObject {
let uuid: String
let noteText: String
}
struct AppointmentObject: RealmObject {
let uuid: String
let time: Date
}
Cacheable is the protocol that types which can be saved in realm must conform to.
For now I've just included the UUID requirement, and the requirement to be initialized from a RealmObject
//Cacheable
protocol Cacheable {
associatedtype T: RealmObject
init(object: T)
}
struct Note: Cacheable {
let noteText: String
let uuid: String
init(object: NoteObject) {
self.uuid = object.uuid
self.noteText = object.noteText
}
}
struct Appointment: Cacheable {
let uuid: String
let time: Date
init(object: AppointmentObject) {
self.uuid = object.uuid
self.time = object.time
}
}
Finally the issue. In my app this function does a lot more than this, but this is as simple as I could make it and still demonstrate the issue.
Basically, T is the generic type which must be a Cacheable object.
U is the RealmObject I want to convert to a cacheable object.
Every Cacheable object has an initializer that accepts an object which is a RealmObject
But it fails
func getCacheable<T: Cacheable, U: RealmObject>(from realmObject: U) -> T {
let thing = T(object: realmObject)
ERROR: Non-nominal type 'T' does not support explicit initialization
return thing
}
let noteObject = NoteObject(uuid: "bobalobla", noteText: "hi how are you")
let note: Note = getCacheable(from: noteObject)
let appointmentObject = AppointmentObject(uuid: "bobloblaw", time: Date())
let appointment: Appointment = getCacheable(from: appointmentObject)
I don't see anything that is so ambiguous the compiler shouldn't be able to easily figure it out
Replacing the generics with the types should be simple
Once the function knows which Cacheable type its working with, the initializer should be easily route to the correct init method. I won't pretend to understand whats actually happening, but this seems like a pretty basic thing to do with generics so I assume I must be doing something wrong. What is going on?
The type system doesn't know, from your list of generics <T: Cacheable, U: RealmObject>, that U is the correct type of RealmObject for T (in other words, you could be passing a Note as T, and an AppointmentObject as U). You just need to update your function signature to:
// The `where T.T == U` is the important part. Because that is defined,
// we can also remove `U: RealmObject` because it's redundant information.
func getCacheable<T: Cacheable, U>(from realmObject: U) -> T where T.T == U {
let thing = T(object: realmObject)
return thing
}
// Alternatively, you can just do:
func getCacheable<T: Cacheable>(from realmObject: T.T) -> T {
let thing = T(object: realmObject)
return thing
}

Generic constrained type default value

Consider the following code:
protocol JSONParserType {
associatedtype Element
}
// MARK: - Entities
struct Item {}
// MARK: - Parsers
struct OuterParser<T: JSONParserType where T.Element == Item>: JSONParserType {
typealias Element = Item
let innerParser: T
init(innerParser: T = InnerParser()) {
self.innerParser = innerParser
}
}
struct InnerParser: JSONParserType {
typealias Element = Item
}
The OuterParser has a child parser that should be constrained to a specific type. Unfortunately providing a default value in the initializer (or in the property definition itself) does lead to the compiler throwing a "Default argument value of type 'InnerParser' cannot be converted to type 'T'".
If I remove the default value assignment and just instantiate the OuterParser providing the InnerParser explicitly, everything is fine.
let outerParser = OuterParser(innerParser: InnerParser())
My question is what's the reason that the approach providing a default value that actually meets the constraints does not work.
The problem is that the actual type of T isn't defined by the class – it's defined by the code that uses the class. It will therefore be defined before you do anything in your class (at either instance or static level). You therefore can't assign InnerParser to T, as T has already been defined to be a given type by that point, which may well not be InnerParser.
For example, let's consider that you have another parser struct:
struct AnotherParser: JSONParserType {
typealias Element = Item
}
and let's assume that your current code compiles. Now consider what would happen when you do this:
let parser = OuterParser<AnotherParser>()
You've defined the generic type to be AnotherParser – but the initialiser will try to assign InnerParser to your property (now of type AnotherParser). These types don't match, therefore it cannot possibly work.
Following the same logic, this implementation also won't work:
struct OuterParser<T: JSONParserType where T.Element == Item>: JSONParserType {
typealias Element = Item
let innerParser: T
init() {
self.innerParser = InnerParser()
}
init(innerParser: T) {
self.innerParser = innerParser
}
}
As there's no guarantee that the generic type T will be the same type as InnerParser. Sure, you can force downcast to T – but that'll just make you code crash if the types aren't compatible.
Unfortunately, there's no real clean solution to this problem. I think the best your best option is probably to create two factory methods for creating your OuterParser instance.
enum Parser {
static func createParser() -> OuterParser<InnerParser> {
return OuterParser(innerParser:InnerParser())
}
static func createParser<T>(innerParser:T) -> OuterParser<T> {
return OuterParser(innerParser:innerParser)
}
}
let innerParser = Parser.createParser() // OuterParser<InnerParser>
let anotherParser = Parser.createParser(AnotherParser()) // OuterParser<AnotherParser>
We're using an caseless enum here to avoid polluting the global namespace with extra functions.
Although this isn't very Swifty, and for that reason I would also recommend maybe rethinking your logic for how you define your parsers.
type T more like a child protocol of JSONParserType you can convert it:
init(innerParser: T = InnerParser() as! T) {
self.innerParser = innerParser
}

How to specify multiple inter-dependent associated types in swift protocols?

How do I specify multiple associated types in a protocol with added dependencies on their inner associated types? For example:
protocol CombinedType
{
typealias First: FirstType
typealias Second: SecondType
var first: First { get }
var second: Second { get }
}
protocol FirstType
{
typealias Value
var value: Value { get }
}
protocol SecondType
{
type alias Value
var value: Value { get }
}
I need to specify a limitation on the CombinedType protocol that would make it so FirstType.Value == SecondType.Value much like in a where clause in generics.
Without that limitation, the code:
func sum<Combined: CombinedType>(combined: Combined) -> Combined.FirstType.Value
{
return combined.first + combined.second
}
produces an error Combined.FirstType.Value is not compatible with Combined.SecondType.Value.
But if I explicitly add the where clause to the function, it compiles:
func sum<Combined: CombinedType where Combined.FirstType.Value == Combined.SecondType.Value>(combined: Combined) -> Combined.FirstType.Value
{
return combined.first + combined.second
}
The problem is that this where needs to be copied everywhere, which produces a ton of boilerplate and it gets even worse when using CombinedType inside another protocol, in which case the where clause gets larger and larger.
My question is: can I somehow include that limitation in the protocol itself?