Last active
September 20, 2023 21:15
-
-
Save PrestonKnopp/00f488d13d3d6eabc6a5fbdb3560c1af to your computer and use it in GitHub Desktop.
Attempt at Optionals in GDScript
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
# optional.gd | |
# | |
# Caveat | |
# ------ | |
# This only works with types inheriting from Object. Built-in types don't work | |
# with the call api. Built-ins can still be fetched but they cannot be operated | |
# on. | |
# | |
# This can be fixed by subclassing optional and adding type specific methods | |
# that wrap the builtin type. | |
# | |
# Usage | |
# ----- | |
# var opt = Optional.new(val) | |
# match opt.kind(): | |
# opt.SOME: return 'It Exists!' | |
# opt.NONE: return 'It null!' | |
# | |
# match opt.kind_value(): | |
# [opt.SOME, var value]: print('Hello, ', value) | |
# [opt.NONE]: print('No Value') | |
# | |
# match opt.value(): | |
# null: pass | |
# var value: print('Hello, ', value) | |
# | |
# if opt.has_some(): | |
# opt.value().hello_world() | |
# | |
# if opt.has_none(): | |
# print('Value is null') | |
# | |
# # will only set if opt has some | |
# opt.some_property_of_val = 'woooo' | |
# # will get value of hello or return 'default_val' | |
# var hello = opt.hello.value('default_val') | |
# | |
# # call func | |
# opt.do('some_func', arg1, arg2, ..., arg12).value() | |
# | |
# # operate on optionals | |
# | |
# # This allows you to build up a query and if any of the steps fail | |
# # will return none optional. | |
# opt.do('some_func').eq(this).do('other_func').pass_to(obj, 'func').value() | |
# # The above could also be written as | |
# opt.some_func.eq(this).do_func.pass_to(obj, 'func').value() | |
# | |
# # This will pass the value of a some optional to my_obj_func and get the | |
# # result of that function as an optional. | |
# opt.pass_to(my_obj, 'my_obj_func', [pre_args], [post_args]) | |
# | |
# # funcs with no args can be called directly like accessing a property. | |
# # this will only work if there isn't a property of the same name. If there | |
# # is use the opt.do() func. | |
# opt.pass_to(my_obj, 'get_next').this_func.value() | |
extends Reference | |
# This is used as a unique identifier for invalid arguments to `do()` | |
class _N: | |
extends Reference | |
enum { | |
NONE, | |
SOME | |
} | |
var __value | |
func _init(nullable_value): | |
__value = nullable_value | |
func set_value(nullable_value): | |
""" | |
Set value to reuse this optional rather than create a new instance. | |
""" | |
__value = nullable_value | |
func make(nullable_value): | |
""" @override | |
Make new instance with `nullable_value`. | |
Override this method if want to do custom initialization or | |
use a pool of optionals to reduce memory usage. | |
Make can be overriden to set __value as `nullable_value` instead of | |
creating a new instance. Reference semantics could possibly make this | |
behavior confusing, but it would be more lightweight. | |
""" | |
return get_script().new(nullable_value) | |
func kind(): | |
""" | |
Get the kind (SOME or NONE) that this Optional is representing. | |
""" | |
if has_none(): | |
return NONE | |
return SOME | |
func kind_value(): | |
""" | |
Get a kind and the value tuple. Intended usage is for match bind | |
expression. | |
""" | |
if has_none(): | |
return [NONE] | |
return [SOME, value()] | |
func value(with_default=null): | |
""" | |
Get the value if this optional has some, otherwise get the default. | |
""" | |
if has_none(): | |
return with_default | |
return __value | |
func has_some(): | |
""" | |
Check if this optional has a non null value. | |
""" | |
return not has_none() | |
func has_none(): | |
""" | |
Check if this optional has a null value. | |
""" | |
if typeof(__value) == TYPE_OBJECT: | |
return weakref(__value) == null | |
return __value == null | |
# ------------------------------------------------------------------------------ | |
# Operations | |
# ------------------------------------------------------------------------------ | |
func _set(key, value): | |
""" | |
Set the `value` for `key` of this optional. | |
@usage | |
opt.do('get_child', 9001).some_prop = 'whatwhat' | |
""" | |
# returning true will prevent invalid set error being raised | |
if has_none(): | |
return true | |
__value.set(key, value) | |
return true | |
func _get(key): | |
""" | |
Get the value from `key`. If a property with key does not exist it will | |
attempt to call function with name `key`. If that fails it will throw an | |
error. | |
@usage | |
opt.some_prop | |
opt.some_func_call.some_prop | |
""" | |
if has_none(): | |
return self | |
if key in __value: | |
return make(__value.get(key)) | |
else: | |
return make(__value.call(key)) | |
func do_v(func_name, args=[]): | |
""" | |
Simulate callv. @see do() | |
@usage | |
opt.do_v('func_name', [one, two]) | |
""" | |
var first_NIL_idx = args.find(_N) | |
var a = args | |
# Remove _N args | |
if first_NIL_idx != -1: | |
args.resize(first_NIL_idx) | |
# INCOMING hack to workaround callv not failing with invalid func call | |
# e.g. calling Node.get_name(0) should error, but callv just returns null | |
var call_result | |
match args.size(): | |
0: call_result = __value.call(func_name) | |
1: call_result = __value.call(func_name, a[0]) | |
2: call_result = __value.call(func_name, a[0], a[1]) | |
3: call_result = __value.call(func_name, a[0], a[1], a[2]) | |
4: call_result = __value.call(func_name, a[0], a[1], a[2], a[3]) | |
5: call_result = __value.call(func_name, a[0], a[1], a[2], a[3], a[4]) | |
6: call_result = __value.call(func_name, a[0], a[1], a[2], a[3], a[4], a[5]) | |
7: call_result = __value.call(func_name, a[0], a[1], a[2], a[3], a[4], a[5], a[6]) | |
8: call_result = __value.call(func_name, a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7]) | |
9: call_result = __value.call(func_name, a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8]) | |
10: call_result = __value.call(func_name, a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8], a[9]) | |
11: call_result = __value.call(func_name, a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8], a[9], a[10]) | |
12: call_result = __value.call(func_name, a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8], a[9], a[10], a[11]) | |
return make(call_result) | |
func do(func_name, a1=_N, a2=_N, a3=_N, a4=_N, a5=_N, a6=_N, a7=_N, a8=_N, a9=_N, a10=_N, a11=_N, a12=_N): | |
""" | |
Call func `func_name` with arguments only when this has some value. | |
Returns and wraps the result of the call to `func_name`. | |
@usage | |
var n = Node.new() | |
n.name = 'whatwhat' | |
var opt = Optional.new(n) | |
opt.do('get_name') # some('whatwhat') | |
opt.get_name # some('whatwhat') | |
opt.name # some('whatwhat') | |
opt.do('get_child', 0) # none | |
""" | |
if has_none(): | |
return self | |
return do_v(func_name, [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12]) | |
func do_set(property, obj, func_name, args=[]): | |
""" | |
Set `proprety` with the result of `obj.func_name(args)`. | |
Only sets `property` and calls `func_name` when has some. Returns self. | |
@usage | |
Optional.new(john).residence.do_set('address', self, 'create_address') | |
""" | |
if has_none(): | |
return self | |
# saves from creating a new Optional instance by caching value | |
var cache_value = __value | |
__value = obj | |
set(property, do_v(func_name, args).value()) | |
__value = cache_value | |
return self | |
func eq(value): | |
""" | |
Check if some equals `value`. If true return self else return none. | |
@usage | |
opt.do('get_obj').some_prop.eq(expected).prop_of_some_prop = 'Whatever' | |
""" | |
if value == __value: | |
return self | |
return make(null) | |
func pass_to(obj, func_name, pre_args=[], post_args=[]): | |
""" | |
Pass some as an argument into `obj`.`func_name`() after `pre_args` and | |
before `post_args`. | |
@usage | |
var opt = Optional.new(two) | |
opt.pass_to(myobj, 'func_on_myobj', [one], [three]) | |
""" | |
if has_none(): | |
return self | |
return do_v(func_name, pre_args + [__value] + post_args) |
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
# reuse_optional.gd | |
# | |
# Each operation does not instance a new optional. It will set value of itself | |
# to the new value. | |
# | |
# This is more lightweight but can be weird with ref semantics. | |
# | |
# var opt = Optional.new(val).some_prop | |
# opt == Optional(result of some_prop) | |
# var next_opt = opt.do('func', arg) | |
# next_opt == opt == Optional(result of func(arg)) | |
extends 'optional.gd' | |
func make(nullable_value): | |
set_value(nullable_value) | |
return self |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment