Created
December 10, 2022 20:21
-
-
Save BigZaphod/b47fec45ec5e053a7a9c39420b0d25b7 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// The original Container protocol. | |
protocol Container { | |
associatedtype Item | |
func deleteItem(_: Item) | |
} | |
// Let's make a couple of concrete containers that use our protocol: | |
class StringContainer: Container { | |
typealias Item = String | |
func deleteItem(_ item: String) { | |
// `item` is guarenteed to be a `String` instance here | |
} | |
} | |
class IntContainer: Container { | |
typealias Item = Int | |
func deleteItem(_ item: Item) { | |
// `item` is guarenteed to be a `Int` instance here | |
} | |
} | |
// If the Service's API is simple and concrete, we can do things pretty easily: | |
struct StringService { | |
let container: StringContainer | |
// This accepts a `String` instance directly since we know `container` is always a `StringContainer` | |
// so we don't need to deal with anything else or do any casting. | |
func deleteItem(_ item: String) { | |
container.deleteItem(item) | |
} | |
} | |
// If we need multiple instances of the service using different types of containers, we could use generics. | |
// This makes sure that the compiler knows exactly which types we need in each context it is used in: | |
struct ItemService<C: Container, I> where C.Item == I { | |
let container: C | |
// This accepts any item of type `I` which is guarenteed by the `where` rule above to match the container's | |
// `Item` type. So we have no problem compiling here. | |
func deleteItem(_ item: I) { | |
container.deleteItem(item) | |
} | |
} | |
// But when we try to be super generic and allow the service to any type of Container, then the container's | |
// Item type could also be any type of value. Now we start getting confused becuase we don't know at compile | |
// time which types are which: | |
struct AbstractService { | |
let container: any Container | |
// Since `container` is `any Container`, the actual container's associated type could be ANY type at all | |
// and we have no way to know what it is because it depends on what *instance* of a container we assign | |
// to the container property. It could be a StringContainer.. it could be an IntContainer.. or it could | |
// be a `FooContainer` or `BazContainer`... or who knows! | |
func deleteItem(_ item: Any) { | |
// We can't figure out if the type `container` expects is the same type we were given. :( | |
// container.deleteItem(item) --- doesn't compile | |
} | |
} | |
// The core problem here, I think, is that the information the compiler needs to know is hidden inside of the | |
// container's type and Swift doesn't have a reasonable way to get it out (without the newer primary associated | |
// type feature, I guess). | |
// | |
// So, how can we make it available ourselves? | |
// | |
// If the container itself knows, then maybe we can just move the casting to the container! This is easy to do | |
// with helper function added to the Container protocol: | |
extension Container { | |
func deleteAnyItem(_ item: Any) { | |
if let castItem = item as? Item { | |
deleteItem(castItem) | |
} | |
} | |
} | |
// And now we can do what is needed, I think? | |
struct AlternateAbstractService { | |
let container: any Container | |
func deleteItem(_ item: Any) { | |
container.deleteAnyItem(item) | |
} | |
} | |
// Note that this could instead be written with the generic syntax instead of `Any`, but since it eventually has | |
// to do a runtime cast, I'm not entirely sure it buys us anything. Perhaps the compiler could sometimes optimize | |
// things a tiny bit more this way, though: | |
extension Container { | |
func deleteSomeItem<I>(_ item: I) { | |
if let castItem = item as? Item { | |
deleteItem(castItem) | |
} | |
} | |
} | |
struct GenericAbstractService { | |
let container: any Container | |
func deleteItem<I>(_ item: I) { | |
container.deleteAnyItem(item) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment