Chapter 18: Kotlin Generics - Comparing to Swift Generics

I’m an iOS / Swift developer, so generics aren’t totally new to me. However, Kotlin has some new ideas that Chapter 18 introduces - Generic Interfaces, Star Projection, and Reified Type Parameters.

So, I took the example “moving company” code from this chapter and transformed it into Swift to see where my knowledge gaps are. I’ve included my source code below.

The most obvious difference between the two implementations is Swift’s usage of Value Types and Kotlin’s usage of Reference Types. It makes it possible to hold a reference of the TV object and smash it after it’s been handed over to the expensive mover.

Is there anything else in my Swift implementation that shows a lack of understanding?


protocol Checkable {
    var isOK: Bool { get }
}

protocol Containable {
    associatedtype Item: Checkable
    
    func isFull() -> Bool
    func isEmpty() -> Bool
    
    mutating func add(item: Item)
    mutating func removeItem() -> Item?
    
    func items() -> [ Item ]
    func makeContainer() -> Self
}

final class Mover<Container: Containable> {
    
    private var inOldPlace: [ Container.Item ] = Array()
    private var inTruck: [ Any ] = Array()
    private var inNewPlace: [ Container.Item ] = Array()
    private var failedCheck: [ Container.Item ] = Array()
    
    init(things: [ Container.Item ], truckHeight: Int = (12 * 12))
    {
        inOldPlace.append(contentsOf: things)
    }
    
    func moveToTruck(startingContainer: Container?)
    {
        var current = startingContainer
        
        while !inOldPlace.isEmpty {
            let item = inOldPlace.remove(at: 0)
            
            if item.isOK {
                
                if current == nil {
                    inTruck.append(item)
                    print("Moved your \(item) to the truck")
                }
                else {
                    if current?.isFull() == true {
                        moveContainerToTruck(container: current)
                        current = current?.makeContainer()
                    }
                    current?.add(item: item)
                    print("Packed your \(item)")
                }
                
            }
            else {
                failedCheck.append(item)
                print("Could not move your \(item) to the truck")
            }
        }
        
        if let current {
            moveContainerToTruck(container: current)
        }
    }
    
    func moveToNewPlace()
    {
        let containers = inTruck.compactMap { $0 as? Container }
        for var container in containers {
            while !container.isEmpty() {
                if let item = container.removeItem() {
                    print("Unpacked  your \(item)")
                    tryMoveToNewPlace(item: item)
                }
            }
        }
        
        let items = inTruck.compactMap { $0 as? Container.Item }
        items.forEach { tryMoveToNewPlace(item: $0) }
    }
    
    func finish()
    {
        print("Okay, we are finished! We moved your: \(inNewPlace)")
        if !failedCheck.isEmpty {
            print("But, we need to talk about your: \(failedCheck)")
        }
    }
    
    private func moveContainerToTruck(container: Container?)
    {
        guard let container else {
            return
        }
        
        inTruck.append(container)
        print("Moved a container with your \(container.items()) to the truck")
    }
    
    private func tryMoveToNewPlace(item: Container.Item)
    {
        if item.isOK {
            inNewPlace.append(item)
            print("Moved your \(item) into your new place")
        }
        else {
            failedCheck.append(item)
            print("Could not move your \(item) into your new place")
        }
    }
}

struct CheapThing: Checkable, CustomStringConvertible {
    
    let name: String
    
    var isOK: Bool {
        true
    }
    
    var description: String {
        name
    }
}

struct BreakableThing: Checkable, CustomStringConvertible {
    
    let name: String
    private var isBroken: Bool
    
    var isOK: Bool {
        !isBroken
    }
    
    var description: String {
        name
    }
    
    init(name: String, isBroken: Bool = false)
    {
        self.name = name
        self.isBroken = isBroken
    }
    
    mutating func smash() {
        isBroken = true
    }
}

struct CardboardBox<T: Checkable>: Containable {
    
    private var contents: [ T ] = Array()
    
    func items() -> [ T ] {
        contents
    }
    
    func isFull() -> Bool {
        contents.count == 2
    }
    
    func isEmpty() -> Bool {
        contents.isEmpty
    }
    
    mutating func add(item: T) {
        contents.append(item)
    }
    
    mutating func removeItem() -> T? {
        return contents.popLast()
    }
    
    func makeContainer() -> CardboardBox {
        return CardboardBox()
    }
}

let cheapThings = [
    CheapThing(name: "Cinder block table"),
    CheapThing(name: "Box of old books"),
    CheapThing(name: "Grandma's couch"),
]

let cheapMover = Mover<CardboardBox>(things: cheapThings)
cheapMover.moveToTruck(startingContainer: nil)
cheapMover.moveToNewPlace()
cheapMover.finish()

let breakableThings = [
    BreakableThing(name: "Television"),
    BreakableThing(name: "Mirror"),
    BreakableThing(name: "Guitar")
]

let expensiveMover = Mover<CardboardBox>(things: breakableThings)
expensiveMover.moveToTruck(startingContainer: CardboardBox())
// Can't smash; value type
expensiveMover.moveToNewPlace()
expensiveMover.finish()