Skip to content

Instantly share code, notes, and snippets.

@brennanMKE
Last active April 21, 2022 17:25
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save brennanMKE/13d501155b368d222f54c369b7c11741 to your computer and use it in GitHub Desktop.
Save brennanMKE/13d501155b368d222f54c369b7c11741 to your computer and use it in GitHub Desktop.

Attempt or Fail

Using do/try/catch syntax is Swift seems to require too many lines of code. It adds so much ceremony in my code when there is a call which throws and I need to handle the error. There is no option to use a guard statement to compact the code a bit more. I'd like to use a Throw Coalescing Operator which does not exist the language. The solution I have come to is this attempt function which will take an expression and wrap the do/try/catch syntax and pass the Swift.Error to the fail closure. It enables various uses which are listed in the samples. The goal was to make code which is more compact.

What I'd like to see implemented in the Swift language is a guard equivalent which passes the thrown error with one or more expressions to the else block.

attempt try writeFiles(), try transformFiles() else { error in 
  handleError(error)
}

In this code the try statements can be put together in the same expression and if either of them throw it will be passed to the else statement which could return or not. It could return a Bool as well to indicate success or failure to work well with branching logic.

import Foundation
/// Attempt to run expression to return a value or fail.
/// - Parameters:
/// - expression: expression
/// - fail: error handler
/// - Returns: optional result
func attempt<T>(_ expression: @autoclosure () throws -> T,
fail: @autoclosure () -> ((Swift.Error) -> Void) = { _ in }) -> T? {
do {
return try expression()
} catch {
fail()(error)
return nil
}
}
/// Attemp to run an expression or fail.
/// - Parameters:
/// - expression: expression
/// - fail: error handler
/// - Returns: success
@discardableResult
func attempt(_ expression: @autoclosure () throws -> Void,
fail: @autoclosure () -> ((Swift.Error) -> Void) = { _ in }) -> Bool {
do {
try expression()
return true
} catch {
fail()(error)
return false
}
}
import Foundation
enum Failure: Error {
case notWorking
case stringInvalid
}
func makeUppercase(string: String) throws -> String {
let throwError = Bool.random()
if throwError {
throw Failure.stringInvalid
}
return string.uppercased()
}
func doWork() throws -> String {
let throwError = Bool.random()
if throwError {
throw Failure.notWorking
}
return "I'm doing it!"
}
func justRun() throws {
let throwError = Bool.random()
if throwError {
throw Failure.notWorking
}
}
func fail(_ error: Swift.Error) {
print("Error: \(error)")
}
func run() {
if attempt(try justRun(), fail: fail) {
print("Success")
} else {
print("Fail")
}
// inline expression which returns a string value
let result1 = attempt(try makeUppercase(string: "abc")) { error in
print("Error: \(error) [no result1]")
} ?? "I failed"
print(result1)
// inline expression which returns a string value
let result2 = attempt(try doWork()) { error in
print("Error: \(error) [no result2]")
} ?? "I failed"
print(result2)
// inline expression and fail function reference
let result3 = attempt(try doWork(), fail: fail) ?? "I failed"
print(result3)
// guard with fail function reference which returns on fail
guard let result4 = attempt(try doWork(), fail: fail) else {
print("else [result4]")
return
}
print(result4)
// guard with no fail function reference which returns on fail
guard let result5 = attempt(try doWork()) else {
print("else [result5]")
return
}
print(result5)
}
run()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment