Swift Generics: How to avoid this strange behaviour? - swift

I met with the strange behaviour. Maybe i'm doing it wrong but I have not a clue.
Error: Cannot assign value of type '[Itemable]' to type '[T]'
protocol Itemable {
var id: Int { get set }
}
protocol Paginatable {
var items: [Itemable] { get set }
}
class Fetcher<T: Itemable, P: Paginatable> {
var items = [T]()
func onReceive(pagination: P) {
items = pagination.items
}
}

Class Fetcher expects a T type, i.e. a specific type conforming to Itemable.
So, for example, if you had a type Foo: Itemable, Fetcher<Foo, ...> would expect to work with Foos - not any Itemable, like AnotherFoo: Itemable - but only Foo.
Yet Ps don't have such as restriction on their items property - both Foo and AnotherFoo and something else could be in items.
So, you're basically trying to do the following:
let items: [Itemable] = [...]
let foos: [Foo] = items // error
If you want to constrain P to hold the same items as T, then you could do the following:
protocol Itemable {
var id: Int { get set }
}
protocol Paginatable {
associatedtype Item: Itemable
var items: [Item] { get set }
}
class Fetcher<T, P: Paginatable> where P.Item == T {
var items = [T]()
func onReceive(pagination: P) {
items = pagination.items
}
}

You need Paginatable to be a generic. Then everything falls into place. You do not want it to declare an array of Itemable but an array of some Itemable adopter. That is exactly what a generic lets you say.
In other words:
protocol Paginatable {
var items: [Itemable] { get set }
}
...means: "Whoever adopts me (I'm looking at you, Fetcher), must declare that items is [Itemable]." That is not what you want Fetcher to have to say. But you are forcing Fetcher to say that.
Instead, say this:
protocol Paginatable {
associatedtype T : Itemable
var items: [T] { get set }
}
Now you are saying: "Whoever adopts me can declare items as an array of any type as long as that type adopts Itemable." That is exactly what you do want to say.
To illustrate further, this is illegal:
protocol Itemable { }
protocol Paginatable {
var items: [Itemable] { get set }
}
struct Dummy : Itemable {}
struct Fetcher : Paginatable { // error
var items: [Dummy]
}
But this is fine:
protocol Itemable { }
protocol Paginatable {
associatedtype T : Itemable
var items: [T] { get set }
}
struct Dummy : Itemable {}
struct Fetcher : Paginatable {
var items: [Dummy]
}
And that second thing is exactly what you want to say. Now that you've understood that, if you then want to make Fetcher a generic too, no problem:
protocol Itemable { }
protocol Paginatable {
associatedtype T : Itemable
var items: [T] { get set }
}
struct Dummy : Itemable {}
struct Fetcher <T:Itemable> : Paginatable {
var items: [T]
}

Related

Using swift protocols as Equatable argument

For example we have two simple protocols:
protocol Container {
var items: [Item] {get}
}
protocol Item {
var name: String {get}
}
And we want to use a function that requires Item to be Equatable, like:
extension Container {
func indexOfItem(_ item: Item) -> Int? {
items.firstIndex(of: item)
}
}
Yet we can't write it like this, as firstIndex requires argument conforming to Equatable. But Item being protocol, can't conform to Equatable.
I've tried to implement it using type erasure, but it became too complicated.
So I am curious is there some workaround to implement such functions (provided that actual items are equatable)?
Since you're using func firstIndex of an array in this func indexOfItem(_ item: Item) -> Int? therefore the Item has to be a concrete object (behind the scene of firstIndex func is comparing each element of an array and print out the index of the element).
There are 2 ways to do this
First is using associatedtype to keep your protocol generic
protocol Item: Equatable {
var name: String { get }
}
protocol Container {
associatedtype Item
var items: [Item] { get }
}
struct MyItem: Item {
var name: String
}
extension Container where Item == MyItem {
func indexOfItem(_ item: Item) -> Int? {
return items.firstIndex(of: item)
}
}
Second is using an equatable object MyItem instead a protocol Item inside the Container protocol
protocol Item {
var name: String { get }
}
protocol Container {
var items: [MyItem] { get }
}
struct MyItem: Item, Equatable {
var name: String
}
extension Container {
func findIndex(of item: MyItem) -> Int? {
return items.firstIndex(of: item)
}
}
Finally find simple enough solution:
То make protocol generic with associated type and constraint
this type to Equatable.
public protocol Container {
associatedtype EquatableItem: Item, Equatable
var items: [EquatableItem] {get}
}
public protocol Item {
var name: String {get}
}
public extension Container {
func indexOfItem(_ item: EquatableItem) -> Int? {
items.firstIndex(of: item)
}
}
This compiles and now if I have some types
struct SomeContainer {
var items: [SomeItem]
}
struct SomeItem: Item, Equatable {
var name: String
}
I only need to resolve associatedtype to provide protocol conformance for SomeContainer type:
extension SomeContainer: Container {
typealias EquatableItem = SomeItem
}

