zeeshankhan
11/7/2016 - 7:15 AM

A simple demonstration of Result

A simple demonstration of Result

import Foundation
import XCPlayground
XCPlaygroundPage.currentPage.needsIndefiniteExecution = true

typealias JSONDictionary = [String: Any]

enum SomeError : Error {
    case zoError
}

enum Result<T> {
    case success(T)
    case failure(Error)
}

extension Result {

    func map<U>(f: (T) -> U) -> Result<U> {

        switch self {
            case .success(let t):
                return .success(f(t))

            case .failure(let error):
                return .failure(error)
        }
    }

    func flatMap<U>(f: (T) -> Result<U>) -> Result<U> {

        switch self {
            case .success(let t):
                return f(t)

            case .failure(let error):
                return .failure(error)
        }
    }

}

extension Result {

    // Return the value if it is a .success or throw the error if it is a .failure
    func resolve() throws -> T {
        switch self {
            case Result.success(let value):
                return value

            case Result.failure(let error):
                throw error
        }
    }

    // Construct a .success if the expression returns a value or a .failure if it throws
    init( _ throwingExpression: (Void) throws -> T) {
        do {
            let value = try throwingExpression()
            self = Result.success(value)
        }
        catch {
            self = Result.failure(error)
        }
    }
}

struct Gist {
    let id: String
}

func jsonFrom(data: Data) -> Result<Any> {

    let json = try! JSONSerialization.jsonObject(with: data, options: [])
    return .success(json as Any)
}


func gistFrom(json: JSONDictionary) -> Gist {
    let id = json["id"] as! String
    return Gist(id: id)
}


func gistsFrom(json: Any) -> Result<[Gist]> {
    let gistsJSON = json as! [JSONDictionary]
    let gists = gistsJSON.flatMap {
        gistFrom(json: $0)
    }
    return .success(gists)
}

// May be we don't need no reason
// Can't stop.

func fetch(url: URL, completion: @escaping (Result<Data>) -> Void) {

    URLSession.shared.dataTask(with: url) { (data, response, error) in

        completion( Result {

            if let error = error { throw error }
            guard let data = data else { throw SomeError.zoError }
            return data
        })

    }.resume()

}


let url = URL(string: "https://api.github.com/gists/public")!

fetch(url: url) { (resultData: Result<Data>) in
    let resultGists = resultData
        .flatMap(f: jsonFrom)
        .flatMap(f: gistsFrom)

    // Then if we want to go back in the do/try/catch world for the rest of the code:
    do {
        let gists = try resultGists.resolve()
        print(gists)

    } catch {
        print("error")
    }
}