- 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?
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…
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...
*/ @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.
*/ @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.
*/ @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.
*/ @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.
... } - 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*.
... } 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*.
*/ 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.
*/ 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.
*/ 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.
... 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.
... 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.
... 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.
{ 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...
... } 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...
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
*/ 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.
*/ 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.
*/ 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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
- 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.
- 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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
- 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
- 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
- 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
- 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
.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.
.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.
.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.
.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.
.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.
{ $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.
{ $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.
{ $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.
{ $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.
{ $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.
$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?
$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?
$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?
$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)
$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)
$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)
$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)
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.
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.
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.
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.
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.
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.
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.
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?
- 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.
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.