mirror of
https://github.com/CompeyDev/rusty-luau.git
synced 2024-12-12 12:50: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
|
||||||
end,
|
end,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
-- TODO: Implement equality and arithmetic metamethods
|
||||||
|
-- TODO: Implement __iter, once iterators traits exist
|
||||||
)
|
)
|
||||||
end
|
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 {
|
return {
|
||||||
-- TODO: Implement Option utility methods
|
|
||||||
Option = Option,
|
Option = Option,
|
||||||
Some = Some,
|
Some = Some,
|
||||||
None = None,
|
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 = {}
|
local Result = {}
|
||||||
export type Result<T, E> = typeof(Result) & {
|
export type Result<T, E> = typeof(Result) & {
|
||||||
_value: T,
|
_value: T,
|
||||||
|
@ -52,6 +46,7 @@ function Result.new<T, E>(val: T, err: E)
|
||||||
error(`eq: cannot compare {tostring(self)} and {tostring(other)}`)
|
error(`eq: cannot compare {tostring(self)} and {tostring(other)}`)
|
||||||
end,
|
end,
|
||||||
|
|
||||||
|
-- TODO: Implement equality and arithmetic metamethods
|
||||||
-- TODO: Implement __iter, once iterators traits exist
|
-- 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
|
return false
|
||||||
end
|
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>
|
function Result.map<T, E, U>(self: Result<T, E>, op: (val: T) -> U): Result<U, E>
|
||||||
if self:isOk() then
|
if self:isOk() then
|
||||||
return Result.new(op(self._value), self._error) :: Result<U, E>
|
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
|
return false
|
||||||
end
|
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 {
|
return {
|
||||||
Ok = Ok,
|
Ok = Ok,
|
||||||
Err = Err,
|
Err = Err,
|
||||||
Result = Result,
|
Result = Result,
|
||||||
}
|
}
|
||||||
|
|
||||||
-- print(y:transpose()) -- this should have a typeerror, i need to fix this
|
|
||||||
|
|
Loading…
Reference in a new issue