mirror of
https://github.com/CompeyDev/rusty-luau.git
synced 2024-12-12 04:40:40 +00:00
feat: finalize Option
& Result
implementations
This commit is contained in:
parent
9d78c7edec
commit
ef451dace6
3 changed files with 315 additions and 49 deletions
97
lib/conversion.luau
Normal file
97
lib/conversion.luau
Normal file
|
@ -0,0 +1,97 @@
|
|||
--[[
|
||||
This exists because if the Option module were to have methods to convert
|
||||
to Result, and the Result module were to have methods to convert to Option,
|
||||
there would be a cyclic dependency.
|
||||
|
||||
So, if a consumer of this library wants to convert between the two, they
|
||||
would rather import this conversion module.
|
||||
]]
|
||||
|
||||
local option = require("option")
|
||||
local Option = option.Option
|
||||
export type Option<T> = option.Option<T>
|
||||
local None = option.None
|
||||
local Some = option.Some
|
||||
|
||||
local result = require("result")
|
||||
local Result = result.Result
|
||||
export type Result<T, E> = result.Result<T, E>
|
||||
local Ok = result.Ok
|
||||
local Err = result.Err
|
||||
|
||||
function Result.ok<T, E>(self: Result<T, E>): Option<T>
|
||||
if self:isOk() then
|
||||
if self._value == nil then
|
||||
return None()
|
||||
end
|
||||
|
||||
return Some(self._value)
|
||||
end
|
||||
|
||||
return None()
|
||||
end
|
||||
|
||||
function Result.err<T, E>(self: Result<T, E>): Option<E>
|
||||
if self:isErr() then
|
||||
return Option.new(self._error) :: Option<E>
|
||||
end
|
||||
|
||||
return None()
|
||||
end
|
||||
|
||||
function Result.transpose<T, E>(self: Result<Option<T>, E>): Option<Result<T, E>>
|
||||
-- TODO: Instead of checking whether values are nil, use
|
||||
-- utility methods for Options once available
|
||||
if self._value == None() then
|
||||
return None()
|
||||
elseif self:isOkAnd(function(val): boolean
|
||||
return val._optValue == nil
|
||||
end) then
|
||||
return Some(Ok(self._value._optValue))
|
||||
elseif self:isErr() then
|
||||
return Some(Err(self._error))
|
||||
end
|
||||
|
||||
error("`Result` is not transposable")
|
||||
end
|
||||
|
||||
function Option.okOr<T, E>(self: Option<T>, err: E): Result<T, E>
|
||||
if self:isSome() then
|
||||
return Ok(self._optValue)
|
||||
end
|
||||
|
||||
return Err(err)
|
||||
end
|
||||
|
||||
function Option.okOrElse<T, E>(self: Option<T>, err: () -> E): Result<T, E>
|
||||
if self:isSome() then
|
||||
return Ok(self._optValue :: T)
|
||||
end
|
||||
|
||||
return Err(err())
|
||||
end
|
||||
|
||||
function Option.transpose<T, E>(self: Option<Result<T, E>>): Result<Option<T>, E>
|
||||
if self:isSome() then
|
||||
local inner = self._optValue
|
||||
assert(self.typeId == "Option" and inner.typeId == "Result", "Only an `Option` of a `Result` can be transposed")
|
||||
|
||||
if inner:isOk() then
|
||||
return Some(Ok(inner._value))
|
||||
elseif inner:isErr() then
|
||||
return Some(Err(inner._error))
|
||||
end
|
||||
end
|
||||
|
||||
return Ok(None())
|
||||
end
|
||||
|
||||
return {
|
||||
Ok = Ok,
|
||||
Err = Err,
|
||||
Result = Result,
|
||||
|
||||
Some = Some,
|
||||
None = None,
|
||||
Option = Option,
|
||||
}
|
218
lib/option.luau
218
lib/option.luau
|
@ -30,11 +30,227 @@ function Option.new<T>(val: T?)
|
|||
end
|
||||
end,
|
||||
}
|
||||
|
||||
-- TODO: Implement equality and arithmetic metamethods
|
||||
-- TODO: Implement __iter, once iterators traits exist
|
||||
)
|
||||
end
|
||||
|
||||
function Option.isSome<T>(self: Option<T>): boolean
|
||||
return self._optValue ~= nil
|
||||
end
|
||||
|
||||
function Option.isSomeAnd<T>(self: Option<T>, op: (val: T) -> boolean): boolean
|
||||
-- We know that it's not None, so this is fine
|
||||
return self:isSome() and op(self._optValue :: T)
|
||||
end
|
||||
|
||||
function Option.isNone<T>(self: Option<T>): boolean
|
||||
return not self:isSome()
|
||||
end
|
||||
|
||||
function Option.expect<T>(self: Option<T>, msg: string): T | never
|
||||
if self:isSome() then
|
||||
return self._optValue :: T
|
||||
end
|
||||
|
||||
return error(`panic: {msg}; expected Option to be type Option::Some(T), got Option::None`)
|
||||
end
|
||||
|
||||
function Option.unwrap<T>(self: Option<T>): T | never
|
||||
if self:isSome() then
|
||||
return self._optValue :: T
|
||||
end
|
||||
|
||||
return error("called `Option:unwrap()` on a `None` value")
|
||||
end
|
||||
|
||||
function Option.unwrapOr<T>(self: Option<T>, default: T): T
|
||||
if self:isSome() then
|
||||
return self._optValue :: T
|
||||
end
|
||||
|
||||
return default
|
||||
end
|
||||
|
||||
function Option.unwrapOrElse<T>(self: Option<T>, default: () -> T): T
|
||||
if self:isSome() then
|
||||
return self._optValue :: T
|
||||
end
|
||||
|
||||
return default()
|
||||
end
|
||||
|
||||
function Option.map<T, U>(self: Option<T>, op: (x: T) -> U?): Option<U>
|
||||
if self:isSome() then
|
||||
local val = op(self._optValue :: T)
|
||||
|
||||
if val == nil then
|
||||
return None()
|
||||
end
|
||||
|
||||
return Some(val)
|
||||
end
|
||||
|
||||
return None()
|
||||
end
|
||||
|
||||
function Option.inspect<T>(self: Option<T>, op: (x: T) -> nil): Option<T>
|
||||
if self:isSome() then
|
||||
op(self._optValue :: T)
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
function Option.mapOr<T, U>(self: Option<T>, default: U, op: (val: T) -> U)
|
||||
if self:isSome() then
|
||||
return op(self._optValue :: T)
|
||||
end
|
||||
|
||||
return default
|
||||
end
|
||||
|
||||
function Option.mapOrElse<T, U>(self: Option<T>, default: () -> U, op: (val: T) -> U): U
|
||||
if self:isSome() then
|
||||
return op(self._optValue :: T)
|
||||
end
|
||||
|
||||
return default()
|
||||
end
|
||||
|
||||
-- TODO: Iterator traits
|
||||
function Option.iter() end
|
||||
|
||||
function Option.and_<T, U>(self: Option<T>, optb: Option<U>): Option<U>
|
||||
if self:isSome() then
|
||||
return optb
|
||||
end
|
||||
|
||||
return None()
|
||||
end
|
||||
|
||||
function Option.andThen<T, U>(self: Option<T>, op: (val: T) -> Option<U>): Option<U>
|
||||
if self:isSome() then
|
||||
return op(self._optValue :: T)
|
||||
end
|
||||
|
||||
return None()
|
||||
end
|
||||
|
||||
function Option.filter<T>(self: Option<T>, predicate: (val: T) -> boolean): Option<T>
|
||||
if self:isSome() then
|
||||
if predicate(self._optValue :: T) then
|
||||
return self
|
||||
end
|
||||
end
|
||||
|
||||
return None()
|
||||
end
|
||||
|
||||
function Option.or_<T>(self: Option<T>, optb: Option<T>): Option<T>
|
||||
if self:isSome() then
|
||||
return self
|
||||
end
|
||||
|
||||
return optb
|
||||
end
|
||||
|
||||
function Option.orElse<T>(self: Option<T>, op: () -> Option<T>): Option<T>
|
||||
if self:isSome() then
|
||||
return self
|
||||
end
|
||||
|
||||
return op()
|
||||
end
|
||||
|
||||
function Option.xor<T>(self: Option<T>, optb: Option<T>): Option<T>
|
||||
if self:isSome() and optb:isSome() then
|
||||
return None()
|
||||
end
|
||||
|
||||
if self:isSome() then
|
||||
return self
|
||||
elseif optb:isSome() then
|
||||
return optb
|
||||
end
|
||||
|
||||
return None()
|
||||
end
|
||||
|
||||
function Option.insert<T>(self: Option<T>, val: T): T
|
||||
self._optValue = val
|
||||
return self._optValue :: T
|
||||
end
|
||||
|
||||
function Option.getOrInsert<T>(self: Option<T>, val: T): T
|
||||
if self:isNone() then
|
||||
self._optValue = val
|
||||
end
|
||||
|
||||
return self._optValue :: T
|
||||
end
|
||||
|
||||
function Option.take<T>(self: Option<T>): Option<T>
|
||||
if self:isSome() then
|
||||
local val = self._optValue :: T
|
||||
self._optValue = nil
|
||||
return Some(val)
|
||||
end
|
||||
|
||||
return None()
|
||||
end
|
||||
|
||||
function Option.replace<T>(self: Option<T>, val: T): Option<T>
|
||||
local current: Option<T> = self
|
||||
self._optValue = val
|
||||
|
||||
if current:isNone() then
|
||||
return current
|
||||
end
|
||||
|
||||
return Some(current._optValue :: T)
|
||||
end
|
||||
|
||||
function Option.contains<T>(self: Option<T>, val: T): boolean
|
||||
if self:isSome() then
|
||||
return self._optValue == val
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
function Option.zip<T, U>(self: Option<T>, other: Option<U>): Option<{ T | U }>
|
||||
if self:isSome() and other:isSome() then
|
||||
return Some({ self._optValue, other._optValue })
|
||||
end
|
||||
|
||||
return None()
|
||||
end
|
||||
|
||||
function Option.zipWith<T, U, R>(self: Option<T>, other: Option<U>, op: (x: T, y: U) -> R?): Option<R>
|
||||
if self:isSome() and other:isSome() then
|
||||
local computed = op(self._optValue :: T, other._optValue :: U)
|
||||
|
||||
if computed ~= nil then
|
||||
return Some(computed :: R)
|
||||
end
|
||||
end
|
||||
|
||||
return None()
|
||||
end
|
||||
|
||||
function Option.unzip<T, A, B>(self: Option<T>): (Option<A>, Option<B>)
|
||||
if self:isSome() then
|
||||
if self:isSome() and typeof(self._optValue) == "table" and #self._optValue == 2 then
|
||||
return Some(self._optValue[1] :: A), Some(self._optValue[2] :: B)
|
||||
end
|
||||
end
|
||||
|
||||
return None(), None()
|
||||
end
|
||||
|
||||
return {
|
||||
-- TODO: Implement Option utility methods
|
||||
Option = Option,
|
||||
Some = Some,
|
||||
None = None,
|
||||
|
|
|
@ -1,9 +1,3 @@
|
|||
local option = require("option")
|
||||
local Option = option.Option
|
||||
type Option<T> = option.Option<T>
|
||||
local None = option.None
|
||||
local Some = option.Some
|
||||
|
||||
local Result = {}
|
||||
export type Result<T, E> = typeof(Result) & {
|
||||
_value: T,
|
||||
|
@ -52,6 +46,7 @@ function Result.new<T, E>(val: T, err: E)
|
|||
error(`eq: cannot compare {tostring(self)} and {tostring(other)}`)
|
||||
end,
|
||||
|
||||
-- TODO: Implement equality and arithmetic metamethods
|
||||
-- TODO: Implement __iter, once iterators traits exist
|
||||
}
|
||||
)
|
||||
|
@ -81,26 +76,6 @@ function Result.isErrAnd<T, E>(self: Result<T, E>, predicate: (val: E) -> boolea
|
|||
return false
|
||||
end
|
||||
|
||||
function Result.ok<T, E>(self: Result<T, E>): Option<T>
|
||||
if self:isOk() then
|
||||
if self._value == nil then
|
||||
return None()
|
||||
end
|
||||
|
||||
return Some(self._value)
|
||||
end
|
||||
|
||||
return None()
|
||||
end
|
||||
|
||||
function Result.err<T, E>(self: Result<T, E>): Option<E>
|
||||
if self:isErr() then
|
||||
return Option.new(self._error) :: Option<E>
|
||||
end
|
||||
|
||||
return None()
|
||||
end
|
||||
|
||||
function Result.map<T, E, U>(self: Result<T, E>, op: (val: T) -> U): Result<U, E>
|
||||
if self:isOk() then
|
||||
return Result.new(op(self._value), self._error) :: Result<U, E>
|
||||
|
@ -260,30 +235,8 @@ function Result.containsErr<T, E>(self: Result<T, E>, err: E): boolean
|
|||
return false
|
||||
end
|
||||
|
||||
function Result.transpose<T, E>(self: Result<Option<T>, E>): Option<Result<T, E>>
|
||||
-- TODO: Instead of checking whether values are nil, use
|
||||
-- utility methods for Options once available
|
||||
if self._value == None() then
|
||||
return None()
|
||||
elseif self:isOkAnd(function(val): boolean
|
||||
return val._optValue == nil
|
||||
end) then
|
||||
return Some(Ok(self._value._optValue))
|
||||
elseif self:isErr() then
|
||||
return Some(Err(self._error))
|
||||
end
|
||||
|
||||
error("`Result` is not transposable")
|
||||
end
|
||||
|
||||
local x: Result<Option<string>, string> = Ok(None())
|
||||
|
||||
print(tostring(x))
|
||||
|
||||
return {
|
||||
Ok = Ok,
|
||||
Err = Err,
|
||||
Result = Result,
|
||||
}
|
||||
|
||||
-- print(y:transpose()) -- this should have a typeerror, i need to fix this
|
||||
|
|
Loading…
Reference in a new issue