How to evaluate equality for homogeneous collections? - swift

The case:
Consider the following:
protocol Car {
static var country: String { get }
var id: Int { get }
var name: String { get set }
}
struct BMW: Car {
static var country: String = "Germany"
var id: Int
var name: String
}
struct Toyota: Car {
static var country: String = "Japan"
var id: Int
var name: String
}
Here I have a simple example of how to create an abstraction layer by using a -Car- protocol, thus I am able to declare a heterogeneous collection of cars:
let cars: [Car] = [BMW(id: 101, name: "X6"), Toyota(id: 102, name: "Prius")]
And it works fine.
The problem:
I want to be able to evaluate the equality of the cars (by id), example:
cars[0] != cars[1] // true
So, what I tried to do is to let Car to conforms to Equatable protocol:
protocol Car: Equatable { ...
However, I got the "typical" compile-time error:
error: protocol 'Car' can only be used as a generic constraint because
it has Self or associated type requirements
I am unable to declare cars: [Car] array anymore. If I am not mistaking, the reason behind it is that Equatable uses Self so it would be considered as homogeneous.
How can I handle this problem? Could Type erasure be a mechanism to resolve it?

A possible solution is a protocol extension, instead of an operator it provides an isEqual(to function
protocol Car {
static var country: String { get }
var id: Int { get }
var name: String { get set }
func isEqual(to car : Car) -> Bool
}
extension Car {
func isEqual(to car : Car) -> Bool {
return self.id == car.id
}
}
and use it
cars[0].isEqual(to: cars[1])

Here is solution using Type Erasure:
protocol Car {
var id: Int { get }
var name: String { get set }
}
struct BMW: Car {
var id: Int
var name: String
}
struct Toyota: Car {
var id: Int
var name: String
}
struct AnyCar: Car, Equatable {
private var carBase: Car
init(_ car: Car) {
self.carBase = car
}
var id: Int { return self.carBase.id }
var name: String {
get { return carBase.name}
set { carBase.name = newValue }
}
public static func ==(lhs: AnyCar, rhs: AnyCar) -> Bool {
return lhs.carBase.id == rhs.carBase.id
}
}
let cars: [AnyCar] = [AnyCar(BMW(id: 101, name: "X6")), AnyCar(Toyota(id: 101, name: "Prius"))]
print(cars[0] == cars[1])
Don't know how to implement this with static property. If I figure out, I will edit this answer.

Some good solutions to the general problem have already been given – if you just want a way to compare two Car values for equality, then overloading == or defining your own equality method, as shown by #Vyacheslav and #vadian respectively, is a quick and simple way to go. However note that this isn't an actual conformance to Equatable, and therefore won't compose for example with conditional conformances – i.e you won't be able to then compare two [Car] values without defining another equality overload.
The more general solution to the problem, as shown by #BohdanSavych, is to build a wrapper type that provides the conformance to Equatable. This requires more boilerplate, but generally composes better.
It's worth noting that the inability to use protocols with associated types as actual types is just a current limitation of the Swift language – a limitation that will likely be lifted in future versions with generalised existentials.
However it often helps in situations like this to consider whether your data structures can be reorganised to eliminate the need for a protocol to begin with, which can eliminate the associated complexity. Rather than modelling individual manufacturers as separate types – how about modelling a manufacturer as a type, and then have a property of this type on a single Car structure?
For example:
struct Car : Hashable {
struct ID : Hashable {
let rawValue: Int
}
let id: ID
struct Manufacturer : Hashable {
var name: String
var country: String // may want to consider lifting into a "Country" type
}
let manufacturer: Manufacturer
let name: String
}
extension Car.Manufacturer {
static let bmw = Car.Manufacturer(name: "BMW", country: "Germany")
static let toyota = Car.Manufacturer(name: "Toyota", country: "Japan")
}
extension Car {
static let bmwX6 = Car(
id: ID(rawValue: 101), manufacturer: .bmw, name: "X6"
)
static let toyotaPrius = Car(
id: ID(rawValue: 102), manufacturer: .toyota, name: "Prius"
)
}
let cars: [Car] = [.bmwX6, .toyotaPrius]
print(cars[0] != cars[1]) // true
Here we're taking advantage of the automatic Hashable synthesis introduced in SE-0185 for Swift 4.1, which will consider all of Car's stored properties for equality. If you want to refine this to only consider the id, you can provide your own implementation of == and hashValue (just be sure to enforce the invariant that if x.id == y.id, then all the other properties are equal).
Given that the conformance is so easily synthesised, IMO there's no real reason to just conform to Equatable rather than Hashable in this case.
A couple of other noteworthy things in the above example:
Using a ID nested structure to represent the id property instead of a plain Int. It doesn't make sense to perform Int operations on such a value (what does it mean to subtract two identifiers?), and you don't want to be able to pass a car identifier to something that for example expects a pizza identifier. By lifting the value into its own strong nested type, we can avoid these issues (Rob Napier has a great talk that uses this exact example).
Using convenience static properties for common values. This lets us for example define the manufacturer BMW once and then re-use the value across different car models that they make.

It is possible to override ==:
import UIKit
var str = "Hello, playground"
protocol Car {
static var country: String { get }
var id: Int { get }
var name: String { get set }
}
struct BMW: Car {
static var country: String = "Germany"
var id: Int
var name: String
}
struct Toyota: Car {
static var country: String = "Japan"
var id: Int
var name: String
}
func ==(lhs: Car, rhs: Car) -> Bool {
return lhs.id == rhs.id
}
BMW(id:0, name:"bmw") == Toyota(id: 0, name: "toyota")

Related

How to create a protocol conform Comparable in Swift

I have a use case to compare the enployee's rank. Here is what I want to do:
protocol Enployee: Comparable {
var id: String { get }
var rank: Int { get }
var name: String { get }
var type: String { get }
}
extension Enployee {
static func <(lhs: Enployee, rhs: Enployee) -> Bool {
return lhs.rank < rhs.rank
}
}
But I got the following error:
Protocol 'Enployee' can only be used as a generic constraint because it has Self or associated type requirements
Then I changed my code:
extension Enployee {
static func <(lhs: Self, rhs: Self) -> Bool {
return lhs.rank < rhs.rank
}
}
I can compile it. But when I continue working on my user case:
struct Engineer: Enployee {
var id: String
var rank: Int
var name: String
let type: String = "Engineer"
}
struct Manager: Enployee {
var id: String
var rank: Int
var name: String
let type: String = "Manager"
}
let staff1 = Engineer(id: "123", rank: 2, name: "Joe")
let staff2 = Engineer(id: "124", rank: 2, name: "Frank")
let staff3 = Manager(id: "101", rank: 10, name: "John")
public struct Department<T: Comparable> {
}
let queue = Department<Enployee>()
I got another error message:
Protocol 'Enployee' as a type cannot conform to 'Comparable'
Any idea?
The error message tells you what the problem is. Having declared Department<T: Comparable> you cannot resolve T as Enployee; it is a protocol, not a type conforming to Comparable (such as Engineer).
A generic has to be resolved to one type. It would be legal to declare Department<T: Enployee> if that is what you really mean. But then you are setting yourself up for multiple Department types, a department of Manager and a department of Engineer. That is probably not what you want, so this is a bad use of a generic.
Moreover, type strings are a horrifically bad smell:
struct Engineer: Enployee {
var id: String
var rank: Int
var name: String
let type: String = "Engineer" // uck
}
It seems much more likely that you want a class and subclasses here. Why fight against OOP?

Generating auto-incrementing Instance IDs in Swift using Protocols and protocol-extensions only

Goal
To create an "AutoIDable" protocol with the following behaviour.
Every instance of a class conforming to this protocol will get an auto-generated "id" property of String type.
The code should generate id strings in the format <prefix><Instance-count-starting-from-1> (Eg: E-1, E-2, ...E-<n> and so on for 1st , 2nd ... nth Instance of the conforming class.
The protocol & protocol extensions should do ALL of the required work to generate the id strings. The conforming class will only have to subscribe to the protocol and nothing more.
Current status:
I have achieved Goal-1 & Goal-2 with the following implementation:
protocol Identifiable {
var id: String { get }
}
protocol AutoIDable: Identifiable{
static var _instanceCount: Int { get set }
}
class AutoID: AutoIDable {
init(idPrefix: String) {
setAutoID(prefix: idPrefix)
}
internal static var _instanceCount: Int = 0
var id: String = ""
func setAutoID(prefix: String = ""){
Self._instanceCount += 1
self.id = "\(prefix)\(Self._instanceCount)"
}
}
class Employee: AutoID {
init(){
super.init(idPrefix: "E-")
}
}
let e1 = Employee()
let e2 = Employee()
let e3 = Employee()
print(e1.id)
print(e2.id)
print(e3.id)
print(e1.id)
The output from running the above code:
E-1
E-2
E-3
E-1
Todo:
To achieve Goal-3, I need to eliminate the AutoID superclass and implement the same functionality using protocol extensions.
I ran into trouble because:
Protocol extensions do not allow static stored properties. I do know how to work around this limitation without using a superclass.
I do not know how to inject code into all the initialisers the creator of the Employee class might create. Again, I could not think of a workaround without using a superclass.
I would be grateful if you can point me in the right direction.
PS: New to Swift programming. If you’ve suggestions for implementing the code in a more “swifty” way, please do let me know. :-)
Since you want to use protocols, you can't have a stored property in the protocol. So, you'll need some place to store the incrementing ID value, if not the IDs themselves.
Not sure if it violates your requirements of using only protocols, because it would require a type for storage, but at least it won't require conforming classes to have a superclass.
So, let's say we build such a class that holds all the IDs and keeps the incrementing counter:
class AutoIncrementId {
static private var inc: Int = 0
static private var ids: [ObjectIdentifier: String] = [:]
static func getId(_ objectId: ObjectIdentifier, prefix: String) -> String {
if let id = ids[objectId] { return id }
else {
inc += 1
let id = "\(prefix)\(inc)"
ids[objectId] = id
return id
}
}
}
Then the protocol requirement could be:
protocol AutoIdentifiable {
static var prefix: String { get }
var id: String { get }
}
So, a class would need to define its prefix. But we could define a default implementation for id:
extension AutoIdentifiable where Self: AnyObject {
var id: String {
AutoIncrementId.getId(ObjectIdentifier(self), prefix: Self.prefix)
}
}
The usage would be:
class Employee: AutoIdentifiable {
static let prefix = "E-"
}
let e1 = Employee()
let e2 = Employee()
let e3 = Employee()
print(e1.id) // E-1
print(e2.id) // E-2
print(e3.id) // E-3
print(e1.id) // E-1

Swift - KeyPath extension - enforce conformance to protocol

ideally, I'd like to get the name of the property referenced by a KeyPath. But this seems not to be possible out-of-the-box in Swift.
So my thinking is that the KeyPath could provide this information based on protocol extension added by a developer. Then I'd like to design an API with an initializer/function that accepts a KeyPath conforming to that protocol (that adds a computed property).
So far I was only able to define the protocol and conditional conformance of the protocol. The following code compiles fine.
protocol KeyPathPropertyNameProviding {
var propertyName: String {get}
}
struct User {
var name: String
var age: Int
}
struct Person {
var name: String
var age: Int
}
extension KeyPath: KeyPathPropertyNameProviding where Root == Person {
var propertyName: String {
switch self {
case \Person.name: return "name"
case \Person.age: return "age"
default: return ""
}
}
}
struct PropertyWrapper<Model> {
var propertyName: String = ""
init<T>(property: KeyPath<Model, T>) {
if let property = property as? KeyPathPropertyNameProviding {
self.propertyName = property.propertyName
}
}
}
let userAge = \User.age as? KeyPathPropertyNameProviding
print(userAge?.propertyName) // "nil"
let personAge = \Person.age as? KeyPathPropertyNameProviding
print(personAge?.propertyName) // "age"
let wrapper = PropertyWrapper<Person>(property: \.age)
print(wrapper.propertyName) // "age"
But I am unable to restrict the API so that initialization parameter property has to be a KeyPath AND must conform to a certain protocol.
For example, the following would result in a compilation error but should work from my understanding (but probably I miss a key detail ;) )
struct PropertyWrapper<Model> {
var propertyName: String = ""
init<T>(property: KeyPath<Model, T> & KeyPathPropertyNameProviding) {
self.propertyName = property.propertyName // compilation error "Property 'propertyName' requires the types 'Model' and 'Person' be equivalent"
}
}
Any tips are highly appreciated!
You are misunderstanding conditional conformance. You seem to want to do this in the future:
extension KeyPath: KeyPathPropertyNameProviding where Root == Person {
var propertyName: String {
switch self {
case \Person.name: return "name"
case \Person.age: return "age"
default: return ""
}
}
}
extension KeyPath: KeyPathPropertyNameProviding where Root == User {
var propertyName: String {
...
}
}
extension KeyPath: KeyPathPropertyNameProviding where Root == AnotherType {
var propertyName: String {
...
}
}
But you can't. You are trying to specify multiple conditions to conform to the same protocol. See here for more info on why this is not in Swift.
Somehow, one part of the compiler thinks that the conformance to KeyPathPropertyNameProviding is not conditional, so KeyPath<Model, T> & KeyPathPropertyNameProviding is actually the same as KeyPath<Model, T>, because somehow KeyPath<Model, T> already "conforms" to KeyPathPropertyNameProviding as far as the compiler is concerned, it's just that the property propertyName will only be available sometimes.
If I rewrite the initialiser this way...
init<T, KeyPathType: KeyPath<Model, T> & KeyPathPropertyNameProviding>(property: KeyPathType) {
self.propertyName = property.propertyName
}
This somehow makes the error disappear and produces a warning:
Redundant conformance constraint 'KeyPathType': 'KeyPathPropertyNameProviding'
Key paths are hashable, so I recommend a dictionary instead. It's especially easy to put it together with strong typing if you're able to use a CodingKey type.
struct Person: Codable {
var name: String
var age: Int
enum CodingKey: Swift.CodingKey {
case name
case age
}
}
extension PartialKeyPath where Root == Person {
var label: String {
[ \Root.name: Root.CodingKey.name,
\Root.age: .age
].mapValues(\.stringValue)[self]!
}
}
Then use parentheses instead of the cast you demonstrated. No need for a protocol so far…
(\Person.name).label // "name"
(\Person.age).label // "age"
This will probably all be cleaner due to built-in support someday. https://forums.swift.org/t/keypaths-and-codable/13945

Protocol Composition can't be used in struct in swift

protocol is a kind of type in swift and should be used in everywhere as a type, so why Protocol Composition can't be used in struct in swift?
protocol Named {
var name: String { get }
}
protocol Aged {
var age: Int { get }
}
struct Person: Named, Aged {
var name: String
var age: Int
}
func wishHappyBirthday(to celebrator: Named & Aged) {
print("Happy birthday, \(celebrator.name), you're \(celebrator.age)!")
}
let birthdayPerson = Person(name: "Malcolm", age: 21)
wishHappyBirthday(to: birthdayPerson)
// Prints "Happy birthday, Malcolm, you're 21!"
In struct Person, if I change it into
struct Person: Named & Aged {
var name: String
var age: Int
}
the complier error appears: Protocol composition is neither allowed nor needed here
So why I can use Protocol composition in function rather than struct?

Mapping in Swift Between Protocol Conforming Types

I want to map between any two objects which conform to the same protocol. It would be convenient to do so via a function with the signature:
func mapFrom<T>(objectA: T, to inout objectB: T)
Even better though (for immutable types) would be to have it in the form:
func map<T, U: T>(from source: T) -> U
where somehow it could initialize a U object from the values in T.
I would like to do this via Swift Reflection rather than using the Objective-C run-time, but I would settle for that if it was the only way. If somehow it could be done without reflection that would be amazing, but I don't see how.
The reason I want to do this is because I have mutable Realm classes which conform to their respective protocol, and I want to map them to the immutable struct types.
An example would be:
/**
The protocol.
*/
protocol Food {
var name: String { get }
var weight: Float { get }
var price: Float { get }
}
/**
The mutable Realm class representation.
*/
final class FoodEntity: Object, Food {
dynamic var name = ""
dynamic var weight = 0.0
dynamic var price = 0.0
}
/**
The final struct I want to map to from the Realm representation.
*/
struct FoodProduct: Food {
let name: String
let weight: Float
let price: Float
}
I would like to be able to have a generic function or method with which to map a FoodEntity to a FoodProduct without having to manually do something like:
FoodProduct(name: entity.name, weight: entity.weight, price: entity.price)
How can this be done, if it can be done at all?
I think you are looking for something like this.
func fetchAllFoodProducts() -> [FoodProduct]
{
var foodProducts : [FoodProduct] = []
// Fetch From Realm
let products = realm.objects(FoodEntity.self)
for product in products
{
foodProducts.append(FoodProduct(name: product.name, weight: product.weight, price: product.price))
}
return foodProducts
}
The thing is that there can't be a generic way to do this. Because you have to assign the values of name, weight & price somehow. This is the closest you can get, I think.
Or you can do something like this.
func fetchAllFoodProducts() -> [FoodProduct]
{
var foodProducts : [FoodProduct] = []
// Fetch From Realm
let products = realm.objects(FoodEntity.self)
for product in products
{
foodProducts.append(FoodProduct(entity: product))
}
return foodProducts
}
By altering your FoodEntity a little.
struct FoodProduct: Food {
let name: String
let weight: Float
let price: Float
init(entity : FoodEntity)
{
self.name = entity.name
self.weight = entity.weight
self.price = entity.price
}
}