Swift APIs: Getting Results

Swift APIs: Getting Results

Compelling APIs are precise and convenient. Explore how Swift enums can help you define a precise API that prevents user error at compile time.

F921fa5d507b31ef6984fd3d77ae710c?s=128

Brian Gesiak

March 21, 2015
Tweet

Transcript

  1. share your code (click to start)

  2. 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?
  3. Brian Gesiak
 @modocache - I’m Brian Gesiak. I go by

    modocache on GitHub and Twitter.
  4. - I’m the creator of Quick, the Swift and Objective-C

    testing framework.
 - Normally I work at Facebook in New York, but today…
  5. 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…
  6. 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.
  7. tradeoffs - Instead, we'll focus on *tradeoffs*: what you'll *gain*,

    and what you'll *lose* by using each technique.
  8. 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...
  9. - ... bananas in your application, and it’s called (click)

    BananaKit.
  10. BananaKit - ... bananas in your application, and it’s called

    (click) BananaKit.
  11. Series A
 B
 C
 D
 - BananaKit has been *years*

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

    in the making, made possible by *extensive investments* into the product.
  13. @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.
  14. @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.
  15. @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.
  16. @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.
  17. 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*.
  18. 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*.
  19. 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.
  20. 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.
  21. 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.
  22. 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.
  23. 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.
  24. 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.
  25. 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...
  26. 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...
  27. MonkeyDB - ...MonkeyDB.

  28. @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.
  29. @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.
  30. @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.
  31. @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.
  32. @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.
  33. @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.
  34. 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.
  35. 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.
  36. 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.
  37. 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.
  38. 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.
  39. 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.
  40. 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.
  41. // ...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.
  42. // ...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.
  43. // ...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.
  44. 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.
  45. 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.
  46. 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.
  47. 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.
  48. /** The result of an operation that could have failed.

    */ enum Result<SuccessType, ErrorType> { /** 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.
  49. /** The result of an operation that could have failed.

    */ enum Result<SuccessType, ErrorType> { /** 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.
  50. /** The result of an operation that could have failed.

    */ enum Result<SuccessType, ErrorType> { /** 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.
  51. LlamaKit - It’s called LlamaKit.
 - Whereas BananaKit is a

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

    a private beta, LlamaKit is open to everyone on GitHub. (click)
  53. struct MonkeyDB { init(url: NSURL) func monkey(name: String) -> Result<Monkey,

    NSError> func tricycle(monkey: Monkey) -> Result<Tricycle, NSError> } - 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.
  54. struct MonkeyDB { init(url: NSURL) func monkey(name: String) -> Result<Monkey,

    NSError> func tricycle(monkey: Monkey) -> Result<Tricycle, NSError> } - 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.
  55. struct MonkeyDB { init(url: NSURL) func monkey(name: String) -> Result<Monkey,

    NSError> func tricycle(monkey: Monkey) -> Result<Tricycle, NSError> } - 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.
  56. 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<Tricycle, NSError>`.
 - 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.
  57. let result = database.tricycle(monkey) return result.brandName 'Result<Tricycle, NSError>' 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<Tricycle, NSError>`.
 - 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.
  58. 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.
  59. 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.
  60. 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.
  61. 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.
  62. 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.
  63. 1. Precise 2. Convenient - But is it convenient?

  64. 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.
  65. 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.
  66. 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.
  67. 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.
  68. 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.
  69. 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.
  70. 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.
  71. 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.
  72. 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.
  73. 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.
  74. 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.
  75. enum Result<SuccessType, ErrorType> { func map<NewType>(transform: SuccessType -> NewType) ->

    Result<NewType, ErrorType> func flatMap<NewType>( transform: SuccessType -> Result<NewType, ErrorType>) -> Result<NewType, ErrorType> } - 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.
  76. 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.
  77. 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.
  78. 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.
  79. 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.
  80. 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.
  81. 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.
  82. 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.
  83. 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.
  84. 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.
  85. 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.
  86. 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.
  87. 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.
  88. 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.
  89. 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.
  90. 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.
  91. 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.
  92. 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.
  93. 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.
  94. 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.
  95. 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.
  96. 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.
  97. 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
  98. 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
  99. 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
  100. 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
  101. - 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.
  102. - Earlier this year I created a library that allows

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

    you to interact with Git from Swift.
  104. 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.
  105. 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.
  106. 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.
  107. 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.
  108. 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.
  109. 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.
  110. 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.
  111. 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.
  112. 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.
  113. 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.
  114. 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.
  115. 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.
  116. 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?
  117. 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?
  118. 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?
  119. 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)
  120. 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)
  121. 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)
  122. 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)
  123. enum Result<SuccessType, ErrorType> { func map<NewType>(transform: SuccessType -> NewType) ->

    Result<NewType, ErrorType> func flatMap<NewType>( transform: SuccessType -> Result<NewType, ErrorType>) -> Result<NewType, ErrorType> } - 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.
  124. enum Result<SuccessType, ErrorType> { func map<NewType>(transform: SuccessType -> NewType) ->

    Result<NewType, ErrorType> func flatMap<NewType>( transform: SuccessType -> Result<NewType, ErrorType>) -> Result<NewType, ErrorType> } - 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.
  125. enum Result<SuccessType, ErrorType> { func map<NewType>(transform: SuccessType -> NewType) ->

    Result<NewType, ErrorType> func flatMap<NewType>( transform: SuccessType -> Result<NewType, ErrorType>) -> Result<NewType, ErrorType> } - 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.
  126. enum Result<SuccessType, ErrorType> { func map<NewType>(transform: SuccessType -> NewType) ->

    Result<NewType, ErrorType> func flatMap<NewType>( transform: SuccessType -> Result<NewType, ErrorType>) -> Result<NewType, ErrorType> } - 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.
  127. enum Result<SuccessType, ErrorType> { func map<NewType>(transform: SuccessType -> NewType) ->

    Result<NewType, ErrorType> func flatMap<NewType>( transform: SuccessType -> Result<NewType, ErrorType>) -> Result<NewType, ErrorType> } - 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.
  128. enum Result<SuccessType, ErrorType> { func map<NewType>(transform: SuccessType -> NewType) ->

    Result<NewType, ErrorType> func flatMap<NewType>( transform: SuccessType -> Result<NewType, ErrorType>) -> Result<NewType, ErrorType> } - 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.
  129. enum Result<SuccessType, ErrorType> { func map<NewType>(transform: SuccessType -> NewType) ->

    Result<NewType, ErrorType> func flatMap<NewType>( transform: SuccessType -> Result<NewType, ErrorType>) -> Result<NewType, ErrorType> } - 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.
  130. 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.
  131. 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?
  132. 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.
  133. 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.
  134. Thanks! - Thanks for listening.
 - Any questions?
 - Make

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

    sure to hit next slide!!
  136. @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!