When Saul Mora last talked about functional programming, we saw by using small, micro functions, a nasty, complex and hard to track function could eventually be written as a pipeline of smaller functions. But using only optionals to pipe functions together is not enough to take full advantage of this technique. This try! Swift NYC talk shows how, with the help of a small but useful Monad called Result (or Either), you can take your functional programming powers to the next level.
Result Oriented Programming In Swift (00:00)
Previously on Adventures in Functional Programming in Swift, we talked about taking a function like the one below, that was basically directly converted from Objective-C:
func old_and_busted_expired(fileURL: NSURL) -> Bool {
let fileManager = NSFileManager()
if let filePath = fileURL.path {
if fileManager.fileExistsAtPath(filePath) {
var error: NSError?
let fileAttributes = fileManager.attributesOfItemAtPath(filePath, error: &error)
if let fileAttributes = fileAttributes {
if let creationDate = fileAttributes[NSFileModificationDate] as? NSDate {
return creationDate.isBefore(NSDate.oneDayAgo())
}
}
else {
NSLog("No file attributes \(filePath)")
}
}
}
return true
}
…and turned into Swift. This is a direct translation. We threw in a new function called bind, bind(A?, f(A)->B?) -> B?
, we gave it a nice custom functional style operator, and then we turned into >>-(A?, f(A)->B?) -> B?
. This reads a little easier, it has extracted some of the common functions, and it made it concise.
We learned that we could take this idea from functional programming, and pipe functions together; we could use functions to build other functions that chain functions together, and this is really useful.
func expired(fileURL: NSURL) -> Bool {
return fileURL.path
>>- fileExists
>>- retrieveFileAttributes
>>- extractCreationDate
>>- checkExpired
?? true
}
But now we want to go further. We’ve already noticed we have this Result
type. Result
is actually the Optional
type with more power. It’s really good at error handling (not just for error handling’s sake), helping to make your code easier to read. It makes your error handling code look exactly like your non-error handling code. You do not have to have all these if
statements all over your code just to handle errors.
Result (01:16)
Let’s look at how we would build the Result
type.
enum Optional<T>
{
case none
case some(_ value: T)
}
Let’s start with the Optional
. Optional
has two cases. It’s just an enumeration in Swift, and we have something with a value - an associated value - or we have nothing. What we want is we want to associate a value with the nothing side of Optional
.
enum Result<T>
{
case success(_ value: T)
case failure(_ error: NSError)
}
Since it’s not none anymore, since we gave it an error, we want to change that to something like failure. Failure does not necessarily match with the sum. Something is good, but it’s not an appropriate name for this scenario.
Let’s go ahead and rename that as well. We still have two cases in our enumeration, we have a success with a value, and failure with an error.
enum Result<T, E: Error>
{
case success(_ value: T)
case failure(_ error: E)
}
This is no longer an Optional
. This is what we call Result
. We have the Optional
type - it’s generic; you can wrap any type in Objective or in Swift with an Optional
.
You can make it look like this, Result
with a success and failure, two cases, and you have an error.
enum Result<T>
{
case success(_ value: T)
case failure(_ error: NSError)
}
You can see many Result
implementations using a generic error as a parameter, and you can put any error that you want to be much more specific about your error and result type. For this talk, we will assume that everything is an NSError
. It makes the slides a lot cleaner, and it helps you think about what the result actually is, without focusing on the error as much as having to specify all of the bits about errors. Let’s just run with this version of Result
.
Let’s go back to the Optional
style. Say we have a function like the one below, and we return an optional string. This is nice shortcut syntactic sugar.
func doSomethingFun() -> String?
{
return "跟朋友们一起学习说中文 🇨🇳"
}
What actually happens is, you have this Optional
wrapper around the String
.
func doSomethingFun() -> Optional<String>
{
return .some("跟朋友们一起学习说中文 🇨🇳")
}
Under the covers, you get your String
value wrapped in the Optional
. You have to unwrap it when you use this. There, using the Result
type is very similar. You would have something like this.
func doSomethingFun() -> Result<String>
{
return .success("Ride bike across the Rocky Mountains 🚵")
}
You have a Result
with the specified type that you’re inside the result value, and you would have a success case, so you wrap your value, in this case a string, inside that success case.
If you wanted to use the Result
type, you could simply have the results from the function, and you can do a switch
case. That’s pretty simple.
let result = doSomethingFun()
switch result {
case .success(let funThing):
print("Had fun: \(funThing)")
case .error:
print("Error having fun 😢")
}
But we can do more than this. Let’s take a look at the Optional
type from the Swift standard library. If you go ahead and look at the optional headers from the Swift library in Xcode, you will get something that looks like this.
/// A type that can represent either a `Wrapped` value or `nil`, the absence
/// of a value.
public enum Optional<Wrapped> : _Reflectable, NilLiteralConvertible {
case None
case Some(Wrapped)
/// Construct a `nil` instance.
public init()
/// Construct a non-`nil` instance that stores `some`.
public init(_ some: Wrapped)
/// If `self == nil`, returns `nil`. Otherwise, returns `f(self!)`.
@warn_unused_result
public func map<U>(@noescape f: (Wrapped) throws -> U) rethrows -> U?
/// Returns `nil` if `self` is `nil`, `f(self!)` otherwise.
@warn_unused_result
public func flatMap<U>(@noescape f: (Wrapped) throws -> U?) rethrows -> U?
/// Create an instance initialized with `nil`.
public init(nilLiteral: ())
}
Notice that you have, obviously, the None
and the Some
, and you have got the generic parameters and everything, but you also have two functions: map
and flatMap
. That is really useful; you can map
an Optional
in Swift.
Map vs. FlatMap (05:07)
Map is a very easy concept: it is a one-to-one mapping of an input type to an output type. For example, this collection of bananas, we map
it, translate it to a collection of apples. We just do a one-to-one mapping. Very easy.
func map<U>(T-> U) -> FunctorOf<U>
FlatMap is similar. We want to do this map
transformation of a collection, but in case of where we have nested multiple structure, or a deeper hierarchy, it will flatten it by one level. It is a flattened step after the map
step. It has the opportunity to remove some values that are invalid, or to flatten your arrays in some cases. It is very similar to map
, but still very useful in the concept of functional programming. Which brings me to this idea of functors and monads.
func flatMap<U>(T -> MondadOf<U>) -> MonadOf<U>
Functor And Monads (06:22)
A functor is a type that wraps another type, T - FunctorOf<T>
. We can obviously see that with our optional, and our result, that has a map function. The functional purists will give you this three-step definition that has to be mathematically correct for everything to be done, but for programming, this is how we want to do it.
A functor is a type that has a map
function. Monads are very similar - it is a type that wraps another type, T. We have monadsOf<T>
, that has a flatMap
. Functors have a map, monads have a flatMap. Very simple.
How do we take that and use that in our programming, in our actual code?
In the code below, we have a string
, a value of a string; in this case, we are using Optional
s, using the non-shortcut syntax with the question mark. If we wanted to use the map
, we could just do something like this, if we use our binding, our operating shortcut syntax, that is nice.
let string: String? = "string value"
let string = Optional<String>("string value")
string.map { print($0) }
string >>- { print($0) }
func map<U>(_ transform: T -> U) -> U?
{
switch self {
case .some(let x):
return .some(transform(x))
case .none:
return .none
}
}
If you wanted to use a map
function, inside the map
function, we are actually doing something like this in Optional
s. We switch
on ourselves. This is a function inside of that enumeration that we just unwrap it. Once we unwrapped the value, in this case, x
, we are going to apply the transform that we are given, and then we are going to rewrap it, and send it back out as a return value.
Of course, if we have none, we are going to return nothing. We are always returning the functor monad in this case - we are always returning something. For the result type, we are going to do something similar with the map
function. We have switch
, we have success, and failure.
enum Result<T>
{
case success(_ value: T)
case failure(_ error: Error)
func map<U>(_ transform: T -> U) -> U
{
switch self {
case .success(let value):
return transform(value)
case .failure(let error):
return Result<T>.failure(error)
}
}
}
let result: Result<String> = doSomething()
result
.map { value in
print("Had fun: \(value)")
}
.mapError { error -> NSError in
print("Error having fun: \(error)")
}
There are two cases here. In success, we are going to apply the transform, and return the value, and if you notice the transform parameter in this case, it takes a T
, and returns a result of U
. The transform can just be returned directly. The result of the transform directly comes out of this map
function.
In the case of the failure step, what were doing is we are just remapping that error into a new result, and returning that instead. We are actually doing something, we are always returning a value; we’re guaranteed to return results of something here, but it could be a value, or it could be an error.
You could do this with the flatMap
version as well; it’s very similar. It would look like this once we start using it. One of the things you notice many times, when you are using Optional
s, you will have people doing if-lets to unwrap it explicitly outside of the things, whereas a map
type of function will actually encapsulate all of the unwrapping and the switch
statements inside the enumeration, and keeping that stuff clean in there, that’s a nice little trick, letting the and enumeration have this function on top of it.
Now what we can do is we can take our result and map
the value. We have map
, and value, and we can just operate on it; we are actually letting the map
function unwrap that value for us. But what happens if there’s an error, if it has an error instead of the value?
It turns out the implementations of the Result
type actually has a map error function helper that lets you unwrap the error, and many times what will happen is, the map
will happen, but the map error will never happen, or the other way around. You will never get both of these executing in the same sequence; it’s one or the other. It’s a different way to handle it. This is not always the right way to handle the result value, but is a really nice clean way to do it in some cases.
Micro-Networking Framework (10:30)
Let’s talk about a real-life example: micro-networking frameworks.
Building HTTP stacks seems to be a good thing to try on this, because many times what I want to do, when I am requesting data from the Interwebs, is I just want to focus on the actual data type that I am requesting. I don’t really want to worry about the plumbing, or know that it is HTTP, or know about all the details. I want an API that says, I want to request data asynchronously from somewhere, give me back the actual data type that I am looking for, and I can move on with this operation.
A while ago, you might have seen a blog post by Chris Eidhof about micro-networking libraries. He had a nice little function where you can have an API request, and a bunch of parameters. The nice thing about this is it has the URL and it has this idea of a resource, which has a bunch of other things that apply directly to a networking endpoint - an HTTP server - and it also has a completion block.
func apiRequest<A> (
modifyRequest: NSMutableURLRequest -> (),
baseURL: NSURL,
resource: Resource<A>,
failure: (Reason, NSData?) -> (),
completion: A -> ()
)
func request<R: HTTPResource> (
resource: R,
cachePolicy: URLRequest.CachePolicy,
requestTimeout: TimeInterval,
host: HTTPHost?,
completion: (Result<R.RequestedType>) -> Void
)
What we have is a request, and a resource. What does this resource look like? We have a resource protocol, and in this protocol, we have a Result
type. This resource has a particular associated value. Say you have a person object, this is a very generic way, so we’re going to have this generic type associated with the protocol, and we’re also going to have an error type specified.
In addition, we have various other things that we need in order to request this resource off the network. One of them is the path; the other one is the query method. All of these are details that I really do not care about when I am talking asynchronously, from a view controller or something higher level within my app. I have a path, a method, a query method, query parameters, and I have a parse function, and in this case, this parse function has a result in there.
Actually this parse function was written incorrectly in a previous block of code; this is the correct version. It has an alias to another function, and this uses the Swift 3 syntax, where you can actually have generic typealias
es. You can pass the generic parameter to the next type. This is nice. I have a very generic way to specify all of my parsing over HTTP.
public protocol HTTPResourceProtocol
{
associatedtype ResultType
associatedtype ErrorType: HTTPResourceError
var path: String { get }
var method: HTTPMethod { get }
var queryParameters: [String: String] { get }
var parse: Result<ResourceDataType, HTTPResponseError> { get }
}
public typealias ResourceParseFunction<ResourceDataType> =
(Data) -> Result<ResourceDataType, HTTPResponseError>
public protocol HTTPResourceProtocol
{
associatedtype ResultType
associatedtype ErrorType: HTTPResourceError
var path: String { get }
var method: HTTPMethod { get }
var queryParameters: [String: String] { get }
var parse: ResourceParseFunction<ResultType> { get }
}
Getting back to how I generally would ideally handle things when I get a response back from an HTTP response: first of all, maybe I want to validate the response code. Did I get a 401, did I get a 500 error, did I get a 201, or something that I need that I can do, that I can handle right away, and return the value fast? If that is okay, I will probably try to re-validate, or pre-process the data.
After that, I will say, the data is good, it is not corrupt, and I will deserialize it with JSON, and then I will decode it with my awesome JSON parser, whichever one was off of that list of your choice from yesterday’s talk. Then eventually, I am going to call a completion handler.
You remember my API had a completion block. The completion handler is something that we use these days, and in this case, we passed a result to the completion block. I want to do all of those, and this is what an implementation of that might look like.
func completionHandlerForRequest<R: HTTPResource>(
resource: R,
validate: ResponseValidationFunction,
completion: @escaping (Result<R.RequestedType>) -> Void
)
-> (Data?, URLResponse?, Error?) -> Void
{
return { (data, response, error) in
_ = Result(response as? HTTPURLResponse, failWith: .InvalidResponseType)
>>- validateResponse(error)
>>- validate(data)
>>- resource.parse
>>- completion
}
}
In this particular case, I have created a function that returns a function that will process an HTTP result. It looks a little weird, because the curly braces after the return statement are defining another function. We’re going to focus on just the stuff in the middle here. This looks like your standard completion handler to the NSURLSession
task methods. We’ll walk through this really quick.
return { (data, response, error) in
_ = Result(response as? HTTPURLResponse,
failWith: .InvalidResponseType)
>>- validateResponse(error)
>>- validate(data)
>>- resource.parse
>>- completion
}
First of all, we want to build this pipeline. We have these bind operators with the arrow-dash syntax, and that helps us build the pipeline. In order to start the pipeline, we have to fill it with result first. In this particular implementation of result, we can cast this response optionally as HTTPURLResponse
. If we fail, we’ll give it some failure error type that we already have defined. If we already failed, and it is returning something that is not an HTTPURLResponse
, the rest of the pipeline will fall through with that initial error.
After that, we’ll go to the validate response with error. I’m creating a function that will return a function. In this case, I’m going to pre-populate it with the error that I have at this particular point. I am only using, in that pipeline, the second half of that, the HTTPURLResponse
returning the HTTPURLResponse
in the result type. Again, we are going to go to the next step in the pipeline, another function that returns the function, and it repeats, and it goes again.
func validateResponse(_ error: Error?) ->
(HTTPURLResponse?) -> Result<HTTPURLResponse,
HTTPResponseError>
func validate(Data?) -> (HTTPURLResponse) ->
Result<Data>
response.parse: (Data) -> Result<ResourceType>
validateResponse :
(HTTPURLResponse?) -> Result<HTTPURLResponse>
customValidate :
(HTTPURLResponse) -> Result<Data>
response.parse :
(Data) -> Result<ResourceType>
completion :
(Result<ResourceType>) -> Void
In this case, on that resource, we also have a parse function. The parse function is defined as taking data and returning the type that I requested in the first place, that I specified in the HTTP resource as the type that I am actually looking for. But first, I am just getting basically binary data, something from NSData
, or in this case, Swift 3, data as a type.
You can see the pipeline here as well. We take validate, and we have our custom validate, and we have a parse, and then completion. It goes from URL response, to URL response, and then that result is unwrapped, and piped into the next function, which takes our URL response, that returns a result, the data goes into the next function, in the parsed function, and that returns a result of the resource type. That output gets piped as the input for the next function for the completion block, for the completion handler.
This is all part of our pipeline, and this pipeline works because all of the outputs of the previous function can be used as inputs for the next function. That is how you chain functions together: the outputs have to match the inputs. When we look at this, we can see that we are just taking the result, the data, the response, and chaining it through a functional pipeline using the result.
What’s nice about this is that, when you use Result
s here, if you fail at any one of those steps, the result, the error will propagate through all the way to the completion block. You are not writing if fail, call, create an NSError
, and then call the completion block with the failure type, or with our failure completion block, if success, call it with something else, with the success type. The success and the failure, they look identical. Success, failure code is the same code; it’s only one code. That’s what is nice about doing it this way.
As an example of how I have implemented this, I have written an API client to a site, a service called Gauges. It’s an HTTP analytics site, you can put it on your website, or your websites, and we have a site gauge with a site ID; I am looking for data.
func siteGauge(siteID: SiteID) -> HTTPResource<Gauge>
{
let path = "gauges/\(siteID)"
return HTTPResource(path: path, parse: parse(rootKey: "gauge"))
}
request(resource: siteGauge(siteID: "my_site_id")) {
$0.map { site in
print("Found \(site)")
}
}
In this case, the Gauge
in the HTTPResource
parameter type is the actual object that I want that has the data that I am looking for. I’m requesting all of the information for a particular site ID, and the path is this gauge’s site ID. I just create a nice path. Then I put it into this request function that has been configured to use a particular host, and it will connect the host base URL with the site gauge URL, with the parameters, and it will build the whole request for me based on all the information I already gave it from the previous function. When I call it to this request function, I can handle it by taking the result from the request asynchronously, and then map that result, and unwrap it using my handy dandy map function.
You could also do it like this to handle your errors, or if you like to do it the old way to be much more explicit, you can also handle it with the switch
statement.
request(resource: siteGauge(siteID: "my_site_id")) {
result in
result.map { site in
print("Found \(site)")
}
.mapError { error -> HTTPResourceError in
print("Error requesting resource: \(error)")
return error
}
}
request(resource: siteGauge(siteID: "my_site_id")) {
result in
switch result {
case .success(let site):
print("Found \(site)”)
case .failure(let error):
print("Error getting site: \(error)")
}
}
Personally, I prefer using the map
syntax, because sometimes I do not want to handle the errors. Sometimes it’s okay to let those fall through. Not always; please handle your errors.
Libraries (20:02)
Do I have to re-implement Result
to get all the awesomeness? No, you don’t have to. If you want to reuse Result
s, you can look at a particular implementation on GitHub. If you want to look at the CoreHTTP library that I wrote, that is implementing this style of coding, you can grab it on GitHub.
Receive news and updates from Realm straight to your inbox