Slide 1

Slide 1 text

share your code (click to start)

Slide 2

Slide 2 text

share your code - Thanks to Beren, Ida, and Morgan
 - CocoaPods, 2011, *fundamentally changed* how I developed apps
 - Why write my own custom alert view, when I know someone much smarter than I am has already written one?
 - For the things I know I can write really well, why not share them?
 - It’s only gotten *easier* to share. But how to write code people will love?

Slide 3

Slide 3 text

Brian Gesiak
 @modocache - I’m Brian Gesiak. I go by modocache on GitHub and Twitter.

Slide 4

Slide 4 text

- I’m the creator of Quick, the Swift and Objective-C testing framework.
 - Normally I work at Facebook in New York, but today…

Slide 5

Slide 5 text

compelling code - …I’d like to show some practical, real-world techniques I’ve used to write *compelling* code. - My goal is to write code that makes people think, "I *want* to use this."
 - I think there are two important qualities of compelling code…

Slide 6

Slide 6 text

1. Precise 2. Convenient - Both of these are *subjective* terms.
 - There’s no *silver bullet* that will transform code to embody *both* of these qualities.

Slide 7

Slide 7 text

tradeoffs - Instead, we'll focus on *tradeoffs*: what you'll *gain*, and what you'll *lose* by using each technique.

Slide 8

Slide 8 text

1. Precise 2. Convenient - Let’s start with *precision*.
 - As an example, I'd like to invite all of you to preview a *private beta* of my latest Swift framework.
 - It is a robust, battle-tested, performant framework, for modeling...

Slide 9

Slide 9 text

- ... bananas in your application, and it’s called (click) BananaKit.

Slide 10

Slide 10 text

BananaKit - ... bananas in your application, and it’s called (click) BananaKit.

Slide 11

Slide 11 text

Series A
 B
 C
 D
 - BananaKit has been *years* in the making, made possible by *extensive investments* into the product.

Slide 12

Slide 12 text

Series W
 X
 Y
 Z
 - BananaKit has been *years* in the making, made possible by *extensive investments* into the product.

Slide 13

Slide 13 text

@interface Banana : NSObject /** Whether the banana is peeled. */ @property (readonly) BOOL peeled; /** Whether the banana is delicious. @warning Do not use before the banana is peeled. */ @property (readonly) BOOL delicious; @end - *Early* prototypes modeled bananas as an *Objective-C class*. - A banana could be *peeled*.
 - *Once* it was peeled, it could either be *delicious* or *not*.
 - But you had to peel it *first*, in order to find out.

Slide 14

Slide 14 text

@interface Banana : NSObject /** Whether the banana is peeled. */ @property (readonly) BOOL peeled; /** Whether the banana is delicious. @warning Do not use before the banana is peeled. */ @property (readonly) BOOL delicious; @end - *Early* prototypes modeled bananas as an *Objective-C class*. - A banana could be *peeled*.
 - *Once* it was peeled, it could either be *delicious* or *not*.
 - But you had to peel it *first*, in order to find out.

Slide 15

Slide 15 text

@interface Banana : NSObject /** Whether the banana is peeled. */ @property (readonly) BOOL peeled; /** Whether the banana is delicious. @warning Do not use before the banana is peeled. */ @property (readonly) BOOL delicious; @end - *Early* prototypes modeled bananas as an *Objective-C class*. - A banana could be *peeled*.
 - *Once* it was peeled, it could either be *delicious* or *not*.
 - But you had to peel it *first*, in order to find out.

Slide 16

Slide 16 text

@interface Banana : NSObject /** Whether the banana is peeled. */ @property (readonly) BOOL peeled; /** Whether the banana is delicious. @warning Do not use before the banana is peeled. */ @property (readonly) BOOL delicious; @end - *Early* prototypes modeled bananas as an *Objective-C class*. - A banana could be *peeled*.
 - *Once* it was peeled, it could either be *delicious* or *not*.
 - But you had to peel it *first*, in order to find out.

Slide 17

Slide 17 text

