Skip to content

Instantly share code, notes, and snippets.

@designatednerd
Last active September 16, 2018 14:26
Show Gist options
  • Save designatednerd/368d48775b6cf71168fb941299ac9927 to your computer and use it in GitHub Desktop.
Save designatednerd/368d48775b6cf71168fb941299ac9927 to your computer and use it in GitHub Desktop.
Chainable Result
//: This is based on Vincent Pradeilles' NSSpain 2018 lightning talk. I tried to find a way around using a
//: custom operator for this because I haaaaate custom operators, but everything else I tried just led to
//: more callback hell.
//:
//: Custom `infix` operators allow you to pass the left hand side and right hand side of the operator as
//: the first and second parameters of a function. I couldn't figure out how to make a function which did that.
//:
//: Vincent's original approach is outlined here:
//: https://github.com/vincent-pradeilles/slides/blob/master/nsspain-2018-solving-callback-hell-with-good-old-function-composition.pdf
//:
//: Other differences:
//:
//: - His approach uses ~> as the operator - I use --> because I think it more accurately depicts
//: the idea that a function is being passed. Also getting to the tilde key makes my wrist hurt :P.
//: - I'm using a `Result` enum instead of a `(Result?, Error?)` tuple because I prefer that for avoiding
//: optional weirdness.
//: - I added support for throwing `map` functions and for all `filter` functions.
//:
//: Things I still haven't figured out how to do yet:
//:
//: - How to make the first function able to take a paramter but not need the completion block explicitly declared
//: - Threading
import UIKit
enum Result<T> {
case success(T)
case error(Error)
}
typealias CompletionHandler<T> = (Result<T>) -> Void
typealias CompoundHandler<T> = (CompletionHandler<T>) -> Void
func getInt(completion: CompletionHandler<Int>) {
completion(.success(42))
}
func divideInt(_ int: Int, completion: CompletionHandler<Int>) {
let divided = int / 2
completion(.success(divided))
}
enum StringConversionError: Error {
case notANumber
}
func convertToString(_ int: Int, completion: CompletionHandler<String>) {
let formatter = NumberFormatter()
formatter.numberStyle = .spellOut
guard let formatted = formatter.string(from: NSNumber(value: int)) else {
completion(.error(StringConversionError.notANumber))
return
}
completion(.success(formatted))
}
func toArray(int: Int) -> [Int] {
return [int, int + 1, int + 2]
}
func isEven(int: Int) -> Bool {
return (int % 2) == 0
}
enum FilterError: Error {
case emptyArray
}
func first(in array: [Int]) throws -> Int {
guard let first = array.first else {
throw FilterError.emptyArray
}
return first
}
// YE OLDE CALLBACK HELL
getInt(completion: { result in
switch result {
case .success(let int):
divideInt(int) { secondResult in
switch secondResult {
case .success(let divided):
convertToString(divided) { thirdResult in
switch thirdResult {
case .success(let string):
print(string)
case .error(let error):
debugPrint("Error: \(error)")
}
}
case .error(let error):
debugPrint("Error: \(error)")
}
}
case .error(let error):
debugPrint("Error: \(error)")
}
})
// Should print: "twenty-one"
// CHAINING
infix operator -->: MultiplicationPrecedence
func --> <T, U>(_ firstFunction: @escaping CompoundHandler<T>,
_ secondFunction: @escaping ((T, CompletionHandler<U>) -> Void)) -> CompoundHandler<U> {
return { completion in
firstFunction { result1 in
switch result1 {
case .success(let item):
secondFunction(item, { result2 in
completion(result2)
})
case .error(let error):
completion(.error(error))
}
}
}
}
// YE NEW LESS HELLISH CALLBACK
let chain1 = getInt -->
divideInt -->
convertToString
chain1({ result in
switch result {
case .success(let item):
print("Chain 1 Got: \(type(of: item)) - \(item)")
case .error(let error):
debugPrint("Chain 1 Error \(error)")
}
})
// Should print: "Chain 1 Got: String - twenty-one"
// MAPPING
func --> <T, U>(_ firstFunction: @escaping CompoundHandler<T>,
_ transformFunction: @escaping (T) throws -> U) -> CompoundHandler<U> {
return { completion in
firstFunction { result in
switch result {
case .success(let item):
do {
let transformed = try transformFunction(item)
completion(.success(transformed))
} catch let error {
completion(.error(error))
}
case .error(let error):
completion(.error(error))
}
}
}
}
// FILTERING
func --> <T>(_ firstFunction: @escaping CompoundHandler<[T]>,
_ filterFunction: @escaping (T) throws -> Bool) -> CompoundHandler<[T]> {
return { completion in
firstFunction { result in
switch result {
case .success(let array):
do {
let filtered = try array.filter(filterFunction)
completion(.success(filtered))
} catch let error {
completion(.error(error))
}
case .error(let error):
completion(.error(error))
}
}
}
}
// YE EVEN CALLBACKIER CALLBACK
let chain2 = getInt --> // Gets the integer
divideInt --> // Divides it
toArray --> // Maps to an array
isEven --> // Filters array by even numbers
first --> // Grabs first number in array
convertToString // Converts that first number to string
chain2({ result in
switch result {
case .success(let item):
print("Chain 2 Got: \(type(of: item)) - \(item)")
case .error(let error):
debugPrint("Chain 2 Error \(error)")
}
})
// Should print: "Chain 2 Got: String - twenty-two"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment