You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
256 lines
5.4 KiB
Lua
256 lines
5.4 KiB
Lua
-- https://github.com/Sleitnick/LuaOption
|
|
--[[
|
|
MatchTable {
|
|
Some: (value: any) -> any
|
|
None: () -> any
|
|
}
|
|
|
|
CONSTRUCTORS:
|
|
|
|
Option.Some(anyNonNilValue): Option<any>
|
|
Option.Wrap(anyValue): Option<any>
|
|
|
|
|
|
STATIC FIELDS:
|
|
|
|
Option.None: Option<None>
|
|
|
|
|
|
STATIC METHODS:
|
|
|
|
Option.Is(obj): boolean
|
|
|
|
|
|
METHODS:
|
|
|
|
opt:Match(): (matches: MatchTable) -> any
|
|
opt:IsSome(): boolean
|
|
opt:IsNone(): boolean
|
|
opt:Unwrap(): any
|
|
opt:Expect(errMsg: string): any
|
|
opt:ExpectNone(errMsg: string): void
|
|
opt:UnwrapOr(default: any): any
|
|
opt:UnwrapOrElse(default: () -> any): any
|
|
opt:And(opt2: Option<any>): Option<any>
|
|
opt:AndThen(predicate: (unwrapped: any) -> Option<any>): Option<any>
|
|
opt:Or(opt2: Option<any>): Option<any>
|
|
opt:OrElse(orElseFunc: () -> Option<any>): Option<any>
|
|
opt:XOr(opt2: Option<any>): Option<any>
|
|
opt:Contains(value: any): boolean
|
|
|
|
--------------------------------------------------------------------
|
|
|
|
Options are useful for handling nil-value cases. Any time that an
|
|
operation might return nil, it is useful to instead return an
|
|
Option, which will indicate that the value might be nil, and should
|
|
be explicitly checked before using the value. This will help
|
|
prevent common bugs caused by nil values that can fail silently.
|
|
|
|
|
|
Example:
|
|
|
|
local result1 = Option.Some(32)
|
|
local result2 = Option.Some(nil)
|
|
local result3 = Option.Some("Hi")
|
|
local result4 = Option.Some(nil)
|
|
local result5 = Option.None
|
|
|
|
-- Use 'Match' to match if the value is Some or None:
|
|
result1:Match {
|
|
Some = function(value) print(value) end;
|
|
None = function() print("No value") end;
|
|
}
|
|
|
|
-- Raw check:
|
|
if result2:IsSome() then
|
|
local value = result2:Unwrap() -- Explicitly call Unwrap
|
|
print("Value of result2:", value)
|
|
end
|
|
|
|
if result3:IsNone() then
|
|
print("No result for result3")
|
|
end
|
|
|
|
-- Bad, will throw error bc result4 is none:
|
|
local value = result4:Unwrap()
|
|
|
|
--]]
|
|
local CLASSNAME = "Option"
|
|
|
|
local Option = {}
|
|
Option.__index = Option
|
|
|
|
function Option._new(value)
|
|
local self = setmetatable({
|
|
ClassName = CLASSNAME,
|
|
_v = value,
|
|
_s = (value ~= nil),
|
|
}, Option)
|
|
return self
|
|
end
|
|
|
|
function Option.Some(value)
|
|
assert(value ~= nil, "Option.Some() value cannot be nil")
|
|
return Option._new(value)
|
|
end
|
|
|
|
function Option.Wrap(value)
|
|
if (value == nil) then
|
|
return Option.None
|
|
else
|
|
return Option.Some(value)
|
|
end
|
|
end
|
|
|
|
function Option.Is(obj)
|
|
return (type(obj) == "table" and getmetatable(obj) == Option)
|
|
end
|
|
|
|
function Option.Assert(obj)
|
|
assert(Option.Is(obj), "Result was not of type Option")
|
|
end
|
|
|
|
function Option.Deserialize(data) -- type data = {ClassName: string, Value: any}
|
|
assert(type(data) == "table" and data.ClassName == CLASSNAME, "Invalid data for deserializing Option")
|
|
return (data.Value == nil and Option.None or Option.Some(data.Value))
|
|
end
|
|
|
|
function Option:Serialize()
|
|
return {
|
|
ClassName = self.ClassName,
|
|
Value = self._v,
|
|
}
|
|
end
|
|
|
|
function Option:Match(matches)
|
|
local onSome = matches.Some
|
|
local onNone = matches.None
|
|
assert(type(onSome) == "function", "Missing 'Some' match")
|
|
assert(type(onNone) == "function", "Missing 'None' match")
|
|
if (self:IsSome()) then
|
|
return onSome(self:Unwrap())
|
|
else
|
|
return onNone()
|
|
end
|
|
end
|
|
|
|
function Option:IsSome()
|
|
return self._s
|
|
end
|
|
|
|
function Option:IsNone()
|
|
return (not self._s)
|
|
end
|
|
|
|
function Option:Expect(msg)
|
|
assert(self:IsSome(), msg)
|
|
return self._v
|
|
end
|
|
|
|
function Option:ExpectNone(msg)
|
|
assert(self:IsNone(), msg)
|
|
end
|
|
|
|
function Option:Unwrap()
|
|
return self:Expect("Cannot unwrap option of None type")
|
|
end
|
|
|
|
function Option:UnwrapOr(default)
|
|
if (self:IsSome()) then
|
|
return self:Unwrap()
|
|
else
|
|
return default
|
|
end
|
|
end
|
|
|
|
function Option:UnwrapOrElse(defaultFunc)
|
|
if (self:IsSome()) then
|
|
return self:Unwrap()
|
|
else
|
|
return defaultFunc()
|
|
end
|
|
end
|
|
|
|
function Option:And(optB)
|
|
if (self:IsSome()) then
|
|
return optB
|
|
else
|
|
return Option.None
|
|
end
|
|
end
|
|
|
|
function Option:AndThen(andThenFunc)
|
|
if (self:IsSome()) then
|
|
local result = andThenFunc(self:Unwrap())
|
|
Option.Assert(result)
|
|
return result
|
|
else
|
|
return Option.None
|
|
end
|
|
end
|
|
|
|
function Option:Or(optB)
|
|
if (self:IsSome()) then
|
|
return self
|
|
else
|
|
return optB
|
|
end
|
|
end
|
|
|
|
function Option:OrElse(orElseFunc)
|
|
if (self:IsSome()) then
|
|
return self
|
|
else
|
|
local result = orElseFunc()
|
|
Option.Assert(result)
|
|
return result
|
|
end
|
|
end
|
|
|
|
function Option:XOr(optB)
|
|
local someOptA = self:IsSome()
|
|
local someOptB = optB:IsSome()
|
|
if (someOptA == someOptB) then
|
|
return Option.None
|
|
elseif (someOptA) then
|
|
return self
|
|
else
|
|
return optB
|
|
end
|
|
end
|
|
|
|
function Option:Filter(predicate)
|
|
if (self:IsNone() or not predicate(self._v)) then
|
|
return Option.None
|
|
else
|
|
return self
|
|
end
|
|
end
|
|
|
|
function Option:Contains(value)
|
|
return (self:IsSome() and self._v == value)
|
|
end
|
|
|
|
function Option:__tostring()
|
|
if (self:IsSome()) then
|
|
return ("Option<" .. type(self._v) .. ">")
|
|
else
|
|
return "Option<None>"
|
|
end
|
|
end
|
|
|
|
function Option:__eq(opt)
|
|
if (Option.Is(opt)) then
|
|
if (self:IsSome() and opt:IsSome()) then
|
|
return (self:Unwrap() == opt:Unwrap())
|
|
elseif (self:IsNone() and opt:IsNone()) then
|
|
return true
|
|
end
|
|
end
|
|
return false
|
|
end
|
|
|
|
Option.None = Option._new()
|
|
|
|
return Option
|