Use generics in place of a dedicated variable enum

I have a protocol. This is implemented by many structs that fall into one of two types of category: TypeOne and TypeTwo. I want to be able to distinguish between their types, so I've added an enum ProtocolType that defines the types typeOne and typeTwo. By default I set the protocolType to be typeOne, but I manually specify typeTwo when it's a TypeTwo struct:
enum ProtocolType {
case typeOne
case typeTwo
}
protocol MyProtocol {
let name: String { get }
var protocolType: ProtocolType { get }
}
extension MyProtocol {
var protocolType: ProtocolType {
return .typeOne
}
}
enum TypeOne {
struct Foo: MyProtocol {
let name = "foo"
}
}
enum TypeTwo {
struct Bar: MyProtocol {
let name = "bar"
let protocolType = .typeTwo
}
}
Is there any way I can remove the necessity for defining protocolType in all structs and somehow use generics to identify what type a struct is? They're already defined under the TypeOne and TypeTwo convenience enums, I was wondering if I could utilise that some how?
Given some protocol:
protocol MyProtocol {
var name: String { get }
}
It sounds like you want to "tag" certain types as special, even though they have the same requirements. That's not an enum, that's just another type (protocol):
// No additional requirements
protocol SpecialVersionOfMyProtocol: MyProtocol {}
You can then tag these at the type level, not the value level:
struct Foo: MyProtocol {
let name = "foo"
}
struct Bar: SpecialVersionOfMyProtocol {
let name = "bar"
}
And you can tell the difference using is if you need to:
func f<T: MyProtocol>(x: T) {
if x is SpecialVersionOfMyProtocol {
print("special one")
}
}
In most cases, though, I wouldn't use this kind of runtime check. I'd just have two protocols (one for TypeOne and one for TypeTwo), and implement whatever you need as extensions on those. For example, say you want to print the name differently depending on the type. Start with a protocol that just expresses that:
protocol NamePrintable {
var name: String { get }
func printName()
}
func printIt<T: NamePrintable>(x: T) {
x.printName()
}
Then extend that for TypeOnes and TypeTwos:
protocol TypeOne: NamePrintable {}
extension TypeOne {
func printName() {
print("I'm a type one with the name \(name)")
}
}
protocol TypeTwo: NamePrintable {}
extension TypeTwo {
func printName() {
print("I'm a type two with the name \(name)")
}
}
And conform your structs:
struct Foo: TypeOne {
let name = "foo"
}
struct Bar: TypeTwo {
let name = "bar"
}
printIt(x: Foo()) // I'm a type one with the name foo
printIt(x: Bar()) // I'm a type two with the name bar
If you want a default implementation, you can hang it on NamePrintable, but I kind of recommend not doing that for what you've described. I'd probably just have "type one" and "type two" explicitly.
extension NamePrintable {
func printName() {
print("BASE FUNCTIONALITY")
}
}

How can I erase generics from a type in Swift?

I need to declare an array like this:
var cups: [Cup<Drink>] = []
The Cup is a struct and the Drink is a protocol, but I got the following error:
Value of protocol type 'Drink' cannot conform to 'Drink'; only struct/enum/class types can conform to protocols
I know the protocol type 'Drink' can be erased by a AnyDrink struct, the flowing code is an example.
But in fact, the Type-Erasure will become extremely complicated when associatetype, Self and static-method (in the case of non-final class adopts Drink, such as the protocol Equatable with static method ==) are used in Drink.
Is there a better way to declare the cups array?
Or: Is there any easy way to make Type-Erasure? It should be a builtin feature.
protocol Drink {
...
}
struct AnyDrink: Drink {
let drink: Drink
...
}
struct Water: Drink {
...
}
struct Coffee: Drink {
...
}
struct Tea: Drink {
...
}
struct Cup<T: Drink> {
private(set) var drink: T?
mutating func bottomUp() {
drink = nil
}
}
struct Waiter {
var cups: [Cup<AnyDrink>] = []
mutating func makeACupOfSth(_ cup: Cup<AnyDrink>) {
cups.append(cup)
}
mutating func pleaseGiveMeACupOfSthToDrink() -> Cup<AnyDrink> {
return cups.removeFirst()
}
static func excuse(_ customer: Customer) -> Waiter {
return Waiter()
}
}
struct Customer {
var me: Self { self }
func drink() {
var waiter = Waiter.excuse(me)
var cup = waiter.pleaseGiveMeACupOfSthToDrink()
cup.bottomUp()
}
}
For question Is there a better way to declare the cups array?:
You can use another protocol called DrinkGeneric like this and implement it by Cup Struct:
protocol DrinkGeneric {
func sample()
func typOfDrink() -> Drink.Type
}
struct Cup<T: Drink>: DrinkGeneric {
public var drink: T?
mutating func bottomUp() {
drink = nil
}
public func typeOfDrink() -> Drink.Type {
return type(of: drink!)
}
func sample() {
print("sample")
}
}
Then create an array with type DrinkGeneric like this:
var cups: [DrinkGeneric] = [Cup(drink: Water()), Cup(drink: Tea())]
For check type:
if cups[0].typeOfDrink() is Water.Type {
// Any work
}

How to use associatedType protocol in vars or arrays throught `some` in Swift 5.1?

I tried to make array or var comform to ProtocolA but I ran into some errors.
What is going on here?
I created two protocols with/without associatedtype and made two struct conform to ProtocolA and ProtocolB
protocol ProtocolA {
associatedtype ProtocolAType
var prop1: ProtocolAType { get set }
func description()
func methodA(param1: ProtocolAType) -> ProtocolAType
}
protocol ProtocolB {
func description()
}
extension ProtocolA {
func description() {
print(self)
}
func methodA(param1: ProtocolAType) -> ProtocolAType {
param1
}
}
struct StructA: ProtocolA, ProtocolB {
var prop1: Int
}
struct StructB: ProtocolA, ProtocolB {
var prop1: String
}
I created CustomCollection to pass some type.
struct CustomCollection<T> {
typealias Items = [T]
private var items: Items
init(items: Items) {
self.items = items
}
}
extension CustomCollection: ExpressibleByArrayLiteral {
init(arrayLiteral elements: T...) {
self.items = elements
}
}
extension CustomCollection: Collection {
typealias Index = Items.Index
typealias Element = Items.Element
var startIndex: Index { items.startIndex }
var endIndex: Index { items.endIndex }
subscript(index: Index) -> Iterator.Element {
get { items[index] }
set { items[index] = newValue }
}
func index(after i: Index) -> Index {
items.index(after: i)
}
}
CustomCollection works ok with non-associated type protocols.
var items: CustomCollection<ProtocolB> = [StructA(prop1: 1), StructB(prop1: "1")]
for item in items {
item.description()
}
I tried to call methodA but I got errors below.
var item1: some ProtocolA = StructA(prop1: 1)
var item2: some ProtocolA = StructB(prop1: "1")
item1.description()
//Cannot invoke 'methodA' with an argument list of type '(param1: Int)'
var result1 = item1.methodA(param1: 1)
//Cannot invoke 'methodA' with an argument list of type '(param1: String)'
var result2 = item2.methodA(param1: "1")
I don't know to make [ProtocolA]
//Cannot convert value of type '[Any]' to specified type 'some ProtocolA'
//Property declares an opaque return type, but cannot infer the underlying type from its initializer expression
var items1: some ProtocolA = [StructA(prop1: 1), StructB(prop1: "1")]
//Property declares an opaque return type, but cannot infer the underlying type from its initializer expression
//Return type of var 'items2' requires that '[StructA]' conform to 'ProtocolA'
var items2: some ProtocolA = [StructA(prop1: 1), StructA(prop1: 1)]
I'd like to call methodA.
for item in items1 {
item.methodA(2)
}
for item in items2 {
item.methodA("2")
}
I mist how to specify the associatedtype
//Protocol 'ProtocolA' can only be used as a generic constraint because it has Self or associated type requirements
var items4: CustomCollection<ProtocolA> = [StructA(prop1: 1), StructB(prop1: "1")]
//An 'opaque' type must specify only 'Any', 'AnyObject', protocols, and/or a base class
var items5: some CustomCollection<ProtocolA> = [StructA(prop1: 1), StructB(prop1: "1")]
I wouldn't like to use casting at the call site, like below
var items: some Collection = [StructA(prop1: 1), StructB(prop1: "1")]
for item in items {
if let item = item as? StructA {
item.methodA(param1: 4)
}
if let item = item as? StructB {
item.methodA(param1: "3")
}
}
I'd like to use something like below
var items: some CustomCollection<ProtocolA> = [StructA(prop1: 1), StructA(prop1: 2)]
for item in items {
item.methodA(param1: 4)
}
I suppose I will have to make different protocols for all supported types without associated types. This one way or has another's?
Unfortunately, you just can't.
The explanation quite is simple, in fact, for Swift your array needs to contain elements of the same "type". When your protocol has no associated type, that's easy, your array contains ProtocolB instances...
But when an associated type (or Self) is involved ProtocolA is not enough. Because ProtocolA does not mean anything alone, it needs its associated type. More precisely you can't have in the same array ProtocolA{Int} and ProtocolA{String}.
So when you declare a variable or an array of ProtocolA the compiler can't know what associated type is... associated.
It is the same thing as generics in a way :
var x : Array = [3,4]
x = ["thing"] // You can't
The first line infers the type to Array<Int> so you can't assign an array of string...
But when you write :
var items: CustomCollection<ProtocolA> = [StructA(prop1: 1)]
It can't be inferred because the "real" type it would need, would be CustomCollection<ProtocolA{Int}> but there's not such thing in swift...
Worse, when you write:
var items: CustomCollection<ProtocolA> = [StructA(prop1: 1), StructB(prop1: "1")]
You have elements of ProtocolA{Int} and ProtocolA{String}.
Note: When I write ProtocolA{Int} it's just a way of clarifying that the protocol itself needs its associated type, not some kind of syntax.
EDIT: For your last question, can't you do something like that?
protocol ProtocolAA {
func description()
}
extension ProtocolAA {
func description() {
print(self)
}
func methodA<T>(param1: T) -> T {
param1
}
}
struct StructA: ProtocolAA, ProtocolB {
var prop1: Int
}
struct StructB: ProtocolAA, ProtocolB {
var prop1: String
}
var items: CustomCollection<ProtocolAA> = [StructA(prop1: 1), StructA(prop1: 2), StructB(prop1: "X")]
for item in items {
item.methodA(param1: 4)
}

Conformance to a protocol (as a protocol)

Say I have a protocol Item, and a struct ConcreteItem that conforms to it.
protocol Item {
var name: String { get }
}
struct ConcreteItem: Item {
let name: String
}
At some point I want to have two sets of ConcreteItem.
let set1 = Set([ConcreteItem(name: "item1")])
let set2 = Set([ConcreteItem(name: "item2"), ConcreteItem(name: "item1")])
Which I'd expect to return the item with name "item1".
I can make ConcreteItem conform to Hashable and the Set code will work. However, lets say I also had the following:
struct AnotherConcreteItem: Item {
let name: String
}
I'd like AnotherConcreteItem to also conform to Hashable simply for having conformed to Item.
However, when I try to implement that idea:
extension Item: Hashable {
var hashValue: Int {
return name.characters.count
}
}
I get the following error: Extension of protocol 'Item' cannot have an inheritance clause.
Extension of protocol 'Item' cannot have an inheritance clause
Here item is the protocol so conforming Hashable protocol will not work. For more details Refer here
What you are trying to do is possible with some protocols, but not all. If the protocol you are trying to conform to does not have any associated types or Self, this is possible.
Example:
protocol A {
func foo()
}
protocol Item: A {
var name: String { get }
}
struct ConcreteItem: Item {
let name: String
}
extension Item {
func foo() {
}
}
Everything that conforms to Item will also conform to A.
However, Hashable has Self constraints. To conform to Hashable, you must also conform to Equatable. To conform to Equatable, the implementation of == must be in a concrete class, not another protocol because Equatable can't be used as a parameter type. The most you can do is something like this:
protocol Item: Hashable {
var name: String { get }
}
struct ConcreteItem: Item {
let name: String
// you need to implement == in every concrete class
static func ==(lhs: ConcreteItem, rhs: ConcreteItem) -> Bool {
return ...
}
}
extension Item {
var hashValue: Int {
return name.characters.count
}
}