Last active
June 9, 2024 22:29
-
-
Save willnationsdev/46bd72b62ae5888ca63fbfc3b414f6af to your computer and use it in GitHub Desktop.
GDScript Result class with usage example.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Example usage | |
static func example_connect_to_network() -> Result: | |
return Result.fail(ERR_CANT_CONNECT) | |
static func example_load_file(conn: ENetConnection) -> Error: | |
return ERR_FILE_CORRUPT | |
class ApiResult: | |
extends RefCounted | |
var http_code | |
var body := "" | |
var error := OK | |
var msg := "" | |
func _init(code = 200, res_body = "", e = null, m = null): | |
http_code = code | |
body = res_body | |
error = e | |
msg = m | |
static func example_try_transform_through_api(buffer) -> ApiResult: | |
return ApiResult.new(500, "", ERR_BUG, "Failed to suplex buffer.") | |
static func example() -> void: | |
# Calling context can continuously flow like a `try` block, even with an error. | |
# Not limited to Result-returning logic thanks to ease of lambda functions. | |
# Unlike exceptions... | |
# - keeps runtime engine simpler & faster. | |
# - does not interrupt code flow & thus improves "reasonability". | |
# - the type system clearly notifies you of when you are engaging in | |
# operations that invoke conditional logic (w/ even more clarity if had generics). | |
var rst := Result \ | |
.ok() \ | |
.then(example_connect_to_network) \ | |
.then(example_load_file, true) \ | |
.then(func(data: PackedByteArray): | |
var res = example_try_transform_through_api(data) | |
return Result.new(res.body, res.error, res.msg)) \ | |
.then(func(data: PackedByteArray): | |
print(data.size())) | |
# Now to evaluate the aggregate results, similar to a | |
# `try` block followed by many `catch (SomeTypeOfException ex)` blocks. | |
if rst.error == OK: | |
# All attempted operations were successful! | |
# We also opted-out of executing subsequent operations (mostly). | |
print(rst.value) | |
else: | |
match rst.error: | |
ERR_CANT_CONNECT: pass | |
ERR_FILE_CORRUPT: pass | |
var other: pass |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
## A two-state faux "discriminated union" Result class. | |
## Similar to, say, a Result<obj, (int, obj)> in a FP language. | |
class_name Result | |
extends RefCounted | |
var value = null | |
var error: Error = OK | |
var error_data = null | |
func _init(value = null, error: Error = OK, error_data = null) -> void: | |
self.value = value | |
self.error = error | |
self.error_data = error_data | |
func then(on_success: Callable, convert_error := false) -> Result: | |
# Short-circuit if already have error, like an exception would. | |
if error != null: | |
return self | |
# Attempt subsequent operation. | |
# If we had generics, we could use static typing to guarantee a Result is returned. | |
# Instead, we have to add a type check. | |
# Results get merged into the calling instance while non-Results are adopted. | |
var ret = on_success.call(value) if on_success else value | |
if ret is Result: | |
if ret.error: | |
self.error = ret.error | |
self.error_data = ret.error_data | |
else: | |
self.value = ret.value | |
# Use this for Error-returning functions with side-effects where | |
# you want to continue going with the chain so long as OK is returned. | |
elif convert_error and ret != OK: | |
self.error = ret.error | |
self.error_data = ret.error_data | |
else: | |
self.value = ret | |
return self | |
# Optional exercise left for the reader. | |
# Can be useful if you want to slip in intermediate error handling. | |
func catch(on_error: Error) -> Result: | |
return Result.fail(on_error.call(null)) | |
# Factory methods for usability. | |
## Create successful result with a specific value. | |
static func ok(value = null) -> Result: | |
return Result.new(value) | |
## Create specific error with optional custom data (focus is on the error) | |
static func fail(error: Error, data = null) -> Result: | |
return Result.new(null, error, data) | |
## Create generic error with custom data (focus is on returning the data, e.g. a custom error object) | |
static func fail_with(data) -> Result: | |
return Result.new(null, FAILED, data) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment