Slide 1

Slide 1 text

FizzBuzz In Swift; A Talk With 3 Codas Abizer Nasir | @abizern

Slide 2

Slide 2 text

The Setup You've turned up for an interview, they would like you to generate FizzBuzz for the first 100 numbers.

Slide 3

Slide 3 text

FizzBuzz • If the number is a multiple of 3, print "Fizz" • If the number is a multiple of 5, print "Buzz" • If the number is a multiple of 3 and 5, print "FizzBuzz" • Otherwise, just print the number

Slide 4

Slide 4 text

Simple solution that works func fizzBuzzSimple(number: Int) -> String { if number % 15 == 0 { return "FizzBuzz" } else if number % 5 == 0 { return "Buzz" } else if number % 3 == 0 { return "Fizz" } else { return "\(number)" } }

Slide 5

Slide 5 text

Using Pattern Matching func fizzBuzzWithPatternMatching(number: Int) -> String { switch (number % 5, number % 3) { case (0, 0): return "FizzBuzz" case (0, _): return "Buzz" case (_, 0): return "Fizz" default: return "\(number)" } }

Slide 6

Slide 6 text

FizzBuzzBar • The same as FizzBuzz except... • If the number is a multiple of 7, add the string "Bar" • e.g 105 -> "FizzBuzzBar"

Slide 7

Slide 7 text

func fizzBuzzBarWithPatternMatching(number: Int) -> String { switch (number % 7, number % 5, number % 3) { case (0, 0, 0): return "FizzBuzzBar" case (0, 0, _): return "BuzzBar" case (_, 0, 0): return "FizzBuzz" case (0, _, 0): return "FizzBar" case (0, _, _): return "Bar" case (_, 0, _): return "Buzz" case (_, _, 0): return "Fizz" default: return "\(number)" } }

Slide 8

Slide 8 text

This is becoming hard to maintain

Slide 9

Slide 9 text

With three clauses, its simple enough to see on one screen and to reason it's correctness. func fizzBuzzWithPatternMatching(number: Int) -> String { switch (number % 5, number % 3) { case (0, 0): return "FizzBuzz" case (0, _): return "Buzz" case (_, 0): return "Fizz" default: return "\(number)" } }

Slide 10

Slide 10 text

With more, you start thinking about needing tests. func fizzBuzzBarWithPatternMatching(number: Int) -> String { switch (number % 7, number % 5, number % 3) { case (0, 0, 0): return "FizzBuzzBar" case (0, 0, _): return "BuzzBar" case (_, 0, 0): return "FizzBuzz" case (0, _, 0): return "FizzBar" case (0, _, _): return "Bar" case (_, 0, _): return "Buzz" case (_, _, 0): return "Fizz" default: return "\(number)" } }

Slide 11

Slide 11 text

The requirements are likely to change again.

Slide 12

Slide 12 text

Pass options into the function. func fizzBuzzWithOptions(number: Int, options: [(Int, String)]) -> String { var output = "" for (divisor, description) in options { if number % divisor == 0 { output += description } } if output.isEmpty { output = "\(number)" } return output } var simpleOptions = [(3, "Fizz"), (5, "Buzz"), (7, "Bar")]

Slide 13

Slide 13 text

Simpler to call var simpleOptions = [(3, "Fizz"), (5, "Buzz"), (7, "Bar")] results = [String]() for number in 1...105 { results.append(fizzBuzzWithOptions(number, simpleOptions)) } results == kFizzBuzzBar

Slide 14

Slide 14 text

Make an extension to Int so that the number parameter doesn't need to be passed in. extension Int { func fizzBuzzIntExtension(options: [(Int, String)]) -> String { var output = "" for (divisor, description) in options { if self % divisor == 0 { output += description } } if output.isEmpty { output = "\(self)" } return output } }

Slide 15

Slide 15 text

Just as simple to call. var simpleOptions = [(3, "Fizz"), (5, "Buzz"), (7, "Bar")] results = [String]() for number in 1...105 { results.append(number.fizzBuzzIntExtension(simpleOptions)) } results == kFizzBuzzBar

Slide 16

Slide 16 text

FizzBuzzBarFooWhate ver is just a sequence that follows a rule. Sequence. That sounds familiar.

Slide 17

Slide 17 text

SequenceType protocol SequenceType : _Sequence_Type { typealias Generator : GeneratorType func generate() -> Generator } GeneratorType protocol GeneratorType { mutating func next() -> Element? }

Slide 18

Slide 18 text

Define a generator struct FizzBuzzGenerator: GeneratorType { let start: Int let end: Int var number: Int init(start: Int, end: Int) { self.start = start self.end = end self.number = start } typealias Element = String mutating func next() -> Element? { while number <= end { return fizzBuzzBarWithPatternMatching(number++) } return nil } }

Slide 19

Slide 19 text

define a sequence that uses this generator struct FizzBuzzSequenceSimple: SequenceType { let start: Int let end: Int typealias Generator = FizzBuzzGenerator func generate() -> Generator { return FizzBuzzGenerator(start: start, end: end) } } results = [String]() for result in FizzBuzzSequenceSimple(start: 1, end: 105) { results.append(result) } results results == kFizzBuzzBar

Slide 20

Slide 20 text

