568224 (4) [Avatar] Offline
#1
I'll try again. Spent some time writing about chapter 5 and submitting it to find that the forum failed to retain my work. If it happens again, I'll abandon it
568224 (4) [Avatar] Offline
#2
Before a few minor criticisms, I'd like to say that I found the chapter useful, especially as a guide on how to generalise a problem solver using Swift generics. So thank you Mr. Kopec.

My criticisms are around the lack of Swiftyness in your approach.

1. Take the shuffled() procedure. My simplified version below uses the guard statement to eliminate the < 2 element arrays using the very clear swift approach. Then I avoid the unnecessary testing with simpler indexing:

public func shuffled() -> [Element] {       // recommended style
        guard count > 1 else { return self }    // clearer
        var shuffledArray = self
        for i in (1 ..< count).reversed() {
            let position = Int(arc4random_uniform(UInt32(i)))   // now we can't have i==position
            shuffledArray.swapAt(i, position)                   // so simpler code here
        }
        return shuffledArray
    }


2. It is clear that Apple and Swift have had a substantial rethink on objects, encouraging us to use value rather than reference semantics where possible. I recast the Chromosome objects as structs, getting simple copy behaviour and a simpler protocol. Note, in particular the simplification of the crossover() method. I also moved protocol conformance into an extension which seems to be viewed as good practice.

public struct SimpleEquation {
    var x: Int
    var y: Int
}

extension SimpleEquation: Chromosome {
    // Protocol Conformance   1.  var fitness: Double { get }
    public var fitness: Double { // 6x - x^2 + 4y - y^2
        return Double(6 * x - x * x + 4 * y - y * y)
    }
    
    // Protocol Conformance   2.  static func randomInstance() -> Self
    public static func randomInstance() -> SimpleEquation {
        return SimpleEquation(x: Int(arc4random_uniform(100)),
                              y: Int(arc4random_uniform(100)))
    }
    
    // Protocol Conformance   3.  func crossover(with partner: Self) -> (child1: Self, child2: Self)
    public func crossover(with partner: SimpleEquation) -> (child1: SimpleEquation, child2: SimpleEquation) {
        return (child1: SimpleEquation(x: self.x, y: partner.y),
                child2: SimpleEquation(x: partner.x, y: self.y))
    }
    
    // Protocol Conformance   4.  mutating func mutate()
    public mutating func mutate() {
        if drand48() > 0.5 { // mutate x
            if drand48() > 0.5 { x += 1 }
            else { x -= 1 }
        } else { // otherwise mutate y
            if drand48() > 0.5 { y += 1 }
            else { y -= 1 }
        }
    }
    
    // Protocol Conformance   5.  func prettyPrint()
    public func prettyPrint() {
        print("x:\(x) y:\(y) fitness:\(fitness)")
    }
}


3. Finally, in the SendMoreMoney struct, I added a line to the fitness() method to eliminate solutions with leading zeros which would not usually be acceptable in solving such puzzles:

if s == 0 || m == 0 { return 0.0 }


David Kopec (8) [Avatar] Offline
#3
Thanks for reading and thanks for the good suggestions.

1. Those look like some good improvements. Luckily, as of Swift 4.2 there is now a shuffled() method in the standard library, making the implementation in the book irrelevant.

2. This definitely looks like an improvement in the clarity of the crossover() function. It comes from the default constructor for the struct being more clear. Adding a constructor to the class version would have a similar clarity. I dispute that having protocols in an extension is always more clear (sometimes it's nice to see right when you start reading a struct/class/enum what protocols it conforms to). I also dispute the dogma in the community against using reference types. To quote Chris Lattner, the creator of Swift:
"What irritates me is when people say classes are bad. Or subclassing is bad. That's totally false. Classes are super important. Reference semantics are super important. If anything, the thing that's wrong is to say, one thing is bad and the other thing is good. These are all different tools in our toolbox, and they're used to solve different kinds of problems. And the bad thing is to say, classes are awesome and should be used to solve all problems, or structs are awesome and should be used to solve all problems. I think a lot of people that say subclassing is bad are reacting to the “overclassification” of Objective-C." (source: https://oleb.net/blog/2017/06/chris-lattner-wwdc-swift-panel/)

3. I left leading zeros in the solution so that it would resolve more quickly in the low-performance environments of Swift Playgrounds.

As an aside, I would encourage you not to get into too much dogma about what is "Swifty." Swift is 4 years old and its style will likely change significantly over time, regardless of what is considered "best practice" today at this early stage. Java is certainly not written in the same way today as it was in 1999 when it was 4 years old.

I'm not religious about writing my code in a particular style (read Swifty). I'm more interested in writing clear code, and I think you have found several ways to make this code more clear. Thank you!
568224 (4) [Avatar] Offline
#4
Actually, I agree broadly with your response to 2 above. There is always a call to be made on reference versus value but in the simple cases treated here, value must win hands-down. I learned one useful lesson here: we can't sometimes use structs and sometimes classes with mutating functions such as mutate() unless we anticipate this is the protocol with the mutating keyword - small flaw in Swift I would say.

As to swiftyness, I bought your book because it was specifically about Swift and I expect swifty features to dominate - otherwise any algorithm book already does the trick. I'm familiar with most of the algorithms you discuss and wanted to see how Swift could be leveraged to implement them, as, for instance, with generics where you succeed admirably but also functional and protocol styles of coding where I was less taken with your solutions.

I'm plowing on with other chapters and am generally satisfied that the book is teaching me something.
Thanks.
David Kopec (8) [Avatar] Offline
#5
Thanks for the reply. You're right, it would probably be better if it were a struct.

With regards to Swift, my goal was to provide an emphasis on examples of language features (which won't change) and the standard library rather than any currently in-vogue style (which may). For example, I aimed to cover protocol-oriented programming (Chapter 4), generics (Chapters 2, 3, 4, 5, 6), and functional approaches (use of first-class functions, value types, map, filter, and reduce in various places throughout the book). Swift is a multi-paradigm language and you will also find examples that use object-oriented solutions (chapter 3), and procedural solutions. As explained in the Introduction, this was how I approached the book—trying to cover several different styles/features rather than being dogmatic about any specific one. I hope readers who read the whole book will see several different ways of approaching problems in Swift, and can decide after which style they like best, even if that style is not currently considered "Swifty."

I think our discussion in this thread is about the difference between style (Swifty) and language features (Swift) which are two different but related things. The former is your choice about when to use the latter. I note your feedback, and if there is another edition I will consider emphasizing style more.

My guess is you will enjoy Chapter 4 if you have not already read it since it focuses on protocol-oriented programming which is very "Swifty."

I hope you enjoy the book, thanks for reading, and sincerely thank you for the suggestions, which I will probably incorporate if there is another edition.