Last active
June 15, 2022 02:35
-
-
Save pitt500/9116ebebcc2272ee3267d14660f14d7c to your computer and use it in GitHub Desktop.
This is a demo code extracted from https://developer.apple.com/wwdc22/110353
This file contains hidden or 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
import Foundation | |
protocol Animal { | |
associatedtype CommodityType: Food | |
associatedtype FeedType: AnimalFeed | |
var isHungry: Bool { get } | |
func produce() -> CommodityType | |
func eat(_: FeedType) | |
} | |
struct Chicken: Animal { | |
var isHungry: Bool = Bool.random() | |
func produce() -> Egg { | |
Egg() | |
} | |
func eat(_ animalFeed: Scratch) { | |
print(animalFeed) | |
} | |
} | |
struct Cow: Animal { | |
var isHungry: Bool = Bool.random() | |
func produce() -> Milk { | |
Milk() | |
} | |
func eat(_ animalFeed: Hay) { | |
print(animalFeed) | |
} | |
} | |
protocol Food {} | |
struct Egg: Food {} | |
struct Milk: Food {} | |
protocol AnimalFeed { | |
// Without where clause, this protocol is too general to accurately model | |
// the desired relationship between our concrete types. | |
// Now Swift will break the loop type and understand the relationship between | |
// AnimalFeed and Crop. | |
associatedtype CropType: Crop where CropType.FeedType == Self | |
static func grow() -> CropType | |
} | |
struct Hay: AnimalFeed { | |
static func grow() -> Alfalfa { Alfalfa() } | |
} | |
struct Scratch: AnimalFeed { | |
static func grow() -> Millet { Millet() } | |
} | |
protocol Crop { | |
// Without where clause, this protocol is too general to accurately model | |
// the desired relationship between our concrete types. | |
// Now Swift will break the loop type and understand the relationship between | |
// AnimalFeed and Crop. | |
associatedtype FeedType: AnimalFeed where FeedType.CropType == Self | |
func harvest() -> FeedType | |
} | |
struct Alfalfa: Crop { | |
func harvest() -> Hay { Hay() } | |
} | |
struct Millet: Crop { | |
func harvest() -> Scratch { Scratch() } | |
} | |
// MARK: - Type erasure - 03:16 | |
// We have erased the relationship between the concrete animal type | |
// and associated commodity type by replacing them with any Animal | |
// and any Food | |
// Type erasure happens when an associated type appears in the result of a function | |
// Declaration. | |
struct Farm { | |
var animals: [any Animal] | |
func produceCommodities() -> [any Food] { | |
let foodList = animals.map { animal in | |
animal.produce() | |
} | |
return foodList | |
} | |
} | |
let animals: [any Animal] = [Cow(), Chicken(), Chicken()] | |
let farm = Farm(animals: animals) | |
print(farm.produceCommodities()) | |
// But it doesn't work when associated type appears in the parameter list. The concrete | |
// type for the parameter is unknown. There's no guarantee that right concrete type is provided. | |
let animalFeed: [any AnimalFeed] = [Hay(), Scratch()] | |
animals.map { animal in | |
/* | |
animal.eat(???) | |
*/ | |
// error: Associated type 'FeedType' can only be used with a concrete type or generic parameter base | |
// error: Member 'eat' cannot be used on value of type 'any Animal'; consider using a generic constraint (or opaque type) instead | |
} | |
// MARK: - Hiding implementation details - 8:35 | |
extension Farm { | |
// Too much details exposed! | |
// var hungryAnimals: LazyFilterSequence<[any Animal]> { | |
// animals.lazy.filter(\.isHungry) | |
// } | |
// Now it hides too much info about the element types ! | |
// var hungryAnimals: some Collection { | |
// animals.lazy.filter(\.isHungry) | |
// } | |
// Great balance ✅ not exposing too much, but not hiding important info! | |
var hungryAnimals: some Collection<any Animal> { | |
animals.lazy.filter(\.isHungry) | |
} | |
func feedAnimals() { | |
// animals is just read once and then discarded. This is inefficient for | |
// large data. Let's use a lazy collection instead. | |
for animal in animals { | |
print("\(animal) is eating") | |
} | |
} | |
} | |
// In Swift 5.7 you can declare primary associated types between brackets. | |
// The associated types that work best as primary associated types are those that are | |
// usually provided by the caller, such as an Element type of a collection, as opposed | |
// to implementation details, such as collection's iterator type. | |
protocol SomeProtocol<Element> { | |
associatedtype Element | |
} | |
// You can read this as "SomeProtocol of Element" | |
// You can also use any in the collection to represent multiple concrete types conforming to a protocol: | |
extension Farm { | |
var isLazy: Bool { | |
Bool.random() | |
} | |
var hungryAnimals2: any Collection<any Animal> { | |
if isLazy { | |
return animals.lazy.filter(\.isHungry) | |
} else { | |
return animals.filter(\.isHungry) | |
} | |
} | |
} | |
// MARK: - Identify type relashionships | |
let alfalfa = Hay.grow() | |
let hay = alfalfa.harvest() | |
let cow = Cow() | |
cow.eat(hay) | |
let millet = Scratch.grow() | |
let scratch = millet.harvest() | |
let chicken = Chicken() | |
chicken.eat(scratch) | |
extension Farm { | |
func feedAnimals2() { | |
for animal in hungryAnimals { | |
feedAnimal(animal) | |
} | |
} | |
// Since feedAnimal() needs to work with eat() method of the Animal protocol, | |
// Which as an associated type in consumer position (as parameter), I'm going | |
// to unbox the existential type by declaring that the feedAnimal() method takes | |
// 'some Animal' (a concrete type conforming animal) as paramater. | |
private func feedAnimal(_ animal: some Animal) { // equivalent to feedAnimal<A>(_ animal: A) where A: Animal | |
// Without where clause, crop will be a value of type: | |
// (some Animal).FeedType.CropType. See definitions of Crop and AnimalFeed. | |
let crop = type(of: animal).FeedType.grow() | |
// and feed a value of type: (some Animal).FeedType.CropType.FeedType | |
let feed = crop.harvest() | |
// eat() method expect (some Animal).FeedType only | |
animal.eat(feed) | |
} | |
} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment