When to use type erasure in Swift? - swift

There are already a lot of questions on how to do type erasure in Swift, and I've seen type erasure often described as an important pattern for working with protocols with associated types and generic types.
However, it seems to me like needing type erasure is often symptomatic of design problems — you're inherently "throwing away" type information (i.e. to put a value in a container or to pass it to a function), which often ultimately needs to be recovered later on anyway through verbose and brittle downcasting. Perhaps what I don't understand is the use case for a "type" like AnyHashable—PATs/protocols with self can only be used as generic constraints because they aren't reified types, which makes me wonder what compelling reasons there are to want to reify them.
In short, when is it a good idea to use type erasure in Swift? I'm looking for some general guidelines on when to use this pattern, and possibly examples of some practical use cases where type erasure is preferable to its alternatives.

I've tried to find a minimalistic example of type erasure. From my experience, it often gets more complex, and I try to avoid it as much as possible. But sometimes that's the way.
It is finally the same complexity as before, with old-school language. Excepted that old-school language was hurting you by crashing, while swift hurts you at build time.
It is meant to be a strongly typed language, so it does not fit well with generics.
Suppose you need to manage some shapes in a document.
These shapes are Identifiables, meaning they have an id which type is determined by an associated type. Int in this case.
The code below won't build because it can't use the Shape protocol directly since the type of id is an associated type that is defined by object conforming to the Shape protocol
import Foundation
protocol Shape: AnyShape, Identifiable {
var name: String { get }
}
struct Square: Shape {
var id: Int = 0
var name: String { "Square" }
}
func selectShape(_ shape: Shape) {
print("\(shape.name) selected")
}
By adding a type erased shape, you can then pass it to functions.
Thus, this will build:
import Foundation
protocol AnyShape {
var name: String { get }
}
protocol Shape: AnyShape, Identifiable {
}
struct Square: Shape {
var id: Int = 0
var name: String { "Square" }
}
func selectShape(_ shape: AnyShape) {
print("\(shape.name) selected")
}
Simple use case.
Suppose now our app connects to two shapes manufacturers servers to fetch their catalog and sync with our's.
We know shapes are shapes, all around the world, but the ACME Shape Factory index in database is an Int, while the Shapers Club use UUID..
That's at this point we need to 'recover' the type, as you say.
It is exactly what's explained when looking in the AnyHashable source doc.
Cast can't be avoided, and it is finally a good thing for the security of the app and the solidity of the models.
The first part is the protocols, and it may be verbose and become complex as the number of situations grows, but it will be in the communication foundation framework of the app, and should not change very often.
import Foundation
// MARK: - Protocols
protocol AnyShape {
var baseID: Any { get }
var name: String { get }
}
// Common functions to all shapes
extension AnyShape {
func sameObject(as shape: AnyShape) -> Bool {
switch shape.baseID.self {
case is Int:
guard let l = baseID as? UUID , let r = shape.baseID as? UUID else { return false }
return l == r
case is UUID:
guard let l = baseID as? UUID , let r = shape.baseID as? UUID else { return false }
return l == r
default:
return false
}
}
func sameShape(as shape: AnyShape) -> Bool {
return name == shape.name
}
func selectShape(_ shape: AnyShape) {
print("\(shape.name) selected")
}
}
protocol Shape: AnyShape, Identifiable {
}
extension Shape {
var baseID: Any { id }
}
The second part is the models - this will hopefully evolve as we work with more shape manufacturers.
The sensitive operation that can be done on shapes are not in this code. So no problem to create and tweak models and apis.
// MARK: - Models
struct ACME_ShapeFactory_Model {
struct Square: Shape {
var id: Int = 0
var name: String { "Square" }
var ACME_Special_Feature: Bool
}
}
struct ShapersClub_Model {
struct Square: Shape {
var id: UUID = UUID()
var name: String { "Square" }
var promoCode: String
}
}
Test
let shape1: AnyShape = ACME_ShapeFactory_Model.Square()
let shape2: AnyShape = ShapersClub_Model.Square()
let shape3: AnyShape = ShapersClub_Model.Square()
Compare two different shapes references from different manufacturers
shape1.sameObject(as: shape2) : false
-> Logic, it can't be the same item if it comes from different manufacturers
Compare two different shapes references from same manufacturers
shape2.sameObject(as: shape3) : false
-> This is a new shape, sync in catalog
Compare two identical shapes references from same manufacturers
shape2.sameObject(as: shape2) : true
-> We already have this one in the catalog
Compare two shapes from different manufacturers
shape1.sameShape(as: shape2) : true
-> Dear customer, we have two kind of squares from two manufacturers
That's all. I hope this may be of any help.
Any correction or remark is welcome.
Last word, I am quite proud of the name of my Shapes manufactures :)

Related

How can several related struct types share an init(...) method without defining read-only properties with var?

I find myself very often forced to use var properties in Swift even when properties will only be assigned once.
Here's an example: The only way I have found to have several types share an init(...) is to put the init(...) in a protocol extension. However if I do this, the properties of the struct must be assigned a dummy value before the body of the init(...) in the protocol extension is run, when it will get its "real" value.
The example below runs without error. How can color be a let property but still be assigned in Piece's init(...)?
protocol Piece {
var color: Character {set get} // "w" or "b"
init()
}
extension Piece {
init(color c: Character) {
self.init()
color = c
// Some complex logic here all Pieces share
}
}
struct Knight: Piece {
var color: Character = "_" // annoying dummy value
internal init(){}
}
// ... the other Piece types here ...
let knight = Knight(color: "w")
To make this clearer, hopefully, this is what I would like instead: (This does not compile, because of let color in struct Knight.)
protocol Piece {
let color: Character {get} // "w" or "b"
}
extension Piece {
init(color c: Character) {
color = c
// Some complex logic here all Pieces share
}
}
struct Knight: Piece {
let color: Character
}
// ... the other Piece types here ...
let knight = Knight(color: "w")
Edit (after I have found an answer, see below): Another way to state the the subject line question: How can several struct types share initialization logic, while allowing read-only properties to be let?
2nd Edit Made clear that second code example doesn't compile.
3rd Edit Made clear which let color.
With some refactoring, you can achieve minimum code duplication. Here is my solution thinking about your scenario with a different perspective:
First, I noticed that you are taking only "w" or "b" as your color property value. Since you have only two (or let's say minimal) variations of inputs you can make color part of type definition itself by using protocol with associated types and generics, rather than having it as a property. By doing this you don't have to worry about setting property during initialization.
You can create a protocol i.e. PieceColor and create a new type for each color, i.e. Black, White and your Piece protocol can have an associated type confirming to PieceColor:
protocol PieceColor {
// color related common properties
// and methods
}
enum Black: PieceColor { // or can be struct
// color specific implementations
}
enum White: PieceColor { // or can be struct
// color specific implementations
}
protocol Piece {
associatedtype Color: PieceColor
}
This approach also provides safety guarantees as you are now restricting user input to only values your code is designed to handle, instead of adding additional validation to user inputs. Also, this helps you implementing specific relations between pieces depending on their color group, i.e. only opposite colors can kill each other etc.
Now for the main part of your question, instead of trying to customize initializer you can create a static method that initializes your piece and does some shared complex handling on it:
protocol Piece {
associatedtype Color: PieceColor
init()
static func customInit() -> Self
}
extension Piece {
static func customInit() -> Self {
let piece = Self()
// Some complex logic here all Pieces share
return piece
}
}
struct Knight<Color: PieceColor>: Piece {
// piece specific implementation
}
let wKnight = Knight<White>.customInit()
let bKnight = Knight<Black>.customInit()
Get-only protocol properties are cool because conforming types have a lot of flexibility in how they define the corresponding properties. All that's required is that the property is gettable.
So if you define your protocol with a get-only property:
protocol Piece {
var color: Character { get }
}
The conforming types can define the color property as a stored variable, a let constant, or a computed property.
Stored variable:
struct Queen: Piece {
var color: Character
}
Computed property:
struct King: Piece {
var color: Character { return "K" }
}
Let constant:
struct Knight: Piece {
let color: Character
}
Each of the above satisfies the gettable property requirement imposed by the protocol.
Regarding initialization. Recall that swift automatically creates default initializers for structs, and those initializers have parameters that correspond to each of the struct's stored properties. Swift may create an initializer for Queen and Knight that looks like this:
init(color: Character) {
self.color = color
}
So if you want color to be a let constant and you want to be able to configure it with an initializer, the above definitions for Piece and Knight are sufficient. You don't need to do any additional work.
You can instantiate a Knight like so:
let knight = Knight(color: "w")
After discovering Charles Srstka's answer to How to use protocols for stucts to emulate classes inheritance. I have built a solution. It isn't the prettiest but it does allow several struct types to share initialization logic while allowing properties that should be read-only, to be defined with let.
This works:
typealias PropertyValues = (Character) // could be more than one
protocol Piece {
var color: Character {get} // "w" or "b"
}
extension Piece {
static func prePropSetup(color c: Character) -> PropertyValues {
// logic all Pieces share, that needs to run
// BEFORE property assignment, goes here
return (c)
}
func postPropSetup(){
// logic all Pieces share, that needs to run
// AFTER property assignment, goes here
print("Example of property read access: \(color)")
}
}
struct Knight: Piece {
let color: Character
init(color c: Character){
(color) = Self.prePropSetup(color: c)
postPropSetup()
}
}
struct Bishop: Piece {
let color: Character
init(color c: Character){
(color) = Self.prePropSetup(color: c)
postPropSetup()
}
}
// ... the other Piece types continue here ...
let knight = Knight(color: "w")
let bishop = Bishop(color: "b")
Out of interest a completely alternative approach:
First we define a curry function - this one taken from https://github.com/pointfreeco/swift-prelude:
public func curry<A, B, C>(_ function: #escaping (A, B) -> C)
-> (A)
-> (B)
-> C {
return { (a: A) -> (B) -> C in
{ (b: B) -> C in
function(a, b)
}
}
}
Now let's suppose we have a piece that has a Role, which is the type of piece. Role can change because you can promote pawns. Here we'll use String for Role:
struct Piece {
let color: Character
var role: String
}
To share the init that we want white pieces and black pieces, we curry the init function:
let curryPieceInit = curry(Piece.init(color:role:))
and make two partially-applied init functions that bake in either w or b for the color:
let white = curryPieceInit("w")
let black = curryPieceInit("b")
And now we can finish the partial application by passing along the remaining argument to fully instantiate a chess piece:
let wBish = white("bishop")
let wQueen = white("queen")
let blackPawns = (1...8).map { black("pawn\($0)") }
Now make Role not a string but some custom type, an enum, encapsulating the logic representing the differences between the various chess pieces.
Make the Piece.init(color:role:) private, expose the curried versions.
No protocols needed.

Extending a constrained protocol for an array argument is not possible

I'm going to explain it by an example. We have a protocol for force having firstName and lastName like:
protocol ProfileRepresentable {
var firstName: String { get }
var lastName: String { get }
}
the type we are going to use have these two, but in an optional form:
struct Profile {
var firstName: String?
var lastName: String?
}
so after conforming to the ProfileRepresentable, we will extend the ProfileRepresentable and try to return the value and a default one for nil state:
extension Profile: ProfileRepresentable { }
extension ProfileRepresentable where Self == Profile {
var firstName: String { self.firstName ?? "NoFirstName" }
var lastName: String { self.lastName ?? "NoLastName" }
}
So far so good
Now there is a similar flow for a list of Profiles.
protocol ProfilerRepresentable {
var profiles: [ProfileRepresentable] { get }
}
struct Profiler {
var profiles: [Profile]
}
First issue
conforming to ProfilerRepresentable does NOT automatically done the implementation as expected (since Profile already conforms to ProfileRepresentable)
extension Profiler: ProfilerRepresentable { }
Second Issue
Following the previous pattern, extending ProfilerRepresentable is not working as expected and it raises a warning:
⚠️ All paths through this function will call itself
extension ProfilerRepresentable where Self == Profiler {
var profiles: [ProfileRepresentable] { self.profiles }
}
How can I achieve the goal for arrays by the way ?
Here is possible solution. Tested with Xcode 12 / swift 5.3
protocol ProfilerRepresentable {
associatedtype T:ProfileRepresentable
var profiles: [T] { get }
}
extension Profiler: ProfilerRepresentable { }
struct Profiler {
var profiles: [Profile]
}
[Profile] is not a subtype of [ProfileRepresentable]. (See Swift Generics & Upcasting for a related but distinct version of this question.) It can be converted through a compiler-provided copying step when passed as a parameter or assigned to a variable, but this is provided as a special-case for those very common uses. It doesn't apply generally.
How you should address this depends on what precisely you want to do with this type.
If you have an algorithm that relies on ProfilerRepresentable, then Asperi's solution is ideal and what I recommend. But going that way won't allow you to create a variable of type ProfileRepresentable or put ProfileRepresentable in an Array.
If you need variables or arrays of ProfilerRepresentable, then you should ask yourself what these protocols are really doing. What algorithms rely on these protocols, and what other reasonable implementations of ProfileRepresentable really make sense? In many cases, ProfileRepresentable should just be replaced with a simple Profile struct, and then have different init methods for creating it in different contexts. (This is what I recommend if your real problem looks a lot like your example, and Asperi's answer doesn't work for you.)
Ultimately you can create type erasers (AnyProfile), but I suggest exploring all other options (particularly redesigning how you do composition) first. Type erasers are perfect if your goal is to erase a complicated or private type (AnyPublisher), but that generally isn't what people mean when they reach for them.
But designing this requires knowing a more concrete goal. There is no general answer that universally applies.
Looking at your comments, there no problem with having multiple types for the same entity if they represent different things. Structs are values. It's fine to have both Double and Float types, even though every Float can also be represented as a Double. So in your case it looks like you just want Profile and PartialProfile structs, and an init that lets you convert one to the other.
struct Profile {
var firstName: String
var lastName: String
}
struct PartialProfile {
var firstName: String?
var lastName: String?
}
extension Profile {
init(_ partial: PartialProfile) {
self.firstName = partial.firstName ?? "NoFirstName"
self.lastName = partial.lastName ?? "NoLastName"
}
}
extension PartialProfile {
init(_ profile: Profile) {
self.firstName = profile.firstName
self.lastName = profile.lastName
}
}
It's possible that you have a lot of these, so this could get a bit tedious. There are many ways to deal with that depending on exactly the problem you're solving. (I recommend starting by writing concrete code, even if it causes a lot of duplication, and then seeing how to remove that duplication.)
One tool that could be useful would be Partial<Wrapped> (inspired by TypeScript) that would create an "optional" version of any non-optional struct:
#dynamicMemberLookup
struct Partial<Wrapped> {
private var storage: [PartialKeyPath<Wrapped>: Any] = [:]
subscript<T>(dynamicMember member: KeyPath<Wrapped, T>) -> T? {
get { storage[member] as! T? }
set { storage[member] = newValue }
}
}
struct Profile {
var firstName: String
var lastName: String
var age: Int
}
var p = Partial<Profile>()
p.firstName = "Bob"
p.firstName // "Bob"
p.age // nil
And a similar converter:
extension Profile {
init(_ partial: Partial<Profile>) {
self.firstName = partial.firstName ?? "NoFirstName"
self.lastName = partial.lastName ?? "NoLastName"
self.age = partial.age ?? 0
}
}
Now moving on to your Array problem, switching between these is just a map.
var partials: [Partial<Profile>] = ...
let profiles = partials.map(Profile.init)
(Of course you could create an Array extension to make this a method like .asWrapped() if it were convenient.)
The other direction is slightly tedious in the simplest approach:
extension Partial where Wrapped == Profile {
init(_ profile: Profile) {
self.init()
self.firstName = profile.firstName
self.lastName = profile.lastName
self.age = profile.age
}
}
If there were a lot of types, it might be worth it to make Partial a little more complicated so you could avoid this. Here's one approach that allows Partial to still be mutable (which I expect would be valuable) while also allowing it to be trivially mapped from the wrapped instances.
#dynamicMemberLookup
struct Partial<Wrapped> {
private var storage: [PartialKeyPath<Wrapped>: Any] = [:]
private var wrapped: Wrapped?
subscript<T>(dynamicMember member: KeyPath<Wrapped, T>) -> T? {
get { storage[member] as! T? ?? wrapped?[keyPath: member] }
set { storage[member] = newValue }
}
}
extension Partial {
init(_ wrapped: Wrapped) {
self.init()
self.wrapped = wrapped
}
}
I don't love this solution; it has a weird quirk where partial.key = nil doesn't work to clear a value. But I don't have a nice fix until we get KeyPathIterable. But there are some other routes you could take depending on your precise problem. And of course things can be simpler if Partial isn't mutable.
The point is that there's no need for protocols here. Just values and structs, and convert between them when you need to. Dig into #dynamicMemberLookup. If your problems are very dynamic, then you may just want more dynamic types.

How to get a class with generic type accept an array of different by same generic types?

I'm in the process trying to learn and understand protocols with associated types in swift.
At the same time, I'm learning SwiftUI and taking a course on Udemy.
The app that we will building is a coffee order application.
With that being said, I do not follow the tutorial to the "T" as I tried to structure the app differently, so I can learn to think on my own. The app is nothing too fancy.
The tutorial doesn't use generics and protocols to represent or structure data. It is just a tutorial to showcase SwiftUI.
I created a protocol called Coffee that has a CupSize associated type.
Each coffee Cappuccino, Espresso, and Brewed Coffee confirms to the Coffee protocol.
protocol Priceable {
var cost: Double { get }
}
protocol Coffee {
associatedtype CupSize
var cupSize: CupSize { get }
init(cupSize: CupSize)
}
enum EspressoCupSize {
case small
}
struct Espresso: Coffee, Priceable {
var cupSize = EspressoCupSize.small
var cost: Double { return 3.00 }
}
enum BrewedCoffeeCupSize {
case small
case medium
case large
}
struct BrewedCoffee: Coffee, Priceable {
var cupSize: BrewedCoffeeCupSize
var cost: Double {
switch self.cupSize {
case .small: return 1.00
case .medium: return 2.00
case .large: return 3.00
}
}
}
enum CappuccinoCupSize {
case small
case medium
case large
}
struct Cappuccino: Coffee, Priceable {
var cupSize: CappuccinoCupSize
var cost: Double {
switch self.cupSize {
case .small: return 2.00
case .medium: return 3.00
case .large: return 4.00
}
}
}
Then, I created an Order struct and an OrderManager class.
Order struct has a generic and needs to be a Priceable item.
The idea of a generic priceable item is to support other items in the future in case I want to expand the app...not just coffee.
The idea behind OrderManager is to keep track all the orders and manage the CRUD operations of the orders (still need to implement delete, read, and update).
struct Order<Item: Priceable> {
var name: String
var item: Item
}
class OrderManager<Item> {
private var orders: [Item]
init(orders: [Item]) {
self.orders = orders
}
func add(_ order: Item) {
self.orders.append(order)
}
}
My issue is using OrderManager.
let maryOrder = Order(name: "Mary", item: Espresso())
let sueOrder = Order(name: "Sue", item: BrewedCoffee(cupSize: .medium))
// Dummy Structure
struct Person {}
let orderManager = OrderManager(orders: [
maryOrder,
sueOrder,
Person() // This works!!! Which is not what I want.
])
I want the generic type for OrderManager to be an Order, but since Order has its own generic type of Priceable, I cannot seem to find the correct answer or find the correct syntax.
Things I have tried to get OrderManager to work
class OrderManager<Order> {} // Does not work because Order needs a generic type
class OrderManager<Order<Priceable>> // Still does not work
class OrderManager<Item: Priceable, Order<Item> {} // Still does not work.
// and I tried other solutions, but I cannot get this to work
// Also, when I think I got the right syntax, I cannot add Mary and Sue's orders to
// OrderManager because Mary's item is Espresso and Sue's item is BrewedCoffee
How can I get OrderManager to accept only an array of orders?
It's good that you want to experiment with generics, but this isn't the occasion for it. You say:
The idea ... is to support other items in the future in case I want to expand the app...not just coffee.
But you don't need a generic for that. The only requirement for managing an order is that the order's item be Priceable. Priceable is already a type; you don't need to add a generic type to the mix.
struct Order {
var name: String
var item: Priceable
}
class OrderManager {
private var orders: [Order]
init(orders: [Order]) {
self.orders = orders
}
func add(_ order: Order) {
self.orders.append(order)
}
}
I'm in the process trying to learn and understand generics with associated types in swift.
There is no such thing as "generics with associated types in Swift." There are generics, and there are protocols with associated types (PAT). They have some things in common, but are deeply different concepts used for very different things.
The purpose of a generic is to allow the type to vary over types chosen by the caller.
The purpose of a PAT is to allow a type to be used by existing algorithms, using types chosen by the implementer. Given this, Coffee does not make sense as a protocol. You're trying to treat it like a heterogeneous type. That's not what a PAT is. A PAT is a hook to allow types to be used by algorithms.
class OrderManager<Item> { ... }
This says that OrderManager can hold anything; literally anything at all. It doesn't have to be Priceable. In your case, Item is being coerced into Any, which is definitely not what you wanted (and why Person works when it shouldn't). But it doesn't make a lot of sense that OrderManager is tied to some item type. Do you really want one OrderManager for Coffee and a completely different OrderManager for Espresso? That doesn't match what you're doing at all. OrderManager should work over an Order of anything, right?
It's not really possible to determine what protocols and generics you do need here because you never do anything with OrderManager.orders. Start with the calling code. Start with no generics or protocols. Just let the code duplicate, and then extract that duplication into generics and protocols. If you don't have a clear algorithm (use case) in mind, you should not be creating a protocol yet.
See matt's answer for a starting point, but I'm sure it's not enough for your problem. You likely will need more things (most likely the name of the item for instance). Start with some simple structs (Espresso, BrewedCoffee, etc), and then start working out your calling code, and then you'll probably have more questions we can discuss.
To your question of how to attack this kind of problem, I would begin like this.
First, we have some items for sale. I model them in their most obvious ways:
// An Espresso has no distinguishing characteristics.
struct Espresso {}
// But other coffees have a size.
enum CoffeeSize: String {
case small, medium, large
}
// You must know the size in order to create a coffee. You don't need to know
// its price, or its name, or anything else. But you do have to know its size
// or you can't pour one. So "size" is a property of the type.
struct BrewedCoffee {
let size: CoffeeSize
}
struct Cappuccino {
let size: CoffeeSize
}
Done!
OK, not really done, but seriously, kind of done. We can now make coffee drinks. Until you have some other problem to solve, you really are done. But we do have another problem:
We want to construct an Order, so we can give the customer a bill. An Order is made up of Items. And Items have names and prices. New things can be added to an Order, and I can get textual representation of the whole thing. So we model first what we need:
struct Order {
private (set) var items: [Item]
mutating func add(_ item: Item) {
items.append(item)
}
var totalPrice: Decimal { items.map { $0.price }.reduce(0, +) }
var text: String { items.map { "\($0.name)\t\($0.price)" }.joined(separator: "\n") }
}
And to implement that, we need a protocol that provides name and price:
protocol Item {
var name: String { get }
var price: Decimal { get }
}
Now we'd like an Espresso to be an Item. So we apply retroactive modeling to make it one:
extension Espresso: Item {
var name: String { "Espresso" }
var price: Decimal { 3.00 }
}
And the same thing with BrewedCoffee:
extension BrewedCoffee {
var name: String { "\(size.rawValue.capitalized) Coffee" }
var price: Decimal {
switch size {
case .small: return 1.00
case .medium: return 2.00
case .large: return 3.00
}
}
}
And of course Cappuccino...but you know, as I start to write that I really want to cut-and-paste BrewedCoffee. That suggests maybe there's a protocol hiding in there.
// Just a helper to make syntax prettier.
struct PriceMap {
var small: Decimal
var medium: Decimal
var large: Decimal
}
protocol SizedCoffeeItem: Item {
var size: CoffeeSize { get }
var baseName: String { get }
var priceMap: PriceMap { get }
}
With that, we can implement the requirements of Item:
extension SizedCoffeeItem {
var name: String { "\(size.rawValue.capitalized) \(baseName)" }
var price: Decimal {
switch size {
case .small: return priceMap.small
case .medium: return priceMap.medium
case .large: return priceMap.large
}
}
}
And now the conformances require no code duplication.
extension BrewedCoffee: SizedCoffeeItem {
var baseName: String { "Coffee" }
var priceMap: PriceMap { PriceMap(small: 1.00, medium: 2.00, large: 3.00) }
}
extension Cappuccino: SizedCoffeeItem {
var baseName: String { "Cappuccino" }
var priceMap: PriceMap { PriceMap(small: 2.00, medium: 3.00, large: 4.00) }
}
These two examples are of two different uses of protocols. The first is implementing a heterogeneous collection ([Item]). These kinds of protocols cannot have associated types. The second is to facilitate code sharing between types. These kinds can. But in both cases I didn't add any protocols until I had a clear use case: I needed to be able to add them to Order and get back certain kinds of data. That led us to each step along the way.
As a starting point, model your data simply and design your algorithms. Then adapt your data to the algorithms with protocols. Protocols come late, not early.
You do not need to define Generic type on the class , you should put it on a method like following:
class OrderManager {
func doOrder<T: Priceable, L: Protocol2 >(obj: T) -> L {
// Use obj as a Priceable model
// return a Protocol2 model
}
}
and for send to the class you just send your model for example
varProtocol2 = OrderManager.doOrder(obj: maryOrder.item)
Here is an example with two generic object
protocol prot1 {
var a: Int {get set}
}
protocol protRet {
var b: String {get set}
init()
}
struct prot1Struct: prot1 {
var a: Int
}
struct prot2Struct: protRet {
init() {
b = ""
}
var b: String
}
class Manage {
func Do<T: prot1, L: protRet>(obj: T) -> L {
var ret: L = L()
ret.b = "\(obj.a)"
return ret
}
}
var obj1: prot2Struct?
var paramItem = prot1Struct(a: 10)
obj1 = Manage().Do(obj: paramItem)
Also if you want to use it on a Class you can do it like following:
class manageb<T: prot1, L: protRet> {
func Do(obj: T) -> L {
var ret: L = L()
ret.b = "\(obj.a)"
return ret
}
}
var obj1: prot2Struct?
var paramItem = prot1Struct(a: 10)
let classB = manageb<prot1Struct, prot2Struct>()
obj1 = classB.Do(obj: paramItem)

Avoiding Type Erasure when implementing the Repository Pattern

I'm trying to implement the repository pattern in Swift in a generic way. The problem that I'm currently facing is, that it seems like I have to write type erasure wrappers for all my repositories. Am I missing something here? Is there a better way to do this or to make the compiler happy at this point?
// 1
class Item {}
// 2
protocol Repository {
associatedtype T
}
// 3
protocol AnyItemRepository: Repository where T == Item {}
// 4
class ItemRepository: AnyItemRepository {
static let shared = ItemRepository()
private init() {}
}
// 5
class ViewController {
// 6
var itemRepository: AnyItemRepository? = ItemRepository.shared
}
One of many entities
The base repository interface that can be extended if necessary or implemented directly
A special item repository interface that guarantees additional functionality on top of the base repository
Concrete repository implementation for a specific entity type
Some class that needs to access the data
Dependency to any item repository. The compiler errors on this line: Protocol 'AnyItemRepository' can only be used as a generic constraint because it has Self or associated type requirements
You don't need the AnyItemRepository type. Just write extension methods on Repository like so:
public extension Repository where T == Item {
func doSomethingSpecial(with items: [Item]) {
// blah blah
}
}
In your view controller, you can't use Repository or AnyItemRepository in this way because these are generic type constraints. You must either use a concrete type or generically parameterize ViewController.
class RepositoryViewController<R>: UIViewController where R: Repository, R.T == Item {
var itemRepository: R { get }
}
class ViewController: RepositoryViewController<ItemRepository> {
override var itemRepository: ItemRepository {
return ItemRepository.shared
}
}
(The above is untested pseudocode designed to give you the gist. It has never been run by anyone ever and may not even compile.)
It's not quite clear what you're planning to use this Repository for. Repository is designed for environments that have certain characteristics (such as strong linkages to row-style databases). I'll discuss other patterns that often work better in common iOS apps (and even smaller Mac apps).
I usually strongly dislike type erasure, and it often indicates a design problem. But in this case I think type erasure might be a reasonable answer.
So we'll start with the kinds of items we can store. They probably are going to need to have some kind of identifier, and be hashable for many common backends (but maybe you won't need hashing; if not, take it out).
protocol Identified {
associatedtype ID
var id: ID { get }
}
typealias Storable = Identified & Hashable
And then there are things that can act as storage. There is no such thing as a "RepositoryStorage." This is just saying "if you comply with these requirements, then Repository can use you."
protocol RepositoryStorage {
associatedtype Item: Storable
func get(identifier: Item.ID) -> Item?
func add(item: Item)
func delete(identifier: Item.ID)
func allItems() -> [Item]
}
And then the standard, somewhat tedious, type-eraser pattern (there's another pattern that stdlib uses that even more tedious, but this one is good enough for most cases).
// I'm making it a class because I assume it'll have reference semantics.
final class Respository<Item: Storable>: RepositoryStorage {
init<Storage: RepositoryStorage>(storage: Storage) where Storage.Item == Item {
self._get = storage.get
self._add = storage.add
self._delete = storage.delete
self._allItems = storage.allItems
}
let _get: (Item.ID) -> Item?
func get(identifier: Item.ID) -> Item? { return _get(identifier) }
let _add: (Item) -> Void
func add(item: Item) { _add(item) }
let _delete: (Item.ID) -> Void
func delete(identifier: Item.ID) { _delete(identifier) }
let _allItems: () -> [Item]
func allItems() -> [Item] { return _allItems() }
}
So that's fine, it's a general-purpose Repository. And this makes reasonable sense if you're dealing with a large set of items that are probably going to be stored in a SQLite database. But in my experience it is often both too much and too little. Too much if it's just a few items, and too little if you have a lot of items and so probably have to do a lot more than just CRUD. You probably need Query and Join, and then this isn't enough. (Making something flexible in one direction often cuts you off in other directions. There's no universal "generic.")
So can we make it simpler for the case when it's really just a few items? Here's an approach I use regularly:
class DataStore<Key: Hashable & Codable, Value: Codable> {
let identifier: String
private(set) var storage: DataStorage
var dictionary: [Key: Value] {
didSet {
storage[identifier] = try? PropertyListEncoder().encode(dictionary)
}
}
init(identifier: String, storage: DataStorage = UserDefaults.standard) {
self.identifier = identifier
self.storage = storage
let data = storage[identifier] ?? Data()
self.dictionary = (try? PropertyListDecoder().decode([Key: Value].self,
from: data)) ?? [:]
}
subscript(key: Key) -> Value? {
get { return dictionary[key] }
set { dictionary[key] = newValue }
}
}
A DataStore acts as dictionary that you can store key/value pairs in:
let ds = DataStore<String: Item>(identifier: "Item")
ds["first"] = item
It can store anything Codable. With a little modification, you could switch it from a dictionary-like interface to an array or set-like interface; I just usually want a dictionary.
When it's updated, it encodes the entire data store to its storage as Data:
protocol DataStorage {
subscript(identifier: String) -> Data? { get set }
}
This is very fast and efficient for dozens of items. I might rethink if if there were over a hundred items, and it would be inappropriate for hundreds or more items. But for small sets, this is very, very fast.
A very common DataStorage is UserDefaults:
extension UserDefaults: DataStorage {
subscript(identifier: String) -> Data? {
get { return data(forKey: identifier) }
set { set(newValue, forKey: identifier) }
}
}
The key lesson is that this gets rid of all the type-erasure hoop-jumping by creating a common currency (Data) that lower layers work exclusively with. Any time you can do that, separating the upper-layer generic interface from a lower-layer non-generic interface, you'll save yourself a lot of tedium.
This may or may not work for your situation. It's tailored to key/value stores rather than databases, and it's designed to be read much more often than written. But for that usage, it's much simpler and generally faster than the Repository pattern. That's the kind of trade-offs I mean when I say that it's important to know your use case.

Using diff in an array of objects that conform to a protocol

I'm experimenting with using Composition instead of Inheritance and I wanted to use diff on an array of objects that comply with a given protocol.
To do so, I implemented a protocol and made it comply with Equatable:
// Playground - noun: a place where people can play
import XCPlayground
import Foundation
protocol Field:Equatable {
var content: String { get }
}
func ==<T: Field>(lhs: T, rhs: T) -> Bool {
return lhs.content == rhs.content
}
func ==<T: Field, U: Field>(lhs: T, rhs: U) -> Bool {
return lhs.content == rhs.content
}
struct First:Field {
let content:String
}
struct Second:Field {
let content:String
}
let items:[Field] = [First(content: "abc"), Second(content: "cxz")] // 💥 boom
But I've soon discovered that:
error: protocol 'Field' can only be used as a generic constraint because it has Self or associated type requirements
I understand why since Swift is a type-safe language that needs to be able to know the concrete type of these objects at anytime.
After tinkering around, I ended up removing Equatable from the protocol and overloading the == operator:
// Playground - noun: a place where people can play
import XCPlayground
import Foundation
protocol Field {
var content: String { get }
}
func ==(lhs: Field, rhs: Field) -> Bool {
return lhs.content == rhs.content
}
func ==(lhs: [Field], rhs: [Field]) -> Bool {
return (lhs.count == rhs.count) && (zip(lhs, rhs).map(==).reduce(true, { $0 && $1 })) // naive, but let's go with it for the sake of the argument
}
struct First:Field {
let content:String
}
struct Second:Field {
let content:String
}
// Requirement #1: direct object comparison
print(First(content: "abc") == First(content: "abc")) // true
print(First(content: "abc") == Second(content: "abc")) // false
// Requirement #2: being able to diff an array of objects complying with the Field protocol
let array1:[Field] = [First(content: "abc"), Second(content: "abc")]
let array2:[Field] = [Second(content: "abc")]
print(array1 == array2) // false
let outcome = array1.diff(array2) // 💥 boom
error: value of type '[Field]' has no member 'diff'
From here on, I'm a bit lost to be honest. I read some great posts about type erasure but even the provided examples suffered from the same issue (which I assume is the lack of conformance to Equatable).
Am I right? And if so, how can this be done?
UPDATE:
I had to stop this experiment for a while and totally forgot about a dependency, sorry! Diff is a method provided by SwiftLCS, an implementation of the longest common subsequence (LCS) algorithm.
TL;DR:
The Field protocol needs to comply with Equatable but so far I have not been able to do this. I need to be able to create an array of objects that comply to this protocol (see the error in the first code block).
Thanks again
The problem comes from a combination of the meaning of the Equatable protocol and Swift’s support for type overloaded functions.
Let’s take a look at the Equatable protocol:
protocol Equatable
{
static func ==(Self, Self) -> Bool
}
What does this mean? Well it’s important to understand what “equatable” actually means in the context of Swift. “Equatable” is a trait of a structure or class that make it so that any instance of that structure or class can be compared for equality with any other instance of that structure or class. It says nothing about comparing it for equality with an instance of a different class or structure.
Think about it. Int and String are both types that are Equatable. 13 == 13 and "meredith" == "meredith". But does 13 == "meredith"?
The Equatable protocol only cares about when both things to be compared are of the same type. It says nothing about what happens when the two things are of different types. That’s why both arguments in the definition of ==(::) are of type Self.
Let’s look at what happened in your example.
protocol Field:Equatable
{
var content:String { get }
}
func ==<T:Field>(lhs:T, rhs:T) -> Bool
{
return lhs.content == rhs.content
}
func ==<T:Field, U:Field>(lhs:T, rhs:U) -> Bool
{
return lhs.content == rhs.content
}
You provided two overloads for the == operator. But only the first one has to do with Equatable conformance. The second overload is the one that gets applied when you do
First(content: "abc") == Second(content: "abc")
which has nothing to do with the Equatable protocol.
Here’s a point of confusion. Equability across instances of the same type is a lower requirement than equability across instances of different types when we’re talking about individually bound instances of types you want to test for equality. (Since we can assume both things being tested are of the same type.)
However, when we make an array of things that conform to Equatable, this is a higher requirement than making an array of things that can be tested for equality, since what you are saying is that every item in the array can be compared as if they were both of the same type. But since your structs are of different types, you can’t guarantee this, and so the code fails to compile.
Here’s another way to think of it.
Protocols without associated type requirements, and protocols with associated type requirements are really two different animals. Protocols without Self basically look and behave like types. Protocols with Self are traits that types themselves conform to. In essence, they go “up a level”, like a type of type. (Related in concept to metatypes.)
That’s why it makes no sense to write something like this:
let array:[Equatable] = [5, "a", false]
You can write this:
let array:[Int] = [5, 6, 7]
or this:
let array:[String] = ["a", "b", "c"]
or this:
let array:[Bool] = [false, true, false]
Because Int, String, and Bool are types. Equatable isn’t a type, it’s a type of a type.
It would make “sense” to write something like this…
let array:[Equatable] = [Int.self, String.self, Bool.self]
though this is really stretching the bounds of type-safe programming and so Swift doesn’t allow this. You’d need a fully flexible metatyping system like Python’s to express an idea like that.
So how do we solve your problem? Well, first of all realize that the only reason it makes sense to apply SwiftLCS on your array is because, at some level, all of your array elements can be reduced to an array of keys that are all of the same Equatable type. In this case, it’s String, since you can get an array keys:[String] by doing [Field](...).map{ $0.content }. Perhaps if we redesigned SwiftLCS, this would make a better interface for it.
However, since we can only compare our array of Fields directly, we need to make sure they can all be upcast to the same type, and the way to do that is with inheritance.
class Field:Equatable
{
let content:String
static func == (lhs:Field, rhs:Field) -> Bool
{
return lhs.content == rhs.content
}
init(_ content:String)
{
self.content = content
}
}
class First:Field
{
init(content:String)
{
super.init(content)
}
}
class Second:Field
{
init(content:String)
{
super.init(content)
}
}
let items:[Field] = [First(content: "abc"), Second(content: "cxz")]
The array then upcasts them all to type Field which is Equatable.
By the way, ironically, the “protocol-oriented” solution to this problem actually still involves inheritance. The SwiftLCS API would provide a protocol like
protocol LCSElement
{
associatedtype Key:Equatable
var key:Key { get }
}
We would specialize it with a superclass
class Field:LCSElement
{
let key:String // <- this is what specializes Key to a concrete type
static func == (lhs:Field, rhs:Field) -> Bool
{
return lhs.key == rhs.key
}
init(_ key:String)
{
self.key = key
}
}
and the library would use it as
func LCS<T: LCSElement>(array:[T])
{
array[0].key == array[1].key
...
}
Protocols and Inheritance are not opposites or substitutes for one another. They complement each other.
I know this is probably now what you want but the only way I know how to make it work is to introduce additional wrapper class:
struct FieldEquatableWrapper: Equatable {
let wrapped: Field
public static func ==(lhs: FieldEquatableWrapper, rhs: FieldEquatableWrapper) -> Bool {
return lhs.wrapped.content == rhs.wrapped.content
}
public static func diff(_ coll: [Field], _ otherCollection: [Field]) -> Diff<Int> {
let w1 = coll.map({ FieldEquatableWrapper(wrapped: $0) })
let w2 = otherCollection.map({ FieldEquatableWrapper(wrapped: $0) })
return w1.diff(w2)
}
}
and then you can do
let outcome = FieldEquatableWrapper.diff(array1, array2)
I don't think you can make Field to conform to Equatable at all as it is designed to be "type-safe" using Self pseudo-class. And this is one reason for the wrapper class. Unfortunately there seems to be one more issue that I don't know how to fix: I can't put this "wrapped" diff into Collection or Array extension and still make it support heterogenous [Field] array without compilation error:
using 'Field' as a concrete type conforming to protocol 'Field' is not supported
If anyone knows a better solution, I'm interested as well.
P.S.
In the question you mention that
print(First(content: "abc") == Second(content: "abc")) // false
but I expect that to be true given the way you defined your == operator