Let's move the generator inside the sequence struct FizzBuzzSequenceComposed: SequenceType { let generator: FizzBuzzGenerator init(start: Int, end: Int) { generator = FizzBuzzGenerator(start: start, end: end) } struct FizzBuzzGenerator: GeneratorType { let start: Int let end: Int var number: Int init(start: Int, end: Int) { self.start = start self.end = end self.number = start } typealias Element = String mutating func next() -> Element? { while number <= end { return fizzBuzzBarWithPatternMatching(number++) } return nil } } typealias Generator = FizzBuzzGenerator func generate() -> Generator { return generator } }

Slide 21

Slide 21 text

This doesn't look right • There is a lot of state being passed between the values. • There is no way to change the generator without editing the function.

Slide 22

Slide 22 text

GeneratorOf struct GeneratorOf : GeneratorType, SequenceType { init(_ next: () -> T?) init(_ self_: G) mutating func next() -> T? func generate() -> GeneratorOf } Takes a closure, returns the function to use as the generator.

Slide 23

Slide 23 text

Pass the options directly to GeneratorOf struct FizzBuzzSequenceWithOptions: SequenceType { let start: Int let end: Int let options: [(Int, String)] func generate() -> GeneratorOf { var number = self.start return GeneratorOf { while number <= self.end { return fizzBuzzWithOptions(number++, self.options) } return nil } } }

Slide 24

Slide 24 text

This is just as simple to call results = [String]() for result in FizzBuzzSequenceWithOptions(start: 1, end: 105, options: simpleOptions) { results.append(result) } results == kFizzBuzzBar

Slide 25

Slide 25 text

What if they get even sneakier? FizzBuzz doesn't have to be restricted to numbers.

Slide 26

Slide 26 text

Generalized FizzBuzzBarFooWhatever • For numbers, behaves as already described. • For Strings, base the output on the length of the string. • For Arrays, base the output on the length of the array. • For Shapes, base the output on the number of sides. • etc.

Slide 27

Slide 27 text

Start by defining an Interface for the Type to conform to protocol FizzBuzzable { func fizzBuzz(options: [(Int, String)]) -> String }

Slide 28

Slide 28 text

Extend Types to conform to this Interface Int extension Int: FizzBuzzable { func fizzBuzz(options: [(Int, String)]) -> String { var output = "" for (divisor, description) in options { if self % divisor == 0 { output += description } } if output.isEmpty { output = "\(self)" } return output } }

Slide 29

Slide 29 text

String extension String: FizzBuzzable { func fizzBuzz(options: [(Int, String)]) -> String { let length = countElements(self) var output = "" for (divisor, description) in options { if length % divisor == 0 { output += description } } if output.isEmpty { output = "\(self)" } return output } }

Slide 30

Slide 30 text

Define an Array to work on. Arrays in Swift must be typed, but Interfaces are also types. var typedArray: [FizzBuzzable] = [3, 5, 7, 11, "How", "Brown", "Penguin", "Marmoset"] var expected = ["Fizz", "Buzz", "Bar", "11", "Fizz", "Buzz", "Bar", "Marmoset"] results = [String]() for element: FizzBuzzable in typedArray { results.append(element.fizzBuzz(simpleOptions)) } results == expected

Slide 31

Slide 31 text

You can even have different FizzBuzz options for each type var typedArray: [FizzBuzzable] = [3, 5, 7, 11, "How", "Brown", "Penguin", "Marmoset"] results = [String]() for element: FizzBuzzable in typedArray { var result: String if let number = element as? Int { result = element.fizzBuzz([(3, "Fizz")]) } else if let string = element as? String { result = element.fizzBuzz([(3, "Hello"), (5, "Goodbye")]) } else { result = "\(element)" } results.append(result) } expected = ["Fizz", "5", "7", "11", "Hello", "Goodbye", "Penguin", "Marmoset"] results == expected

Slide 32

Slide 32 text

Coda 1. First Person

Slide 33

Slide 33 text

No content

Slide 34

Slide 34 text

No content

Slide 35

Slide 35 text

Please, not Java!

Slide 36

Slide 36 text

It turned out to be not so bad • I know a little Haskell. • It made me a better Objective-C programmer. • It's going to make me a better Swift Programmer. • Swift isn't a purely functional language, but some of the concepts can be applied. • For most people, it’s the Cocoa Frameworks that are the hurdle, not the language.

Slide 37

Slide 37 text

Coda 2. Second Person

Slide 38

Slide 38 text

What can you do about it? Learn A functional Programming Language. I like Haskell, but other products are available: Scala, Clojure, OCAML, F#... Some handy tips you'll pick up and understand will be...

Slide 39

Slide 39 text

Think of Types and Interfaces rather than classes and operations. It's like OOP, but from a different perspective.

Slide 40

Slide 40 text

Aim for immutability in your objects. Structs rather than classes.

Slide 41

Slide 41 text

Write pure functions as much as possible, keep methods that have side effects to a minimum.

Slide 42

Slide 42 text

Don’t just re-write your Objective-C code with Swift Syntax.

Slide 43

Slide 43 text

Coda 3. Third Person

Slide 44

Slide 44 text

“Write the code, change the world”

Slide 45

Slide 45 text

“Find the bugs, file the radars”

Slide 46

Slide 46 text

Don’t give up on Objective-C just yet. You’ll be writing it for a while, and reading it for even longer.

Slide 47

Slide 47 text

Thank You! http://downloads.abizern.org/FizzBuzzery.zip http://learnyouahaskell.com http://book.realworldhaskell.org