BKBanana *banana = [[BKBanana alloc] init]; if (banana.delicious) { // ... } - Not *precise*.
 - Users could do something wrong: access the `delicious` property *before* the banana was peeled.
 - Only way to avoid that mistake is by *reading documentation*.

Slide 18

Slide 18 text

BKBanana *banana = [[BKBanana alloc] init]; if (banana.delicious) { // ... } Caught "NSInternalInconsistencyException" º - Not *precise*.
 - Users could do something wrong: access the `delicious` property *before* the banana was peeled.
 - Only way to avoid that mistake is by *reading documentation*.

Slide 19

Slide 19 text

enum Banana { /** An unpeeled banana has no taste. */ case Unpeeled /** A peeled banana has a boolean to indicate whether it's delicious. */ case Peeled(delicious: Bool) } - Thanks to Swift, BananaKit has become much more precise.
 - Uses an `enum` to express what used to be communicated via documentation.
 - It has two cases: unpeeled and peeled.
 - The `delicious` boolean is only available in the case of peeled bananas.

Slide 20

Slide 20 text

enum Banana { /** An unpeeled banana has no taste. */ case Unpeeled /** A peeled banana has a boolean to indicate whether it's delicious. */ case Peeled(delicious: Bool) } - Thanks to Swift, BananaKit has become much more precise.
 - Uses an `enum` to express what used to be communicated via documentation.
 - It has two cases: unpeeled and peeled.
 - The `delicious` boolean is only available in the case of peeled bananas.

Slide 21

Slide 21 text

enum Banana { /** An unpeeled banana has no taste. */ case Unpeeled /** A peeled banana has a boolean to indicate whether it's delicious. */ case Peeled(delicious: Bool) } - Thanks to Swift, BananaKit has become much more precise.
 - Uses an `enum` to express what used to be communicated via documentation.
 - It has two cases: unpeeled and peeled.
 - The `delicious` boolean is only available in the case of peeled bananas.

Slide 22

Slide 22 text

let banana = Banana.Unpeeled switch banana { case .Unpeeled: // ... case .Peeled(let delicious): if (delicious) { // ... } } - That extra precision *does* come at a cost.
 - We have to enumerate the unpeeled and peeled cases.
 - So it takes more lines of code to access the `delicious` attribute.

Slide 23

Slide 23 text

let banana = Banana.Unpeeled switch banana { case .Unpeeled: // ... case .Peeled(let delicious): if (delicious) { // ... } } - That extra precision *does* come at a cost.
 - We have to enumerate the unpeeled and peeled cases.
 - So it takes more lines of code to access the `delicious` attribute.

Slide 24

Slide 24 text

let banana = Banana.Unpeeled switch banana { case .Unpeeled: // ... case .Peeled(let delicious): if (delicious) { // ... } } - That extra precision *does* come at a cost.
 - We have to enumerate the unpeeled and peeled cases.
 - So it takes more lines of code to access the `delicious` attribute.

Slide 25

Slide 25 text

Banana [[ if } let banana = Banana.Unpeeled switch banana { case .Unpeeled: // ... case .Peeled(let delicious): if (delicious) { // ... } } - On the other hand, the Objective-C implementation was shorter, but only because it made a dangerous assumption: that delicious was available in all cases. - This is 1 example of using types to better convey our code's intention.
 - When I write documentation that warns the user against doing something, or insists that the library be used in a certain way, I try to examine whether that could be better expressed with types, since those are checked at compile-time.
 - Example of expressing intent w/ types is BananaKit's persistence layer...

Slide 26

Slide 26 text

Banana *banana = [[Banana alloc] init]; if (banana.delicious) { // ... } let switch case case } } - On the other hand, the Objective-C implementation was shorter, but only because it made a dangerous assumption: that delicious was available in all cases. - This is 1 example of using types to better convey our code's intention.
 - When I write documentation that warns the user against doing something, or insists that the library be used in a certain way, I try to examine whether that could be better expressed with types, since those are checked at compile-time.
 - Example of expressing intent w/ types is BananaKit's persistence layer...

Slide 27

Slide 27 text

MonkeyDB - ...MonkeyDB.

Slide 28

Slide 28 text

@interface MonkeyDB : NSObject /** Creates a database backed by the store at the given URL. */ - (instancetype)initWithURL:(NSURL *)url; /** Loads a monkey with the given name, or nil if one doesn't exist. */ - (Monkey *)monkeyWithName:(NSString *)name error:(NSError **)error; @end - This is an early version of the API. - (click) A monkey DB is initialized with the URL at which it saves its records.
 - (click) You can read a record representing a monkey from the database.

Slide 29

Slide 29 text

@interface MonkeyDB : NSObject /** Creates a database backed by the store at the given URL. */ - (instancetype)initWithURL:(NSURL *)url; /** Loads a monkey with the given name, or nil if one doesn't exist. */ - (Monkey *)monkeyWithName:(NSString *)name error:(NSError **)error; @end - This is an early version of the API. - (click) A monkey DB is initialized with the URL at which it saves its records.
 - (click) You can read a record representing a monkey from the database.

Slide 30

Slide 30 text

@interface MonkeyDB : NSObject /** Creates a database backed by the store at the given URL. */ - (instancetype)initWithURL:(NSURL *)url; /** Loads a monkey with the given name, or nil if one doesn't exist. */ - (Monkey *)monkeyWithName:(NSString *)name error:(NSError **)error; @end - This is an early version of the API. - (click) A monkey DB is initialized with the URL at which it saves its records.
 - (click) You can read a record representing a monkey from the database.

Slide 31

Slide 31 text

@interface Tricycle : NSObject @property (readonly) NSString *brandName; @end - It’s common knowledge that many monkeys own tricycles—it’s their primary mode of transportation.
 - And the brand of the tricycle is *very* important—as a monkey, your street cred *hinges* on what brand of tricycle you’re riding.

Slide 32

Slide 32 text

@interface MonkeyDB (Tricycles) /** Looks up whether the given monkey owns a tricycle. If the monkey owns one, returns the tricycle. If not, returns nil. If an error occurs, the error pointer will be populated. */ - (Tricycle *)tricycleForMonkey:(Monkey *)monkey error:(NSError **)error; @end - Because they’re so important, MonkeyDB can, of course, load tricycles from disk as well.
 - This code worked, but it wasn't precise. (click) Notice the written warnings in the documentation.
 - *If* the monkey owns a tricycle, that tricycle is returned, and the error *should* be nil. If the monkey *doesn’t* own a tricycle, then *nil* is returned, and the error should *not* be nil.

Slide 33

Slide 33 text

@interface MonkeyDB (Tricycles) /** Looks up whether the given monkey owns a tricycle. If the monkey owns one, returns the tricycle. If not, returns nil. If an error occurs, the error pointer will be populated. */ - (Tricycle *)tricycleForMonkey:(Monkey *)monkey error:(NSError **)error; @end - Because they’re so important, MonkeyDB can, of course, load tricycles from disk as well.
 - This code worked, but it wasn't precise. (click) Notice the written warnings in the documentation.
 - *If* the monkey owns a tricycle, that tricycle is returned, and the error *should* be nil. If the monkey *doesn’t* own a tricycle, then *nil* is returned, and the error should *not* be nil.

Slide 34

Slide 34 text

MonkeyDB .monkey(name: String) - Let’s take a look at how this API was used to get the brand name of a monkey's tricycle.
 - This is done in three steps: first, we retrieve the monkey from the database, using its name. - (click) Then, we retrieve the monkey’s tricycle from the database.
 - (click) Then, we return the tricycle’s brand name.

Slide 35

Slide 35 text

MonkeyDB .monkey(name: String) MonkeyDB .tricycle(monkey: Monkey) - Let’s take a look at how this API was used to get the brand name of a monkey's tricycle.
 - This is done in three steps: first, we retrieve the monkey from the database, using its name. - (click) Then, we retrieve the monkey’s tricycle from the database.
 - (click) Then, we return the tricycle’s brand name.

Slide 36

Slide 36 text

MonkeyDB .monkey(name: String) MonkeyDB .tricycle(monkey: Monkey) Tricycle.brandName - Let’s take a look at how this API was used to get the brand name of a monkey's tricycle.
 - This is done in three steps: first, we retrieve the monkey from the database, using its name. - (click) Then, we retrieve the monkey’s tricycle from the database.
 - (click) Then, we return the tricycle’s brand name.

Slide 37

Slide 37 text

NSURL *url = [NSURL fileURLWithPath:@".monkeydb"]; MonkeyDB *database = [[MonkeyDB alloc] initWithURL:url]; NSError *monkeyError = nil; Monkey *monkey = [database monkeyWithName:@"Peepers" error:&monkeyError]; if (monkey == nil || monkeyError != nil) { // ...error handling. return nil; } NSError *tricycleError = nil; Tricycle *tricycle = [database tricycleForMonkey:monkey error:&tricycleError]; if (tricycle == nil || tricycleError != nil) { - Here’s the code for getting the monkey’s tricycle’s brand name.
 - (click) First, we initialize the database with a URL. This is where the monkeys are stored. - (click) Then, we grab a monkey, named “Peepers”, from the database.
 - (click) It’s possible “Peepers” doesn’t exist in the database. In that case, we short-circuit: we perform error handling, and return nil. - (click) If Peepers *does* exist in the database, we next try to grab his tricycle. - (click) Again, we have to deal with the failure case: if Peepers doesn’t have a tricycle, we *once again* perform error handling, and return nil. - (click) *If* Peepers exists in the database, and *if* he has a tricycle in the database, *then* we return the brand name of that tricycle.
 - Notice how much code we had to write--and that's with the error handling omitted. If we wanted to surface those errors somehow, we'd need to write even more code.

Slide 38

Slide 38 text

NSURL *url = [NSURL fileURLWithPath:@".monkeydb"]; MonkeyDB *database = [[MonkeyDB alloc] initWithURL:url]; NSError *monkeyError = nil; Monkey *monkey = [database monkeyWithName:@"Peepers" error:&monkeyError]; if (monkey == nil || monkeyError != nil) { // ...error handling. return nil; } NSError *tricycleError = nil; Tricycle *tricycle = [database tricycleForMonkey:monkey error:&tricycleError]; if (tricycle == nil || tricycleError != nil) { - Here’s the code for getting the monkey’s tricycle’s brand name.
 - (click) First, we initialize the database with a URL. This is where the monkeys are stored. - (click) Then, we grab a monkey, named “Peepers”, from the database.
 - (click) It’s possible “Peepers” doesn’t exist in the database. In that case, we short-circuit: we perform error handling, and return nil. - (click) If Peepers *does* exist in the database, we next try to grab his tricycle. - (click) Again, we have to deal with the failure case: if Peepers doesn’t have a tricycle, we *once again* perform error handling, and return nil. - (click) *If* Peepers exists in the database, and *if* he has a tricycle in the database, *then* we return the brand name of that tricycle.
 - Notice how much code we had to write--and that's with the error handling omitted. If we wanted to surface those errors somehow, we'd need to write even more code.

Slide 39

Slide 39 text

NSURL *url = [NSURL fileURLWithPath:@".monkeydb"]; MonkeyDB *database = [[MonkeyDB alloc] initWithURL:url]; NSError *monkeyError = nil; Monkey *monkey = [database monkeyWithName:@"Peepers" error:&monkeyError]; if (monkey == nil || monkeyError != nil) { // ...error handling. return nil; } NSError *tricycleError = nil; Tricycle *tricycle = [database tricycleForMonkey:monkey error:&tricycleError]; if (tricycle == nil || tricycleError != nil) { - Here’s the code for getting the monkey’s tricycle’s brand name.
 - (click) First, we initialize the database with a URL. This is where the monkeys are stored. - (click) Then, we grab a monkey, named “Peepers”, from the database.
 - (click) It’s possible “Peepers” doesn’t exist in the database. In that case, we short-circuit: we perform error handling, and return nil. - (click) If Peepers *does* exist in the database, we next try to grab his tricycle. - (click) Again, we have to deal with the failure case: if Peepers doesn’t have a tricycle, we *once again* perform error handling, and return nil. - (click) *If* Peepers exists in the database, and *if* he has a tricycle in the database, *then* we return the brand name of that tricycle.
 - Notice how much code we had to write--and that's with the error handling omitted. If we wanted to surface those errors somehow, we'd need to write even more code.

Slide 40

Slide 40 text

NSURL *url = [NSURL fileURLWithPath:@".monkeydb"]; MonkeyDB *database = [[MonkeyDB alloc] initWithURL:url]; NSError *monkeyError = nil; Monkey *monkey = [database monkeyWithName:@"Peepers" error:&monkeyError]; if (monkey == nil || monkeyError != nil) { // ...error handling. return nil; } NSError *tricycleError = nil; Tricycle *tricycle = [database tricycleForMonkey:monkey error:&tricycleError]; if (tricycle == nil || tricycleError != nil) { - Here’s the code for getting the monkey’s tricycle’s brand name.
 - (click) First, we initialize the database with a URL. This is where the monkeys are stored. - (click) Then, we grab a monkey, named “Peepers”, from the database.
 - (click) It’s possible “Peepers” doesn’t exist in the database. In that case, we short-circuit: we perform error handling, and return nil. - (click) If Peepers *does* exist in the database, we next try to grab his tricycle. - (click) Again, we have to deal with the failure case: if Peepers doesn’t have a tricycle, we *once again* perform error handling, and return nil. - (click) *If* Peepers exists in the database, and *if* he has a tricycle in the database, *then* we return the brand name of that tricycle.
 - Notice how much code we had to write--and that's with the error handling omitted. If we wanted to surface those errors somehow, we'd need to write even more code.

Slide 41

Slide 41 text

// ...error handling. return nil; } NSError *tricycleError = nil; Tricycle *tricycle = [database tricycleForMonkey:monkey error:&tricycleError]; if (tricycle == nil || tricycleError != nil) { // ...error handling. return nil; } return tricycle.brandName; - Here’s the code for getting the monkey’s tricycle’s brand name.
 - (click) First, we initialize the database with a URL. This is where the monkeys are stored. - (click) Then, we grab a monkey, named “Peepers”, from the database.
 - (click) It’s possible “Peepers” doesn’t exist in the database. In that case, we short-circuit: we perform error handling, and return nil. - (click) If Peepers *does* exist in the database, we next try to grab his tricycle. - (click) Again, we have to deal with the failure case: if Peepers doesn’t have a tricycle, we *once again* perform error handling, and return nil. - (click) *If* Peepers exists in the database, and *if* he has a tricycle in the database, *then* we return the brand name of that tricycle.
 - Notice how much code we had to write--and that's with the error handling omitted. If we wanted to surface those errors somehow, we'd need to write even more code.

Slide 42

Slide 42 text

// ...error handling. return nil; } NSError *tricycleError = nil; Tricycle *tricycle = [database tricycleForMonkey:monkey error:&tricycleError]; if (tricycle == nil || tricycleError != nil) { // ...error handling. return nil; } return tricycle.brandName; - Here’s the code for getting the monkey’s tricycle’s brand name.
 - (click) First, we initialize the database with a URL. This is where the monkeys are stored. - (click) Then, we grab a monkey, named “Peepers”, from the database.
 - (click) It’s possible “Peepers” doesn’t exist in the database. In that case, we short-circuit: we perform error handling, and return nil. - (click) If Peepers *does* exist in the database, we next try to grab his tricycle. - (click) Again, we have to deal with the failure case: if Peepers doesn’t have a tricycle, we *once again* perform error handling, and return nil. - (click) *If* Peepers exists in the database, and *if* he has a tricycle in the database, *then* we return the brand name of that tricycle.
 - Notice how much code we had to write--and that's with the error handling omitted. If we wanted to surface those errors somehow, we'd need to write even more code.

Slide 43

Slide 43 text

// ...error handling. return nil; } NSError *tricycleError = nil; Tricycle *tricycle = [database tricycleForMonkey:monkey error:&tricycleError]; if (tricycle == nil || tricycleError != nil) { // ...error handling. return nil; } return tricycle.brandName; - Here’s the code for getting the monkey’s tricycle’s brand name.
 - (click) First, we initialize the database with a URL. This is where the monkeys are stored. - (click) Then, we grab a monkey, named “Peepers”, from the database.
 - (click) It’s possible “Peepers” doesn’t exist in the database. In that case, we short-circuit: we perform error handling, and return nil. - (click) If Peepers *does* exist in the database, we next try to grab his tricycle. - (click) Again, we have to deal with the failure case: if Peepers doesn’t have a tricycle, we *once again* perform error handling, and return nil. - (click) *If* Peepers exists in the database, and *if* he has a tricycle in the database, *then* we return the brand name of that tricycle.
 - Notice how much code we had to write--and that's with the error handling omitted. If we wanted to surface those errors somehow, we'd need to write even more code.

Slide 44

Slide 44 text

NSURL *url = [NSURL fileURLWithPath:@".monkeydb"]; MonkeyDB *database = [[MonkeyDB alloc] initWithURL:url]; Monkey *monkey = [database monkeyWithName:@"Peepers" error:nil]; Tricycle *tricycle = [database tricycleForMonkey:monkey error:nil]; return tricycle.brandName; - Given how much code there was to write, in practice, many people ended up skipping proper error handling.
 - They passed nil for the error pointer parameters, and didn’t bother to write nil-checks for return values.
 - (click) Sometimes that led to runtime exceptions.
 - The errors in these cases—*why* the monkey passed to this method was nil—would never be surfaced.
 - (click) Sometimes they’d end up returning nil for the brand name, which would cause problems elsewhere.

Slide 45

Slide 45 text

NSURL *url = [NSURL fileURLWithPath:@".monkeydb"]; MonkeyDB *database = [[MonkeyDB alloc] initWithURL:url]; Monkey *monkey = [database monkeyWithName:@"Peepers" error:nil]; Tricycle *tricycle = [database tricycleForMonkey:monkey error:nil]; return tricycle.brandName; Caught "NSInternalInconsisntencyException" º - Given how much code there was to write, in practice, many people ended up skipping proper error handling.
 - They passed nil for the error pointer parameters, and didn’t bother to write nil-checks for return values.
 - (click) Sometimes that led to runtime exceptions.
 - The errors in these cases—*why* the monkey passed to this method was nil—would never be surfaced.
 - (click) Sometimes they’d end up returning nil for the brand name, which would cause problems elsewhere.

Slide 46

Slide 46 text

NSURL *url = [NSURL fileURLWithPath:@".monkeydb"]; MonkeyDB *database = [[MonkeyDB alloc] initWithURL:url]; Monkey *monkey = [database monkeyWithName:@"Peepers" error:nil]; Tricycle *tricycle = [database tricycleForMonkey:monkey error:nil]; return tricycle.brandName; Thread 1: EXC_BAD_INSTRUCTION - Given how much code there was to write, in practice, many people ended up skipping proper error handling.
 - They passed nil for the error pointer parameters, and didn’t bother to write nil-checks for return values.
 - (click) Sometimes that led to runtime exceptions.
 - The errors in these cases—*why* the monkey passed to this method was nil—would never be surfaced.
 - (click) Sometimes they’d end up returning nil for the brand name, which would cause problems elsewhere.

Slide 47

Slide 47 text

if (brandName) {
 // ...
 } - To avoid those problems, developers had to perform nil-checks wherever the returned `brandName` was used.
 - The API isn't *precise*. `-monkeyWithName:error:` either returns a `Monkey`, or `nil`.
 - Implied that if it returns `nil`, then something went wrong.
 - Sounds like the issue I mentioned earlier--someone using my API has to read the documentation to use it correctly.

Slide 48

Slide 48 text

/** The result of an operation that could have failed. */ enum Result { /** If the operation succeeded, the result has a value. */ case Success(SuccessType) /** If the operation failed, the result holds an error. */ case Failure(ErrorType) } - Once again, we can use an `enum` to make our API more expressive.
 - We have two cases when reading monkeys and tricycles out of the database.
 1. (click) The first is if our operation succeeded, and we have an object of the type we expected—the SuccessType.
 2. (click) The second is if our operation failed, in which case we get an error object, of the ErrorType.
 - It turns out there’s a library that defines a Result enum for us.

Slide 49

Slide 49 text

/** The result of an operation that could have failed. */ enum Result { /** If the operation succeeded, the result has a value. */ case Success(SuccessType) /** If the operation failed, the result holds an error. */ case Failure(ErrorType) } - Once again, we can use an `enum` to make our API more expressive.
 - We have two cases when reading monkeys and tricycles out of the database.
 1. (click) The first is if our operation succeeded, and we have an object of the type we expected—the SuccessType.
 2. (click) The second is if our operation failed, in which case we get an error object, of the ErrorType.
 - It turns out there’s a library that defines a Result enum for us.

Slide 50

Slide 50 text

/** The result of an operation that could have failed. */ enum Result { /** If the operation succeeded, the result has a value. */ case Success(SuccessType) /** If the operation failed, the result holds an error. */ case Failure(ErrorType) } - Once again, we can use an `enum` to make our API more expressive.
 - We have two cases when reading monkeys and tricycles out of the database.
 1. (click) The first is if our operation succeeded, and we have an object of the type we expected—the SuccessType.
 2. (click) The second is if our operation failed, in which case we get an error object, of the ErrorType.
 - It turns out there’s a library that defines a Result enum for us.

Slide 51

Slide 51 text

LlamaKit - It’s called LlamaKit.
 - Whereas BananaKit is a private beta, LlamaKit is open to everyone on GitHub. (click)

Slide 52

Slide 52 text

LlamaKit github.com/LlamaKit - It’s called LlamaKit.
 - Whereas BananaKit is a private beta, LlamaKit is open to everyone on GitHub. (click)

Slide 53

Slide 53 text

struct MonkeyDB { init(url: NSURL) func monkey(name: String) -> Result func tricycle(monkey: Monkey) -> Result } - Each of the MonkeyDB operations we defined before could fail.
 - Our new API returns `Result` enums that represent that fact:
 1. (click) monkey returns either a Monkey or an error.
 2. (click) tricycle returns either a Tricycle or an error.

Slide 54

Slide 54 text

struct MonkeyDB { init(url: NSURL) func monkey(name: String) -> Result func tricycle(monkey: Monkey) -> Result } - Each of the MonkeyDB operations we defined before could fail.
 - Our new API returns `Result` enums that represent that fact:
 1. (click) monkey returns either a Monkey or an error.
 2. (click) tricycle returns either a Tricycle or an error.

Slide 55

Slide 55 text

struct MonkeyDB { init(url: NSURL) func monkey(name: String) -> Result func tricycle(monkey: Monkey) -> Result } - Each of the MonkeyDB operations we defined before could fail.
 - Our new API returns `Result` enums that represent that fact:
 1. (click) monkey returns either a Monkey or an error.
 2. (click) tricycle returns either a Tricycle or an error.

Slide 56

Slide 56 text

let result = database.tricycle(monkey) return result.brandName - This forces people who use our API to think about errors
 - (click) You can't simply get the brand name of a `Result`.
 - Instead, you're forced to consider whether the result was successful or not.
 - Let’s see how that changes how people use our API, by once again trying to retrieve Peeper’s tricycle’s brand name.

Slide 57

Slide 57 text

let result = database.tricycle(monkey) return result.brandName 'Result' does not have a member named 'brandName'  - This forces people who use our API to think about errors
 - (click) You can't simply get the brand name of a `Result`.
 - Instead, you're forced to consider whether the result was successful or not.
 - Let’s see how that changes how people use our API, by once again trying to retrieve Peeper’s tricycle’s brand name.

Slide 58

Slide 58 text

let monkeyResult = database.monkey(“Peepers") switch monkeyResult { case .Failure(let error): return .Failure(error) case .Success(let monkey): let tricycleResult = database.tricycle(monkey) switch tricycleResult { case .Failure(let error): return .Failure(error) case .Success(let tricycle): return .Success(tricycle.brandName) } } - We begin by, once again, trying to grab Peepers from the monkey database. This time, we get a Result, which is either a Monkey or an error.
 - (click) If it’s an error, we short-circuit, and return that error as a failure.
 - (click) If the result is a success, then we can get the Result’s successful value, which is a Monkey. We then try fetching the tricycle for the monkey.
 - (click) If we can’t find a tricycle, we short-circuit, returning the tricycle fetching error.
 - (click) If we get a tricycle, we return a success, with that tricycle’s value.
 - I think that’s pretty precise.

Slide 59

Slide 59 text

let monkeyResult = database.monkey(“Peepers") switch monkeyResult { case .Failure(let error): return .Failure(error) case .Success(let monkey): let tricycleResult = database.tricycle(monkey) switch tricycleResult { case .Failure(let error): return .Failure(error) case .Success(let tricycle): return .Success(tricycle.brandName) } } - We begin by, once again, trying to grab Peepers from the monkey database. This time, we get a Result, which is either a Monkey or an error.
 - (click) If it’s an error, we short-circuit, and return that error as a failure.
 - (click) If the result is a success, then we can get the Result’s successful value, which is a Monkey. We then try fetching the tricycle for the monkey.
 - (click) If we can’t find a tricycle, we short-circuit, returning the tricycle fetching error.
 - (click) If we get a tricycle, we return a success, with that tricycle’s value.
 - I think that’s pretty precise.

Slide 60

Slide 60 text

let monkeyResult = database.monkey(“Peepers") switch monkeyResult { case .Failure(let error): return .Failure(error) case .Success(let monkey): let tricycleResult = database.tricycle(monkey) switch tricycleResult { case .Failure(let error): return .Failure(error) case .Success(let tricycle): return .Success(tricycle.brandName) } } - We begin by, once again, trying to grab Peepers from the monkey database. This time, we get a Result, which is either a Monkey or an error.
 - (click) If it’s an error, we short-circuit, and return that error as a failure.
 - (click) If the result is a success, then we can get the Result’s successful value, which is a Monkey. We then try fetching the tricycle for the monkey.
 - (click) If we can’t find a tricycle, we short-circuit, returning the tricycle fetching error.
 - (click) If we get a tricycle, we return a success, with that tricycle’s value.
 - I think that’s pretty precise.

Slide 61

Slide 61 text

let monkeyResult = database.monkey(“Peepers") switch monkeyResult { case .Failure(let error): return .Failure(error) case .Success(let monkey): let tricycleResult = database.tricycle(monkey) switch tricycleResult { case .Failure(let error): return .Failure(error) case .Success(let tricycle): return .Success(tricycle.brandName) } } - We begin by, once again, trying to grab Peepers from the monkey database. This time, we get a Result, which is either a Monkey or an error.
 - (click) If it’s an error, we short-circuit, and return that error as a failure.
 - (click) If the result is a success, then we can get the Result’s successful value, which is a Monkey. We then try fetching the tricycle for the monkey.
 - (click) If we can’t find a tricycle, we short-circuit, returning the tricycle fetching error.
 - (click) If we get a tricycle, we return a success, with that tricycle’s value.
 - I think that’s pretty precise.

Slide 62

Slide 62 text

let monkeyResult = database.monkey(“Peepers") switch monkeyResult { case .Failure(let error): return .Failure(error) case .Success(let monkey): let tricycleResult = database.tricycle(monkey) switch tricycleResult { case .Failure(let error): return .Failure(error) case .Success(let tricycle): return .Success(tricycle.brandName) } } - We begin by, once again, trying to grab Peepers from the monkey database. This time, we get a Result, which is either a Monkey or an error.
 - (click) If it’s an error, we short-circuit, and return that error as a failure.
 - (click) If the result is a success, then we can get the Result’s successful value, which is a Monkey. We then try fetching the tricycle for the monkey.
 - (click) If we can’t find a tricycle, we short-circuit, returning the tricycle fetching error.
 - (click) If we get a tricycle, we return a success, with that tricycle’s value.
 - I think that’s pretty precise.

Slide 63

Slide 63 text

1. Precise 2. Convenient - But is it convenient?

Slide 64

Slide 64 text

let monkeyResult = database.monkey(“Peepers") switch monkeyResult { case .Failure(let error): return .Failure(error) case .Success(let monkey): let tricycleResult = database.tricycle(monkey) switch tricycleResult { case .Failure(let error): return .Failure(error) case .Success(let tricycle): return .Success(tricycle.brandName) } } - Who thinks this is convenient?
 - (If no hands up): Exactly.
 - (If hands up): Aw, you’re a terrific audience. But I don’t think it is.
 - Let’s examine what this code is doing.

Slide 65

Slide 65 text

MonkeyDB .monkey(name: return MonkeyDB .tricycle(monkey: return - We want to get to either a tricycle brand name—which we consider a success—or a failure. (click)
 - Ideally the failure has some information about the failure attached: an NSError.
 - (click) We start out with a name of a monkey, “Peepers”. We want his tricycle. We try to load him from the database.
 - (click) If that fails, boom—straight to a failure.
 - (click) If it succeeds, we go onto the next step. - Given a monkey, we can grab its tricycle from the database.
 - (click) If it doesn’t exist, we get on the failure track.
 - (click) If it does, success! We return the brand name.

Slide 66

Slide 66 text

MonkeyDB .monkey(name: return .Failure(NSError) MonkeyDB .tricycle(monkey: return .Success( Tricycle.brandName) - We want to get to either a tricycle brand name—which we consider a success—or a failure. (click)
 - Ideally the failure has some information about the failure attached: an NSError.
 - (click) We start out with a name of a monkey, “Peepers”. We want his tricycle. We try to load him from the database.
 - (click) If that fails, boom—straight to a failure.
 - (click) If it succeeds, we go onto the next step. - Given a monkey, we can grab its tricycle from the database.
 - (click) If it doesn’t exist, we get on the failure track.
 - (click) If it does, success! We return the brand name.

Slide 67

Slide 67 text

MonkeyDB .monkey(name: String) return .Failure(NSError) MonkeyDB .tricycle(monkey: return .Success( Tricycle.brandName) - We want to get to either a tricycle brand name—which we consider a success—or a failure. (click)
 - Ideally the failure has some information about the failure attached: an NSError.
 - (click) We start out with a name of a monkey, “Peepers”. We want his tricycle. We try to load him from the database.
 - (click) If that fails, boom—straight to a failure.
 - (click) If it succeeds, we go onto the next step. - Given a monkey, we can grab its tricycle from the database.
 - (click) If it doesn’t exist, we get on the failure track.
 - (click) If it does, success! We return the brand name.

Slide 68

Slide 68 text

MonkeyDB .monkey(name: String) return .Failure(NSError) MonkeyDB .tricycle(monkey: return .Success( Tricycle.brandName) - We want to get to either a tricycle brand name—which we consider a success—or a failure. (click)
 - Ideally the failure has some information about the failure attached: an NSError.
 - (click) We start out with a name of a monkey, “Peepers”. We want his tricycle. We try to load him from the database.
 - (click) If that fails, boom—straight to a failure.
 - (click) If it succeeds, we go onto the next step. - Given a monkey, we can grab its tricycle from the database.
 - (click) If it doesn’t exist, we get on the failure track.
 - (click) If it does, success! We return the brand name.

Slide 69

Slide 69 text

MonkeyDB .monkey(name: String) return .Failure(NSError) MonkeyDB .tricycle(monkey: Monkey) return .Success( Tricycle.brandName) - We want to get to either a tricycle brand name—which we consider a success—or a failure. (click)
 - Ideally the failure has some information about the failure attached: an NSError.
 - (click) We start out with a name of a monkey, “Peepers”. We want his tricycle. We try to load him from the database.
 - (click) If that fails, boom—straight to a failure.
 - (click) If it succeeds, we go onto the next step. - Given a monkey, we can grab its tricycle from the database.
 - (click) If it doesn’t exist, we get on the failure track.
 - (click) If it does, success! We return the brand name.

Slide 70

Slide 70 text

MonkeyDB .monkey(name: String) return .Failure(NSError) MonkeyDB .tricycle(monkey: Monkey) return .Success( Tricycle.brandName) - We want to get to either a tricycle brand name—which we consider a success—or a failure. (click)
 - Ideally the failure has some information about the failure attached: an NSError.
 - (click) We start out with a name of a monkey, “Peepers”. We want his tricycle. We try to load him from the database.
 - (click) If that fails, boom—straight to a failure.
 - (click) If it succeeds, we go onto the next step. - Given a monkey, we can grab its tricycle from the database.
 - (click) If it doesn’t exist, we get on the failure track.
 - (click) If it does, success! We return the brand name.

Slide 71

Slide 71 text

MonkeyDB .monkey(name: String) return .Failure(NSError) MonkeyDB .tricycle(monkey: Monkey) return .Success( Tricycle.brandName) - We want to get to either a tricycle brand name—which we consider a success—or a failure. (click)
 - Ideally the failure has some information about the failure attached: an NSError.
 - (click) We start out with a name of a monkey, “Peepers”. We want his tricycle. We try to load him from the database.
 - (click) If that fails, boom—straight to a failure.
 - (click) If it succeeds, we go onto the next step. - Given a monkey, we can grab its tricycle from the database.
 - (click) If it doesn’t exist, we get on the failure track.
 - (click) If it does, success! We return the brand name.

Slide 72

Slide 72 text

MonkeyDB .monkey(name: return MonkeyDB .tricycle(monkey: return - Because of the different “tracks”, this sort of code is often called “railway-oriented programming”.
 - Our train continues down the successful track.
 - (click) When it performs operations that could fail, it switches to the failure track.
 - This “track-switching” logic is actually built into LlamaKit’s Result enum.

Slide 73

Slide 73 text

MonkeyDB .monkey(name: return MonkeyDB .tricycle(monkey: return - Because of the different “tracks”, this sort of code is often called “railway-oriented programming”.
 - Our train continues down the successful track.
 - (click) When it performs operations that could fail, it switches to the failure track.
 - This “track-switching” logic is actually built into LlamaKit’s Result enum.

Slide 74

Slide 74 text

MonkeyDB .monkey(name: return MonkeyDB .tricycle(monkey: return - Because of the different “tracks”, this sort of code is often called “railway-oriented programming”.
 - Our train continues down the successful track.
 - (click) When it performs operations that could fail, it switches to the failure track.
 - This “track-switching” logic is actually built into LlamaKit’s Result enum.

Slide 75

Slide 75 text

enum Result { func map(transform: SuccessType -> NewType) -> Result func flatMap( transform: SuccessType -> Result) -> Result } - Result allows us to define success and failure tracks using the map and flatMap methods.
 - Don’t worry about the method signatures—let’s jump right into an example.

Slide 76

Slide 76 text

let monkeyResult = database.monkey("Peepers") let tricycleResult = monkeyResult.flatMap { (monkey: Monkey) in return database.tricycle(monkey) } let brandNameResult = tricycleResult.map { (tricycle: Tricycle) in return tricycle.brandName } return brandNameResult - First, we grab the result of reading “Peepers” from the database. - (click) Then, we grab a tricycle using that result. - (click) flatMap takes a closure that, given a monkey, returns a Result, which is either a Tricycle or an error. - (click) The closure is *only* run if `monkeyResult` is a success. If it’s a *failure*, then we have no monkey to pass to the closure, and it’s never executed. - (click) Next, we grab the brand name of the tricycle. - (click) The map method takes a closure that, given a tricycle, returns that tricycle’s brand name. The ‘brandName’ method can’t fail—it doesn’t return a Result, it returns a String. - (click) Once again, this closure is *not run* if the tricycle result is a failure—that is, if we’re already on our failure track. - (click) The only way we can be on our success track is if Peepers is in the database, and if his tricycle is also in the database. - (click) Finally, we return the brand name result.

Slide 77

Slide 77 text

let monkeyResult = database.monkey("Peepers") let tricycleResult = monkeyResult.flatMap { (monkey: Monkey) in return database.tricycle(monkey) } let brandNameResult = tricycleResult.map { (tricycle: Tricycle) in return tricycle.brandName } return brandNameResult - First, we grab the result of reading “Peepers” from the database. - (click) Then, we grab a tricycle using that result. - (click) flatMap takes a closure that, given a monkey, returns a Result, which is either a Tricycle or an error. - (click) The closure is *only* run if `monkeyResult` is a success. If it’s a *failure*, then we have no monkey to pass to the closure, and it’s never executed. - (click) Next, we grab the brand name of the tricycle. - (click) The map method takes a closure that, given a tricycle, returns that tricycle’s brand name. The ‘brandName’ method can’t fail—it doesn’t return a Result, it returns a String. - (click) Once again, this closure is *not run* if the tricycle result is a failure—that is, if we’re already on our failure track. - (click) The only way we can be on our success track is if Peepers is in the database, and if his tricycle is also in the database. - (click) Finally, we return the brand name result.

Slide 78

Slide 78 text

let monkeyResult = database.monkey("Peepers") let tricycleResult = monkeyResult.flatMap { (monkey: Monkey) in return database.tricycle(monkey) } let brandNameResult = tricycleResult.map { (tricycle: Tricycle) in return tricycle.brandName } return brandNameResult - First, we grab the result of reading “Peepers” from the database. - (click) Then, we grab a tricycle using that result. - (click) flatMap takes a closure that, given a monkey, returns a Result, which is either a Tricycle or an error. - (click) The closure is *only* run if `monkeyResult` is a success. If it’s a *failure*, then we have no monkey to pass to the closure, and it’s never executed. - (click) Next, we grab the brand name of the tricycle. - (click) The map method takes a closure that, given a tricycle, returns that tricycle’s brand name. The ‘brandName’ method can’t fail—it doesn’t return a Result, it returns a String. - (click) Once again, this closure is *not run* if the tricycle result is a failure—that is, if we’re already on our failure track. - (click) The only way we can be on our success track is if Peepers is in the database, and if his tricycle is also in the database. - (click) Finally, we return the brand name result.

Slide 79

Slide 79 text

let monkeyResult = database.monkey("Peepers") let tricycleResult = monkeyResult.flatMap { (monkey: Monkey) in return database.tricycle(monkey) } let brandNameResult = tricycleResult.map { (tricycle: Tricycle) in return tricycle.brandName } return brandNameResult - First, we grab the result of reading “Peepers” from the database. - (click) Then, we grab a tricycle using that result. - (click) flatMap takes a closure that, given a monkey, returns a Result, which is either a Tricycle or an error. - (click) The closure is *only* run if `monkeyResult` is a success. If it’s a *failure*, then we have no monkey to pass to the closure, and it’s never executed. - (click) Next, we grab the brand name of the tricycle. - (click) The map method takes a closure that, given a tricycle, returns that tricycle’s brand name. The ‘brandName’ method can’t fail—it doesn’t return a Result, it returns a String. - (click) Once again, this closure is *not run* if the tricycle result is a failure—that is, if we’re already on our failure track. - (click) The only way we can be on our success track is if Peepers is in the database, and if his tricycle is also in the database. - (click) Finally, we return the brand name result.

Slide 80

Slide 80 text

let monkeyResult = database.monkey("Peepers") let tricycleResult = monkeyResult.flatMap { (monkey: Monkey) in return database.tricycle(monkey) } let brandNameResult = tricycleResult.map { (tricycle: Tricycle) in return tricycle.brandName } return brandNameResult - First, we grab the result of reading “Peepers” from the database. - (click) Then, we grab a tricycle using that result. - (click) flatMap takes a closure that, given a monkey, returns a Result, which is either a Tricycle or an error. - (click) The closure is *only* run if `monkeyResult` is a success. If it’s a *failure*, then we have no monkey to pass to the closure, and it’s never executed. - (click) Next, we grab the brand name of the tricycle. - (click) The map method takes a closure that, given a tricycle, returns that tricycle’s brand name. The ‘brandName’ method can’t fail—it doesn’t return a Result, it returns a String. - (click) Once again, this closure is *not run* if the tricycle result is a failure—that is, if we’re already on our failure track. - (click) The only way we can be on our success track is if Peepers is in the database, and if his tricycle is also in the database. - (click) Finally, we return the brand name result.

Slide 81

Slide 81 text

let monkeyResult = database.monkey("Peepers") let tricycleResult = monkeyResult.flatMap { (monkey: Monkey) in return database.tricycle(monkey) } let brandNameResult = tricycleResult.map { (tricycle: Tricycle) in return tricycle.brandName } return brandNameResult - First, we grab the result of reading “Peepers” from the database. - (click) Then, we grab a tricycle using that result. - (click) flatMap takes a closure that, given a monkey, returns a Result, which is either a Tricycle or an error. - (click) The closure is *only* run if `monkeyResult` is a success. If it’s a *failure*, then we have no monkey to pass to the closure, and it’s never executed. - (click) Next, we grab the brand name of the tricycle. - (click) The map method takes a closure that, given a tricycle, returns that tricycle’s brand name. The ‘brandName’ method can’t fail—it doesn’t return a Result, it returns a String. - (click) Once again, this closure is *not run* if the tricycle result is a failure—that is, if we’re already on our failure track. - (click) The only way we can be on our success track is if Peepers is in the database, and if his tricycle is also in the database. - (click) Finally, we return the brand name result.

Slide 82

Slide 82 text

let monkeyResult = database.monkey("Peepers") let tricycleResult = monkeyResult.flatMap { (monkey: Monkey) in return database.tricycle(monkey) } let brandNameResult = tricycleResult.map { (tricycle: Tricycle) in return tricycle.brandName } return brandNameResult - First, we grab the result of reading “Peepers” from the database. - (click) Then, we grab a tricycle using that result. - (click) flatMap takes a closure that, given a monkey, returns a Result, which is either a Tricycle or an error. - (click) The closure is *only* run if `monkeyResult` is a success. If it’s a *failure*, then we have no monkey to pass to the closure, and it’s never executed. - (click) Next, we grab the brand name of the tricycle. - (click) The map method takes a closure that, given a tricycle, returns that tricycle’s brand name. The ‘brandName’ method can’t fail—it doesn’t return a Result, it returns a String. - (click) Once again, this closure is *not run* if the tricycle result is a failure—that is, if we’re already on our failure track. - (click) The only way we can be on our success track is if Peepers is in the database, and if his tricycle is also in the database. - (click) Finally, we return the brand name result.

Slide 83

Slide 83 text

let monkeyResult = database.monkey("Peepers") let tricycleResult = monkeyResult.flatMap { (monkey: Monkey) in return database.tricycle(monkey) } let brandNameResult = tricycleResult.map { (tricycle: Tricycle) in return tricycle.brandName } return brandNameResult - First, we grab the result of reading “Peepers” from the database. - (click) Then, we grab a tricycle using that result. - (click) flatMap takes a closure that, given a monkey, returns a Result, which is either a Tricycle or an error. - (click) The closure is *only* run if `monkeyResult` is a success. If it’s a *failure*, then we have no monkey to pass to the closure, and it’s never executed. - (click) Next, we grab the brand name of the tricycle. - (click) The map method takes a closure that, given a tricycle, returns that tricycle’s brand name. The ‘brandName’ method can’t fail—it doesn’t return a Result, it returns a String. - (click) Once again, this closure is *not run* if the tricycle result is a failure—that is, if we’re already on our failure track. - (click) The only way we can be on our success track is if Peepers is in the database, and if his tricycle is also in the database. - (click) Finally, we return the brand name result.

Slide 84

Slide 84 text

let monkeyResult = database.monkey("Peepers") let tricycleResult = monkeyResult.flatMap { (monkey: Monkey) in return database.tricycle(monkey) } let brandNameResult = tricycleResult.map { (tricycle: Tricycle) in return tricycle.brandName } return brandNameResult - The brand name result is *only* successful if *every* operation that preceded it succeeded.

Slide 85

Slide 85 text

let monkeyResult = database.monkey("Peepers") let tricycleResult = monkeyResult.flatMap { (monkey: Monkey) in return database.tricycle(monkey) } let brandNameResult = tricycleResult.map { (tricycle: Tricycle) in return tricycle.brandName } return brandNameResult - The brand name result is *only* successful if *every* operation that preceded it succeeded.

Slide 86

Slide 86 text

let monkeyResult = database.monkey("Peepers") let tricycleResult = monkeyResult.flatMap { (monkey: Monkey) in return database.tricycle(monkey) } let brandNameResult = tricycleResult.map { (tricycle: Tricycle) in return tricycle.brandName } return brandNameResult - The brand name result is *only* successful if *every* operation that preceded it succeeded.

Slide 87

Slide 87 text

let monkeyResult = database.monkey("Peepers") let tricycleResult = monkeyResult.flatMap { (monkey: Monkey) in return database.tricycle(monkey) } let brandNameResult = tricycleResult.map { (tricycle: Tricycle) in return tricycle.brandName } return brandNameResult - If *any* of the operations failed, the result is also a failure. - The result will contain the error message of the *first* operation that failed. - (click) For example, if Peepers didn’t have at tricycle in the database, the result will have an error that indicates that no such tricycle exists. - (click) And in that case, this closure, which accesses the tricycle’s brand name, is never executed.

Slide 88

Slide 88 text

let monkeyResult = database.monkey("Peepers") let tricycleResult = monkeyResult.flatMap { (monkey: Monkey) in return database.tricycle(monkey) } let brandNameResult = tricycleResult.map { (tricycle: Tricycle) in return tricycle.brandName } return brandNameResult - If *any* of the operations failed, the result is also a failure. - The result will contain the error message of the *first* operation that failed. - (click) For example, if Peepers didn’t have at tricycle in the database, the result will have an error that indicates that no such tricycle exists. - (click) And in that case, this closure, which accesses the tricycle’s brand name, is never executed.

Slide 89

Slide 89 text

let monkeyResult = database.monkey("Peepers") let tricycleResult = monkeyResult.flatMap { (monkey: Monkey) in return database.tricycle(monkey) } let brandNameResult = tricycleResult.map { (tricycle: Tricycle) in return tricycle.brandName } return brandNameResult - If *any* of the operations failed, the result is also a failure. - The result will contain the error message of the *first* operation that failed. - (click) For example, if Peepers didn’t have at tricycle in the database, the result will have an error that indicates that no such tricycle exists. - (click) And in that case, this closure, which accesses the tricycle’s brand name, is never executed.

Slide 90

Slide 90 text

let monkeyResult = database.monkey("Peepers") let tricycleResult = monkeyResult.flatMap { (monkey: Monkey) in return database.tricycle(monkey) } let brandNameResult = tricycleResult.map { (tricycle: Tricycle) in return tricycle.brandName } return brandNameResult - If *any* of the operations failed, the result is also a failure. - The result will contain the error message of the *first* operation that failed. - (click) For example, if Peepers didn’t have at tricycle in the database, the result will have an error that indicates that no such tricycle exists. - (click) And in that case, this closure, which accesses the tricycle’s brand name, is never executed.

Slide 91

Slide 91 text

let monkeyResult = database.monkey("Peepers") let tricycleResult = monkeyResult.flatMap { (monkey: Monkey) in return database.tricycle(monkey) } let brandNameResult = tricycleResult.map { (tricycle: Tricycle) in return tricycle.brandName } return brandNameResult - We can make this code shorter and easier to read by using numbered closure arguments. - (click) Notice the named arguments, monkey and tricycle. - (click) $0 instead of monkey. - (click) $0 instead of tricycle.

Slide 92

Slide 92 text

let monkeyResult = database.monkey("Peepers") let tricycleResult = monkeyResult.flatMap { (monkey: Monkey) in return database.tricycle(monkey) } let brandNameResult = tricycleResult.map { (tricycle: Tricycle) in return tricycle.brandName } return brandNameResult - We can make this code shorter and easier to read by using numbered closure arguments. - (click) Notice the named arguments, monkey and tricycle. - (click) $0 instead of monkey. - (click) $0 instead of tricycle.

Slide 93

Slide 93 text

let monkeyResult = database.monkey("Peepers") let tricycleResult = monkeyResult.flatMap { (monkey: Monkey) in return database.tricycle(monkey) } let brandNameResult = tricycleResult.map { (tricycle: Tricycle) in return tricycle.brandName } return brandNameResult let tricycleResult = monkeyResult.flatMap { return database.tricycle($0) } - We can make this code shorter and easier to read by using numbered closure arguments. - (click) Notice the named arguments, monkey and tricycle. - (click) $0 instead of monkey. - (click) $0 instead of tricycle.

Slide 94

Slide 94 text

let monkeyResult = database.monkey("Peepers") let tricycleResult = monkeyResult.flatMap { (monkey: Monkey) in return database.tricycle(monkey) } let brandNameResult = tricycleResult.map { (tricycle: Tricycle) in return tricycle.brandName } return brandNameResult let tricycleResult = monkeyResult.flatMap { return database.tricycle($0) } let brandNameResult = tricycleResult.map { return $0.brandName } - We can make this code shorter and easier to read by using numbered closure arguments. - (click) Notice the named arguments, monkey and tricycle. - (click) $0 instead of monkey. - (click) $0 instead of tricycle.

Slide 95

Slide 95 text

let monkeyResult = database.monkey("Peepers") let tricycleResult = monkeyResult.flatMap { return database.tricycle($0) } let brandNameResult = tricycleResult.map { return $0.brandName } return brandNameResult - These return statements are also redundant. Since the closures only contain a single statement, that statement is the implied return value. - (click) So we can remove the return statements, making the closures a little shorter. - Now, the variable names are really just there for convenience.

Slide 96

Slide 96 text

let monkeyResult = database.monkey("Peepers") let tricycleResult = monkeyResult.flatMap { return database.tricycle($0) } let brandNameResult = tricycleResult.map { return $0.brandName } return brandNameResult let brandNameResult = tricycleResult.map { $0.brandName } let tricycleResult = monkeyResult.flatMap { database.tricycle($0) } - These return statements are also redundant. Since the closures only contain a single statement, that statement is the implied return value. - (click) So we can remove the return statements, making the closures a little shorter. - Now, the variable names are really just there for convenience.

Slide 97

Slide 97 text

return database.monkey("Peepers") .flatMap { database.tricycle($0) } .map { $0.brandName } - We can put it all on one line. - Remember, the closures don’t get executed unless they’re on the happy path:
 1. (click) First we read “Peepers” from the database
 2. (click) Then, iff that’s successful, we get Peepers’s tricycle
 3. (click) Finally, iff we successfully got a tricycle, we get its brand name

Slide 98

Slide 98 text

return database.monkey("Peepers") .flatMap { database.tricycle($0) } .map { $0.brandName } - We can put it all on one line. - Remember, the closures don’t get executed unless they’re on the happy path:
 1. (click) First we read “Peepers” from the database
 2. (click) Then, iff that’s successful, we get Peepers’s tricycle
 3. (click) Finally, iff we successfully got a tricycle, we get its brand name

Slide 99

Slide 99 text

return database.monkey("Peepers") .flatMap { database.tricycle($0) } .map { $0.brandName } - We can put it all on one line. - Remember, the closures don’t get executed unless they’re on the happy path:
 1. (click) First we read “Peepers” from the database
 2. (click) Then, iff that’s successful, we get Peepers’s tricycle
 3. (click) Finally, iff we successfully got a tricycle, we get its brand name

Slide 100

Slide 100 text

return database.monkey("Peepers") .flatMap { database.tricycle($0) } .map { $0.brandName } - We can put it all on one line. - Remember, the closures don’t get executed unless they’re on the happy path:
 1. (click) First we read “Peepers” from the database
 2. (click) Then, iff that’s successful, we get Peepers’s tricycle
 3. (click) Finally, iff we successfully got a tricycle, we get its brand name

Slide 101

Slide 101 text

- This may seem very technical.
 - Sure, it’s fine for the high-tech world of bananas, monkeys, and tricycles.
 - But what about our everyday applications?
 - It works for those, too.

Slide 102

Slide 102 text

- Earlier this year I created a library that allows you to interact with Git from Swift.

Slide 103

Slide 103 text

- Earlier this year I created a library that allows you to interact with Git from Swift.

Slide 104

Slide 104 text

Gift - It’s called Gift.
 - It uses the railway-oriented programming techniques I just described. - (click) This one isn’t in private beta, like BananaKit. You can grab it on GitHub.

Slide 105

Slide 105 text

Gift https://github.com/modocache/Gift - It’s called Gift.
 - It uses the railway-oriented programming techniques I just described. - (click) This one isn’t in private beta, like BananaKit. You can grab it on GitHub.

Slide 106

Slide 106 text

let url = NSURL( fileURLWithPath: "/Users/modocache/Gift")! let latestCommitMessage = openRepository(url) .flatMap { $0.headReference } .flatMap { $0.commit } .flatMap { $0.message } switch latestCommitMessage { case .Success(let message): println(message) case .Failure(let error): println(error.localizedDescription) } - Here’s an example of using Gift: let’s open the Gift repository itself, get it’s HEAD, and print out the commit message HEAD points to.
 - First, we open the repository. This might fail: no repository may exist at that location.
 - (click) Then, we grab the HEAD reference. This might fail: the repository might not have any commits, and so it doesn’t have a HEAD.
 - (click) Then, we get the commit that reference points to.
 - (click) Then, we get the commit message.
 - That gives us a result for the commit message.
 - (click) If that result is successful, we print the message. Otherwise, we print the error.

Slide 107

Slide 107 text

let url = NSURL( fileURLWithPath: "/Users/modocache/Gift")! let latestCommitMessage = openRepository(url) .flatMap { $0.headReference } .flatMap { $0.commit } .flatMap { $0.message } switch latestCommitMessage { case .Success(let message): println(message) case .Failure(let error): println(error.localizedDescription) } - Here’s an example of using Gift: let’s open the Gift repository itself, get it’s HEAD, and print out the commit message HEAD points to.
 - First, we open the repository. This might fail: no repository may exist at that location.
 - (click) Then, we grab the HEAD reference. This might fail: the repository might not have any commits, and so it doesn’t have a HEAD.
 - (click) Then, we get the commit that reference points to.
 - (click) Then, we get the commit message.
 - That gives us a result for the commit message.
 - (click) If that result is successful, we print the message. Otherwise, we print the error.

Slide 108

Slide 108 text

let url = NSURL( fileURLWithPath: "/Users/modocache/Gift")! let latestCommitMessage = openRepository(url) .flatMap { $0.headReference } .flatMap { $0.commit } .flatMap { $0.message } switch latestCommitMessage { case .Success(let message): println(message) case .Failure(let error): println(error.localizedDescription) } - Here’s an example of using Gift: let’s open the Gift repository itself, get it’s HEAD, and print out the commit message HEAD points to.
 - First, we open the repository. This might fail: no repository may exist at that location.
 - (click) Then, we grab the HEAD reference. This might fail: the repository might not have any commits, and so it doesn’t have a HEAD.
 - (click) Then, we get the commit that reference points to.
 - (click) Then, we get the commit message.
 - That gives us a result for the commit message.
 - (click) If that result is successful, we print the message. Otherwise, we print the error.

Slide 109

Slide 109 text

let url = NSURL( fileURLWithPath: "/Users/modocache/Gift")! let latestCommitMessage = openRepository(url) .flatMap { $0.headReference } .flatMap { $0.commit } .flatMap { $0.message } switch latestCommitMessage { case .Success(let message): println(message) case .Failure(let error): println(error.localizedDescription) } - Here’s an example of using Gift: let’s open the Gift repository itself, get it’s HEAD, and print out the commit message HEAD points to.
 - First, we open the repository. This might fail: no repository may exist at that location.
 - (click) Then, we grab the HEAD reference. This might fail: the repository might not have any commits, and so it doesn’t have a HEAD.
 - (click) Then, we get the commit that reference points to.
 - (click) Then, we get the commit message.
 - That gives us a result for the commit message.
 - (click) If that result is successful, we print the message. Otherwise, we print the error.

Slide 110

Slide 110 text

let url = NSURL( fileURLWithPath: "/Users/modocache/Gift")! let latestCommitMessage = openRepository(url) .flatMap { $0.headReference } .flatMap { $0.commit } .flatMap { $0.message } switch latestCommitMessage { case .Success(let message): println(message) case .Failure(let error): println(error.localizedDescription) } - Here’s an example of using Gift: let’s open the Gift repository itself, get it’s HEAD, and print out the commit message HEAD points to.
 - First, we open the repository. This might fail: no repository may exist at that location.
 - (click) Then, we grab the HEAD reference. This might fail: the repository might not have any commits, and so it doesn’t have a HEAD.
 - (click) Then, we get the commit that reference points to.
 - (click) Then, we get the commit message.
 - That gives us a result for the commit message.
 - (click) If that result is successful, we print the message. Otherwise, we print the error.

Slide 111

Slide 111 text

openRepository(url) .flatMap { $0.index } .flatMap { $0.add() } .flatMap { $0.writeTree() } .flatMap { $0.commit("This is bananas!") } git commit --all --message "this is bananas" - Here’s another example: let’s add all unstaged files to the index, then make a commit.
 - (click) We grab the index, or staging area.
 - (click) We add all files to the index.
 - (click) We create a tree with the current index.
 - (click) And we make a commit based off of that tree.
 - Of course, if any step fails, the return value is the first error in the chain. I hope you agree that that’s pretty convenient.

Slide 112

Slide 112 text

openRepository(url) .flatMap { $0.index } .flatMap { $0.add() } .flatMap { $0.writeTree() } .flatMap { $0.commit("This is bananas!") } git commit --all --message "this is bananas" - Here’s another example: let’s add all unstaged files to the index, then make a commit.
 - (click) We grab the index, or staging area.
 - (click) We add all files to the index.
 - (click) We create a tree with the current index.
 - (click) And we make a commit based off of that tree.
 - Of course, if any step fails, the return value is the first error in the chain. I hope you agree that that’s pretty convenient.

Slide 113

Slide 113 text

openRepository(url) .flatMap { $0.index } .flatMap { $0.add() } .flatMap { $0.writeTree() } .flatMap { $0.commit("This is bananas!") } git commit --all --message "this is bananas" - Here’s another example: let’s add all unstaged files to the index, then make a commit.
 - (click) We grab the index, or staging area.
 - (click) We add all files to the index.
 - (click) We create a tree with the current index.
 - (click) And we make a commit based off of that tree.
 - Of course, if any step fails, the return value is the first error in the chain. I hope you agree that that’s pretty convenient.

Slide 114

Slide 114 text

openRepository(url) .flatMap { $0.index } .flatMap { $0.add() } .flatMap { $0.writeTree() } .flatMap { $0.commit("This is bananas!") } git commit --all --message "this is bananas" - Here’s another example: let’s add all unstaged files to the index, then make a commit.
 - (click) We grab the index, or staging area.
 - (click) We add all files to the index.
 - (click) We create a tree with the current index.
 - (click) And we make a commit based off of that tree.
 - Of course, if any step fails, the return value is the first error in the chain. I hope you agree that that’s pretty convenient.

Slide 115

Slide 115 text

openRepository(url) .flatMap { $0.index } .flatMap { $0.add() } .flatMap { $0.writeTree() } .flatMap { $0.commit("This is bananas!") } git commit --all --message "this is bananas" - Here’s another example: let’s add all unstaged files to the index, then make a commit.
 - (click) We grab the index, or staging area.
 - (click) We add all files to the index.
 - (click) We create a tree with the current index.
 - (click) And we make a commit based off of that tree.
 - Of course, if any step fails, the return value is the first error in the chain. I hope you agree that that’s pretty convenient.

Slide 116

Slide 116 text

let entryCountResult = openRepository(url) .flatMap { $0.index } .map { $0.entryCount } git status --staged | wc -l - Let’s take a look at one last example: finding the total number of files in the staging area of a repository. We open the repository…
 - (click) We grab the index
 - (click) And we grab the number of entries.
 - Notice that the first operation uses flatMap, and the second uses map. So what’s the difference?

Slide 117

Slide 117 text

let entryCountResult = openRepository(url) .flatMap { $0.index } .map { $0.entryCount } git status --staged | wc -l - Let’s take a look at one last example: finding the total number of files in the staging area of a repository. We open the repository…
 - (click) We grab the index
 - (click) And we grab the number of entries.
 - Notice that the first operation uses flatMap, and the second uses map. So what’s the difference?

Slide 118

Slide 118 text

let entryCountResult = openRepository(url) .flatMap { $0.index } .map { $0.entryCount } git status --staged | wc -l - Let’s take a look at one last example: finding the total number of files in the staging area of a repository. We open the repository…
 - (click) We grab the index
 - (click) And we grab the number of entries.
 - Notice that the first operation uses flatMap, and the second uses map. So what’s the difference?

Slide 119

Slide 119 text

let entryCountResult = openRepository(url) .flatMap { $0.index } .map { $0.entryCount } git status --staged | wc -l - flatMap takes a closure that’s run when we’re on the successful track—and only if we’re on the successful track.
 - The closure takes the successful value, and returns a *Result*—something that may succeed, but may fail.
 - In other words, this closure is a track switch—it can switch us from the successful track to the failure track. If Gift fails to acquire the index, we get switched to the failure track.
 - (click) map *also* takes a closure that’s *only* run if we’re on the successful track. But unlike flatMap, it doesn’t return a Result—it just returns a value. In other words, it’s *not* a track switch—it can’t transition us to the failure track. (click)

Slide 120

Slide 120 text

let entryCountResult = openRepository(url) .flatMap { $0.index } .map { $0.entryCount } git status --staged | wc -l - flatMap takes a closure that’s run when we’re on the successful track—and only if we’re on the successful track.
 - The closure takes the successful value, and returns a *Result*—something that may succeed, but may fail.
 - In other words, this closure is a track switch—it can switch us from the successful track to the failure track. If Gift fails to acquire the index, we get switched to the failure track.
 - (click) map *also* takes a closure that’s *only* run if we’re on the successful track. But unlike flatMap, it doesn’t return a Result—it just returns a value. In other words, it’s *not* a track switch—it can’t transition us to the failure track. (click)

Slide 121

Slide 121 text

let entryCountResult = openRepository(url) .flatMap { $0.index } .map { $0.entryCount } git status --staged | wc -l - flatMap takes a closure that’s run when we’re on the successful track—and only if we’re on the successful track.
 - The closure takes the successful value, and returns a *Result*—something that may succeed, but may fail.
 - In other words, this closure is a track switch—it can switch us from the successful track to the failure track. If Gift fails to acquire the index, we get switched to the failure track.
 - (click) map *also* takes a closure that’s *only* run if we’re on the successful track. But unlike flatMap, it doesn’t return a Result—it just returns a value. In other words, it’s *not* a track switch—it can’t transition us to the failure track. (click)

Slide 122

Slide 122 text

let entryCountResult = openRepository(url) .flatMap { $0.index } .map { $0.entryCount } git status --staged | wc -l - flatMap takes a closure that’s run when we’re on the successful track—and only if we’re on the successful track.
 - The closure takes the successful value, and returns a *Result*—something that may succeed, but may fail.
 - In other words, this closure is a track switch—it can switch us from the successful track to the failure track. If Gift fails to acquire the index, we get switched to the failure track.
 - (click) map *also* takes a closure that’s *only* run if we’re on the successful track. But unlike flatMap, it doesn’t return a Result—it just returns a value. In other words, it’s *not* a track switch—it can’t transition us to the failure track. (click)

Slide 123

Slide 123 text

enum Result { func map(transform: SuccessType -> NewType) -> Result func flatMap( transform: SuccessType -> Result) -> Result } - Let’s go back and reinforce this using the actual method definitions.
 - (Click) Both are methods on Result—this is the track we’re currently working on.
 - (Click) And both return a Result—we’re continuing down the track.

Slide 124

Slide 124 text

enum Result { func map(transform: SuccessType -> NewType) -> Result func flatMap( transform: SuccessType -> Result) -> Result } - Let’s go back and reinforce this using the actual method definitions.
 - (Click) Both are methods on Result—this is the track we’re currently working on.
 - (Click) And both return a Result—we’re continuing down the track.

Slide 125

Slide 125 text

enum Result { func map(transform: SuccessType -> NewType) -> Result func flatMap( transform: SuccessType -> Result) -> Result } - Let’s go back and reinforce this using the actual method definitions.
 - (Click) Both are methods on Result—this is the track we’re currently working on.
 - (Click) And both return a Result—we’re continuing down the track.

Slide 126

Slide 126 text

enum Result { func map(transform: SuccessType -> NewType) -> Result func flatMap( transform: SuccessType -> Result) -> Result } - Let’s go back and reinforce this using the actual method definitions.
 - (Click) Both are methods on Result—this is the track we’re currently working on.
 - (Click) And both return a Result—we’re continuing down the track.

Slide 127

Slide 127 text

enum Result { func map(transform: SuccessType -> NewType) -> Result func flatMap( transform: SuccessType -> Result) -> Result } - map takes a single argument: a closure that, given the Result’s current type, returns a new type.
 - In our previous example, this took an index, and returned an integer representing the number of entries
 - flatMap also takes a single argument, also a closure. This one, given the Result’s current type, returns a *Result* of the new type. In our previous example, it took a repository, and *tried* to return the index—a result that could have failed. A failure brings us on the failure track.

Slide 128

Slide 128 text

enum Result { func map(transform: SuccessType -> NewType) -> Result func flatMap( transform: SuccessType -> Result) -> Result } - map takes a single argument: a closure that, given the Result’s current type, returns a new type.
 - In our previous example, this took an index, and returned an integer representing the number of entries
 - flatMap also takes a single argument, also a closure. This one, given the Result’s current type, returns a *Result* of the new type. In our previous example, it took a repository, and *tried* to return the index—a result that could have failed. A failure brings us on the failure track.

Slide 129

Slide 129 text

enum Result { func map(transform: SuccessType -> NewType) -> Result func flatMap( transform: SuccessType -> Result) -> Result } - map takes a single argument: a closure that, given the Result’s current type, returns a new type.
 - In our previous example, this took an index, and returned an integer representing the number of entries
 - flatMap also takes a single argument, also a closure. This one, given the Result’s current type, returns a *Result* of the new type. In our previous example, it took a repository, and *tried* to return the index—a result that could have failed. A failure brings us on the failure track.

Slide 130

Slide 130 text

let entryCountResult = openRepository(url) .flatMap { $0.index } .map { $0.entryCount } git status --staged | wc -l - Put that all together, and we get this: clean, readable code.

Slide 131

Slide 131 text

tradeoff? - It seems like Results are very powerful: you can chain operations that can fail. In Objective-C, this meant tons of code that checked errors, or tons of nested blocks. - So what’s the tradeoff of using Result enums? What do we *lose* by using them?

Slide 132

Slide 132 text

return database.monkey("Peepers") .flatMap { database.tricycle($0) } .map { $0.brandName } - Arguably, readability. - This method is great if you know what a result enum is, and if you know what flatMap and map do. - But to the untrained eye, it’s a little hard to decipher. - Luckily, I think this is becoming a more and more common pattern in Swift, so people will get used to it. - And there are a lot of great talks today that will help you learn more about map and flatMap, in case you don’t already.

Slide 133

Slide 133 text

1. Precise 2. Convenient - 1. Using types to express how to use the API. Pro: Leave correcting your consumers to the compiler, not runtime exceptions. Con: More verbose.
 - 2. Use operators like Result.flatMap to allow users to chain failing operations. Pro: Convenient to use, easy chaining. Con: Hard to understand to the untrained eye.

Slide 134

Slide 134 text

Thanks! - Thanks for listening.
 - Any questions?
 - Make sure to hit next slide!!

Slide 135

Slide 135 text

Thanks! - Thanks for listening.
 - Any questions?
 - Make sure to hit next slide!!

Slide 136

Slide 136 text

@modocache
 github.com/LlamaKit/LlamaKit github.com/modocache/Gift github.com/SwiftGit2/SwiftGit2 - I didn’t talk about SwiftGit2, but it’s a library I’m developing with the folks at GitHub that uses a lot of the same techniques as Gift. Check out both!