mirror of
https://github.com/CompeyDev/rusty-luau.git
synced 2024-12-12 04:40:40 +00:00
docs + refactor: document all of Result
module & minor refactors/fixes
* Include moonwave doc comments for `Result` * Renamed `Option:getInner` to `Option:unwrapUnchecked` * Updated both `Option.__eq` and `Result.__eq` to support tables * Replaced nil returns in types with unit type * Make unimplemented methods throw an error * Rename `Result.exceptErr` to `Result.expectErr` * Add missing return type in `Result.unwrapOrElse`
This commit is contained in:
parent
4c7a4ee0fd
commit
fb97755cad
3 changed files with 705 additions and 46 deletions
|
@ -1,37 +1,4 @@
|
||||||
-- From https://gist.github.com/sapphyrus/fd9aeb871e3ce966cc4b0b969f62f539
|
local tableEq = require("util").tableEq
|
||||||
local function tableEq(tbl1, tbl2)
|
|
||||||
if tbl1 == tbl2 then
|
|
||||||
return true
|
|
||||||
elseif type(tbl1) == "table" and type(tbl2) == "table" then
|
|
||||||
for key1, value1 in pairs(tbl1) do
|
|
||||||
local value2 = tbl2[key1]
|
|
||||||
|
|
||||||
if value2 == nil then
|
|
||||||
-- avoid the type call for missing keys in tbl2 by directly comparing with nil
|
|
||||||
return false
|
|
||||||
elseif value1 ~= value2 then
|
|
||||||
if type(value1) == "table" and type(value2) == "table" then
|
|
||||||
if not tableEq(value1, value2) then
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
else
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- check for missing keys in tbl1
|
|
||||||
for key2, _ in pairs(tbl2) do
|
|
||||||
if tbl1[key2] == nil then
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
|
|
||||||
--[=[
|
--[=[
|
||||||
@class Option
|
@class Option
|
||||||
|
@ -911,14 +878,14 @@ end
|
||||||
local x: Option<string> = Some("lol")
|
local x: Option<string> = Some("lol")
|
||||||
local y: Option<string> = None()
|
local y: Option<string> = None()
|
||||||
|
|
||||||
assert(x:getInner() == "lol")
|
assert(x:unwrapUnchecked() == "lol")
|
||||||
assert(y:getInner() == nil)
|
assert(y:unwrapUnchecked() == nil)
|
||||||
```
|
```
|
||||||
|
|
||||||
@param self Option<T>
|
@param self Option<T>
|
||||||
@return T?
|
@return T?
|
||||||
]=]
|
]=]
|
||||||
function Option.getInner<T>(self: Option<T>): T?
|
function Option.unwrapUnchecked<T>(self: Option<T>): T?
|
||||||
return self._optValue
|
return self._optValue
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
672
lib/result.luau
672
lib/result.luau
|
@ -1,18 +1,111 @@
|
||||||
|
local tableEq = require("util").tableEq
|
||||||
|
|
||||||
|
--[=[
|
||||||
|
@class Result
|
||||||
|
|
||||||
|
Error handling with the [Result] type.
|
||||||
|
|
||||||
|
[Result]`<T, E>` is the type used for returning a possible error. It is a class with the
|
||||||
|
constructor variants, [Result:Ok]`(T)`, representing success and containing a value, and
|
||||||
|
[Result:Err]`(E)`, representing error and containing an error value.
|
||||||
|
|
||||||
|
```lua
|
||||||
|
local result: Result<number, string>
|
||||||
|
|
||||||
|
-- Both variants `Ok` and `Err` fit into the type
|
||||||
|
result = Ok(21)
|
||||||
|
result = Err("some error")
|
||||||
|
```
|
||||||
|
|
||||||
|
Functions return [Result] whenever errors are expected and recoverable.
|
||||||
|
|
||||||
|
A simple function returning [Result] might be defined and used like so:
|
||||||
|
```lua
|
||||||
|
local PAT = "v(%d+)"
|
||||||
|
|
||||||
|
function parseVersion(str: string): Result<number, string>
|
||||||
|
local version: number = string.match(str, PAT)
|
||||||
|
|
||||||
|
if version == 0 then
|
||||||
|
return Err("Version cannot be zero")
|
||||||
|
else if version == nil then
|
||||||
|
return Err("Invalid string")
|
||||||
|
end
|
||||||
|
|
||||||
|
return Ok(version)
|
||||||
|
end
|
||||||
|
|
||||||
|
print("Successful result: " .. parseVersion("v12"):display())
|
||||||
|
print("Error result: " .. parseVersion("vString"):display())
|
||||||
|
```
|
||||||
|
|
||||||
|
[Result] comes with some convenience methods that make working with it more succinct.
|
||||||
|
|
||||||
|
```lua
|
||||||
|
local goodResult: Result<number, number> = Ok(10)
|
||||||
|
local badResult: Result<number, number> = Err(10)
|
||||||
|
|
||||||
|
-- The `isOk`and `isErr` methods do what they say.
|
||||||
|
assert(goodResult:isOk() and not goodResult:isErr())
|
||||||
|
assert(badResult:isErr() and not badResult:isOk())
|
||||||
|
|
||||||
|
-- `map` produces a new result from an existing one
|
||||||
|
goodResult = goodResult:map(function(i) return i + 1 end)
|
||||||
|
badResult = badResult:map(function(i) return i - 1 end)
|
||||||
|
|
||||||
|
-- Use `and_then` to continue the computation.
|
||||||
|
local anotherGoodResult: Result<boolean, number> = goodResult:andThen(function(i) return Ok(i == 11) end)
|
||||||
|
|
||||||
|
-- Use `or_else` to handle the error
|
||||||
|
local badResult: Result<number, number> = bad_result:orElse(function(i) return Ok(i + 20) end)
|
||||||
|
|
||||||
|
-- Get the internal `Ok` value and panic if there is an `Err`
|
||||||
|
local finalAwesomeResult: boolean = anotherGoodResult:unwrap()
|
||||||
|
```
|
||||||
|
]=]
|
||||||
local Result = {}
|
local Result = {}
|
||||||
|
|
||||||
export type Result<T, E> = typeof(Result) & {
|
export type Result<T, E> = typeof(Result) & {
|
||||||
_value: T,
|
_value: T,
|
||||||
_error: E,
|
_error: E,
|
||||||
typeId: "Result",
|
typeId: "Result",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
--[=[
|
||||||
|
@within Result
|
||||||
|
|
||||||
|
Contains the success value.
|
||||||
|
|
||||||
|
@param val T
|
||||||
|
@return Result<T, E>
|
||||||
|
]=]
|
||||||
function Ok<T, E>(val: T): Result<T, E>
|
function Ok<T, E>(val: T): Result<T, E>
|
||||||
return Result.new(val, (nil :: unknown) :: E)
|
return Result.new(val, (nil :: unknown) :: E)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
--[=[
|
||||||
|
@within Result
|
||||||
|
|
||||||
|
Contains the error value.
|
||||||
|
|
||||||
|
@param err E
|
||||||
|
@return Result<T, E>
|
||||||
|
]=]
|
||||||
function Err<T, E>(err: E): Result<T, E>
|
function Err<T, E>(err: E): Result<T, E>
|
||||||
return Result.new((nil :: unknown) :: T, err)
|
return Result.new((nil :: unknown) :: T, err)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
--[=[
|
||||||
|
@within Result
|
||||||
|
@private
|
||||||
|
|
||||||
|
Constructs a [Result]. **For internal use only**.
|
||||||
|
|
||||||
|
For user consumption, see [Result:Ok] and [Result:Err].
|
||||||
|
|
||||||
|
@param err E
|
||||||
|
@return Result<T, E>
|
||||||
|
]=]
|
||||||
function Result.new<T, E>(val: T, err: E)
|
function Result.new<T, E>(val: T, err: E)
|
||||||
return setmetatable(
|
return setmetatable(
|
||||||
{
|
{
|
||||||
|
@ -43,7 +136,7 @@ function Result.new<T, E>(val: T, err: E)
|
||||||
return self._value == other._value and self._error == other._error
|
return self._value == other._value and self._error == other._error
|
||||||
end
|
end
|
||||||
|
|
||||||
error(`eq: cannot compare {tostring(self)} and {tostring(other)}`)
|
return tableEq(self._value, other._value) and tableEq(self._error, other._error)
|
||||||
end,
|
end,
|
||||||
|
|
||||||
-- TODO: Implement equality and arithmetic metamethods
|
-- TODO: Implement equality and arithmetic metamethods
|
||||||
|
@ -52,6 +145,22 @@ function Result.new<T, E>(val: T, err: E)
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
--[=[
|
||||||
|
@within Result
|
||||||
|
|
||||||
|
Returns `true` if the result is [Result:Ok].
|
||||||
|
|
||||||
|
```lua
|
||||||
|
local x: Result<number, string> = Ok(3)
|
||||||
|
assert(x:isOk())
|
||||||
|
|
||||||
|
x = Err("Some error message")
|
||||||
|
assert(not x:isOk())
|
||||||
|
```
|
||||||
|
|
||||||
|
@param self Result<T, E>
|
||||||
|
@return boolean
|
||||||
|
]=]
|
||||||
function Result.isOk<T, E>(self: Result<T, E>): boolean
|
function Result.isOk<T, E>(self: Result<T, E>): boolean
|
||||||
if self._value == nil then
|
if self._value == nil then
|
||||||
return false
|
return false
|
||||||
|
@ -60,14 +169,72 @@ function Result.isOk<T, E>(self: Result<T, E>): boolean
|
||||||
return true
|
return true
|
||||||
end
|
end
|
||||||
|
|
||||||
|
--[=[
|
||||||
|
@within Result
|
||||||
|
|
||||||
|
Returns `true` if the result is [Result:Ok] and the value inside it matches a
|
||||||
|
predicate.
|
||||||
|
|
||||||
|
```lua
|
||||||
|
local x: Result<number, string> = Ok(2)
|
||||||
|
assert(x:isOkAnd(function(x) return x > 1 end))
|
||||||
|
|
||||||
|
x = Ok(0)
|
||||||
|
assert(not x:isOkAnd(function(x) return x > 1 end))
|
||||||
|
|
||||||
|
x = Err("hey")
|
||||||
|
assert(not x:isOkAnd(function(x) return x > 1 end))
|
||||||
|
```
|
||||||
|
|
||||||
|
@param self Result<T, E>
|
||||||
|
@param predicate (val: T?) -> boolean
|
||||||
|
@return boolean
|
||||||
|
]=]
|
||||||
function Result.isOkAnd<T, E>(self: Result<T, E>, predicate: (val: T?) -> boolean): boolean
|
function Result.isOkAnd<T, E>(self: Result<T, E>, predicate: (val: T?) -> boolean): boolean
|
||||||
return self:isOk() and predicate(self._value)
|
return self:isOk() and predicate(self._value)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
--[=[
|
||||||
|
@within Result
|
||||||
|
|
||||||
|
Returns `true` if the result is [Result:Err].
|
||||||
|
|
||||||
|
```lua
|
||||||
|
local x: Result<number, string> = Ok(3)
|
||||||
|
assert(not x:isErr())
|
||||||
|
|
||||||
|
x = Err("Some error message")
|
||||||
|
assert(x:isErr())
|
||||||
|
```
|
||||||
|
|
||||||
|
@param self Result<T, E>
|
||||||
|
@return boolean
|
||||||
|
]=]
|
||||||
function Result.isErr<T, E>(self: Result<T, E>)
|
function Result.isErr<T, E>(self: Result<T, E>)
|
||||||
return not self:isOk()
|
return not self:isOk()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
--[=[
|
||||||
|
@within Result
|
||||||
|
|
||||||
|
Returns `true` if the result is [Result:Err] and the value inside it matches a
|
||||||
|
predicate.
|
||||||
|
|
||||||
|
```lua
|
||||||
|
local x: Result<number, string> = Ok(2)
|
||||||
|
assert(not x:isErrAnd(function(x) return x > 1 end))
|
||||||
|
|
||||||
|
x = Err(3)
|
||||||
|
assert(x:isErrAnd(function(x) return x > 1 end))
|
||||||
|
|
||||||
|
x = Err("hey")
|
||||||
|
assert(not x:isErrAnd(function(x) return x > 1 end))
|
||||||
|
```
|
||||||
|
|
||||||
|
@param self Result<T, E>
|
||||||
|
@param predicate (val: T?) -> boolean
|
||||||
|
@return boolean
|
||||||
|
]=]
|
||||||
function Result.isErrAnd<T, E>(self: Result<T, E>, predicate: (val: E) -> boolean): boolean
|
function Result.isErrAnd<T, E>(self: Result<T, E>, predicate: (val: E) -> boolean): boolean
|
||||||
if self:isErr() then
|
if self:isErr() then
|
||||||
return predicate(self._error)
|
return predicate(self._error)
|
||||||
|
@ -76,6 +243,42 @@ function Result.isErrAnd<T, E>(self: Result<T, E>, predicate: (val: E) -> boolea
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
|
|
||||||
|
--[=[
|
||||||
|
@within Result
|
||||||
|
|
||||||
|
Maps a [Result]<T, E> to [Result]<U, E> by applying a function to a contained [Result:Ok] value,
|
||||||
|
leaving an [Result:Err] value untouched.
|
||||||
|
|
||||||
|
This function can be used to compose the results of two functions.
|
||||||
|
|
||||||
|
```lua
|
||||||
|
local lines = "1\n2\n3\n4\n"
|
||||||
|
|
||||||
|
function parseInt(x: string): Result<number, string>
|
||||||
|
local num = tonumber(x)
|
||||||
|
|
||||||
|
if num == nil then
|
||||||
|
return Err("not an integer")
|
||||||
|
end
|
||||||
|
|
||||||
|
return Ok(num)
|
||||||
|
end
|
||||||
|
|
||||||
|
for line in lines:split("\n") do
|
||||||
|
local number = parseInt(line)
|
||||||
|
|
||||||
|
if number:isOk() then
|
||||||
|
print(number:unwrap())
|
||||||
|
else
|
||||||
|
print("not a number!")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
@param self Result<T, E>
|
||||||
|
@param op (val: T) -> U
|
||||||
|
@return Result<U, E>
|
||||||
|
]=]
|
||||||
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>
|
||||||
|
@ -84,6 +287,29 @@ function Result.map<T, E, U>(self: Result<T, E>, op: (val: T) -> U): Result<U, E
|
||||||
return Err(self._error)
|
return Err(self._error)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
--[=[
|
||||||
|
@within Result
|
||||||
|
|
||||||
|
Returns the provided default (if [Result:Err]), or applies a function to
|
||||||
|
the contained value (if [Result:Ok]).
|
||||||
|
|
||||||
|
Arguments passed to [Result:mapOr] are eagerly evaluated; if you are passing the
|
||||||
|
result of a function call, it is recommended to use [Result:mapOrElse], which is
|
||||||
|
lazily evaluated.
|
||||||
|
|
||||||
|
```lua
|
||||||
|
local x: Result<string, string> = Ok("foo")
|
||||||
|
assert(x:mapOr("bar", string.upper) == "FOO")
|
||||||
|
|
||||||
|
x = Err("foo")
|
||||||
|
assert(x:mapOr("bar", string.upper) == "bar")
|
||||||
|
```
|
||||||
|
|
||||||
|
@param self Result<T, E>
|
||||||
|
@param default U
|
||||||
|
@param op (val: T) -> U
|
||||||
|
@return U
|
||||||
|
]=]
|
||||||
function Result.mapOr<T, E, U>(self: Result<T, E>, default: U, op: (val: T) -> U): U
|
function Result.mapOr<T, E, U>(self: Result<T, E>, default: U, op: (val: T) -> U): U
|
||||||
if self:isOk() then
|
if self:isOk() then
|
||||||
return op(self._value)
|
return op(self._value)
|
||||||
|
@ -92,6 +318,27 @@ function Result.mapOr<T, E, U>(self: Result<T, E>, default: U, op: (val: T) -> U
|
||||||
return default
|
return default
|
||||||
end
|
end
|
||||||
|
|
||||||
|
--[=[
|
||||||
|
@within Result
|
||||||
|
|
||||||
|
Maps a [Result]<T, E> to U by applying fallback function default to a contained
|
||||||
|
[Result:Err] value, or function f to a contained [Result:Ok] value.
|
||||||
|
|
||||||
|
This function can be used to unpack a successful result while handling an error.
|
||||||
|
|
||||||
|
```lua
|
||||||
|
local x: Result<string, string> = Ok("foo")
|
||||||
|
assert(x:mapOrElse(function(x) return "error: ".. x end, string.upper) == "FOO")
|
||||||
|
|
||||||
|
x = Err("foo")
|
||||||
|
assert(x:mapOrElse(function(x) return "error: ".. x end, string.upper) == "error: foo")
|
||||||
|
```
|
||||||
|
|
||||||
|
@param self Result<T, E>
|
||||||
|
@param default (val: E) -> U
|
||||||
|
@param op (val: T) -> U
|
||||||
|
@return U
|
||||||
|
]=]
|
||||||
function Result.mapOrElse<T, E, U>(self: Result<T, E>, default: (val: E) -> U, op: (val: T) -> U): U
|
function Result.mapOrElse<T, E, U>(self: Result<T, E>, default: (val: E) -> U, op: (val: T) -> U): U
|
||||||
if self:isOk() then
|
if self:isOk() then
|
||||||
return op(self._value)
|
return op(self._value)
|
||||||
|
@ -100,6 +347,31 @@ function Result.mapOrElse<T, E, U>(self: Result<T, E>, default: (val: E) -> U, o
|
||||||
return default(self._error)
|
return default(self._error)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
--[=[
|
||||||
|
@within Result
|
||||||
|
|
||||||
|
Maps a [Result]<T, E> to [Result]<T, F> by applying a function to a contained
|
||||||
|
[Result:Err] value, leaving an [Result:Ok] value untouched.
|
||||||
|
|
||||||
|
This function can be used to pass through a successful result while handling an
|
||||||
|
error.
|
||||||
|
|
||||||
|
```lua
|
||||||
|
local function stringify(x: number): string
|
||||||
|
return string.format("error code: %d", x)
|
||||||
|
end
|
||||||
|
|
||||||
|
local x: Result<number, number> = Ok(2)
|
||||||
|
assert(x:mapErr(stringify) == Ok(2))
|
||||||
|
|
||||||
|
x = Err(13)
|
||||||
|
assert(x:mapErr(stringify) == Err("error code: 13"))
|
||||||
|
```
|
||||||
|
|
||||||
|
@param self Result<T, E>
|
||||||
|
@param op (val: E) -> F
|
||||||
|
@return Result<T, F>
|
||||||
|
]=]
|
||||||
function Result.mapErr<T, E, F>(self: Result<T, E>, op: (val: E) -> F): Result<T, F>
|
function Result.mapErr<T, E, F>(self: Result<T, E>, op: (val: E) -> F): Result<T, F>
|
||||||
if self:isErr() then
|
if self:isErr() then
|
||||||
return Result.new(self._value, op(self._error))
|
return Result.new(self._value, op(self._error))
|
||||||
|
@ -108,7 +380,35 @@ function Result.mapErr<T, E, F>(self: Result<T, E>, op: (val: E) -> F): Result<T
|
||||||
return Ok(self._value)
|
return Ok(self._value)
|
||||||
end
|
end
|
||||||
|
|
||||||
function Result.inspect<T, E>(self: Result<T, E>, op: (val: T) -> nil): Result<T, E>
|
--[=[
|
||||||
|
@within Result
|
||||||
|
|
||||||
|
Calls the provided closure with a reference to the contained value
|
||||||
|
(if [Result:Ok]).
|
||||||
|
|
||||||
|
```lua
|
||||||
|
function parseInt(x: string): Result<number, string>
|
||||||
|
local num = tonumber(x)
|
||||||
|
|
||||||
|
if num == nil then
|
||||||
|
return Err("not an integer")
|
||||||
|
end
|
||||||
|
|
||||||
|
return Ok(num)
|
||||||
|
end
|
||||||
|
|
||||||
|
local x = parseInt("4")
|
||||||
|
:inspect(function(x) print("original: " .. x) end)
|
||||||
|
:map(function(x) return x ^ 3 end)
|
||||||
|
:expect("not an integer")
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
@param self Result<T, E>
|
||||||
|
@param op (val: E) -> ()
|
||||||
|
@return Result<T, E>
|
||||||
|
]=]
|
||||||
|
function Result.inspect<T, E>(self: Result<T, E>, op: (val: T) -> ()): Result<T, E>
|
||||||
if self:isOk() then
|
if self:isOk() then
|
||||||
op(self._value)
|
op(self._value)
|
||||||
end
|
end
|
||||||
|
@ -116,7 +416,35 @@ function Result.inspect<T, E>(self: Result<T, E>, op: (val: T) -> nil): Result<T
|
||||||
return self
|
return self
|
||||||
end
|
end
|
||||||
|
|
||||||
function Result.inspectErr<T, E>(self: Result<T, E>, op: (val: E) -> nil): Result<T, E>
|
--[=[
|
||||||
|
@within Result
|
||||||
|
|
||||||
|
Calls the provided closure with a reference to the contained value
|
||||||
|
(if [Result:Err]).
|
||||||
|
|
||||||
|
```lua
|
||||||
|
function parseInt(x: string): Result<number, string>
|
||||||
|
local num = tonumber(x)
|
||||||
|
|
||||||
|
if num == nil then
|
||||||
|
return Err("not an integer")
|
||||||
|
end
|
||||||
|
|
||||||
|
return Ok(num)
|
||||||
|
end
|
||||||
|
|
||||||
|
local x = parseInt("string")
|
||||||
|
:inspectErr(function(x) print("error: " .. x) end)
|
||||||
|
:map(function(x) return x ^ 3 end)
|
||||||
|
:unwrap()
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
@param self Result<T, E>
|
||||||
|
@param op (val: E) -> ()
|
||||||
|
@return Result<T, E>
|
||||||
|
]=]
|
||||||
|
function Result.inspectErr<T, E>(self: Result<T, E>, op: (val: E) -> ()): Result<T, E>
|
||||||
if self:isErr() then
|
if self:isErr() then
|
||||||
op(self._error)
|
op(self._error)
|
||||||
end
|
end
|
||||||
|
@ -125,8 +453,56 @@ function Result.inspectErr<T, E>(self: Result<T, E>, op: (val: E) -> nil): Resul
|
||||||
end
|
end
|
||||||
|
|
||||||
-- TODO: Iterator traits
|
-- TODO: Iterator traits
|
||||||
function Result.iter() end
|
function Result.iter(): never
|
||||||
|
return error("Unimplemented: `Result:iter()`")
|
||||||
|
end
|
||||||
|
|
||||||
|
--[=[
|
||||||
|
@within Result
|
||||||
|
|
||||||
|
Returns the contained [Result:Ok] value, consuming the self value.
|
||||||
|
|
||||||
|
Because this function may panic, its use is generally discouraged. Instead,
|
||||||
|
prefer to use [Result:isErr] and handle the Err case explicitly, or call
|
||||||
|
[Result:unwrapOr], [Result:unwrapOrElse], or [Result:unwrapOrDefault].
|
||||||
|
Panics
|
||||||
|
|
||||||
|
@error panic -- If the value is a [Result:Err], with a panic message including the passed message, and the content of the `Err`.
|
||||||
|
|
||||||
|
```lua
|
||||||
|
local x: Result<number, string> = Err("emergency failure")
|
||||||
|
x:expect("Testing expect") -- panics with message `Testing expect: emergency failure`
|
||||||
|
```
|
||||||
|
|
||||||
|
## Recommended Message Style
|
||||||
|
It is recommended that expect messages are used to describe the reason you expect the
|
||||||
|
[Result] should be [Result:Ok].
|
||||||
|
|
||||||
|
```lua
|
||||||
|
local process = require("@lune/process")
|
||||||
|
|
||||||
|
local function envVar<T>(var: string): Result<T, string>
|
||||||
|
local val = process.env[var]
|
||||||
|
|
||||||
|
if val == nil then
|
||||||
|
return Err("environment variable not found")
|
||||||
|
end
|
||||||
|
|
||||||
|
Ok(val)
|
||||||
|
end
|
||||||
|
|
||||||
|
local path = envVar("IMPORTANT_PATH")
|
||||||
|
:expect("env variable `IMPORTANT_PATH` should be set by `wrapper_script.sh`")
|
||||||
|
```
|
||||||
|
|
||||||
|
**Hint**: If you’re having trouble remembering how to phrase expect error messages remember to focus
|
||||||
|
on the word “should” as in “env variable should be set by blah” or “the given binary should be available
|
||||||
|
and executable by the current user”.
|
||||||
|
|
||||||
|
@param self Result<T, E>
|
||||||
|
@param msg string
|
||||||
|
@return T | never
|
||||||
|
]=]
|
||||||
function Result.expect<T, E>(self: Result<T, E>, msg: string): T | never
|
function Result.expect<T, E>(self: Result<T, E>, msg: string): T | never
|
||||||
if self:isOk() then
|
if self:isOk() then
|
||||||
return self._value
|
return self._value
|
||||||
|
@ -135,6 +511,26 @@ function Result.expect<T, E>(self: Result<T, E>, msg: string): T | never
|
||||||
return error(`panic: {msg}; {self._error}`)
|
return error(`panic: {msg}; {self._error}`)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
--[=[
|
||||||
|
@within Result
|
||||||
|
|
||||||
|
Returns the [Result:Ok].
|
||||||
|
|
||||||
|
Because this function may panic, its use is generally discouraged. Instead, prefer to use
|
||||||
|
[Result:unwrapOr], [Result:unwrapOrElse], or [Result:unwrapOrDefault].
|
||||||
|
|
||||||
|
@error panic -- Panics if the value is an [Result:Err], with a panic message provided by the [Result:Err]’s value.
|
||||||
|
|
||||||
|
```lua
|
||||||
|
local x: Result<number, string> = Ok(2)
|
||||||
|
assert(x:unwrap() == 2)
|
||||||
|
|
||||||
|
x = Err("oh no")
|
||||||
|
x:unwrap() -- panics with `oh no`
|
||||||
|
```
|
||||||
|
|
||||||
|
@param self Result<T, E>
|
||||||
|
]=]
|
||||||
function Result.unwrap<T, E>(self: Result<T, E>): T | never
|
function Result.unwrap<T, E>(self: Result<T, E>): T | never
|
||||||
if self:isOk() then
|
if self:isOk() then
|
||||||
return self._value
|
return self._value
|
||||||
|
@ -143,11 +539,28 @@ function Result.unwrap<T, E>(self: Result<T, E>): T | never
|
||||||
return error(`panic: \`Result:unwrap()\` called on an \`Err\` value: {self._error}`)
|
return error(`panic: \`Result:unwrap()\` called on an \`Err\` value: {self._error}`)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- TODO: default values for types
|
||||||
function Result.unwrapOrDefault<T, E>(self: Result<T, E>): never
|
function Result.unwrapOrDefault<T, E>(self: Result<T, E>): never
|
||||||
return error("TODO: Result:unwrapOrDefault()")
|
return error("Unimplemented: `Result:unwrapOrDefault()`")
|
||||||
end
|
end
|
||||||
|
|
||||||
function Result.exceptErr<T, E>(self: Result<T, E>, msg: string): E | never
|
--[=[
|
||||||
|
@within Result
|
||||||
|
|
||||||
|
Returns the contained [Result:Err].
|
||||||
|
|
||||||
|
@error panic -- Panics if the value is an [Result:Ok], with a panic message including the passed message, and the content of the [Resul:Ok].
|
||||||
|
|
||||||
|
```lua
|
||||||
|
local x: Result<number, string> = Ok(10)
|
||||||
|
x:expectErr("Testing expect") -- panics with `Testing expect: 10`
|
||||||
|
```
|
||||||
|
|
||||||
|
@param self Result<T, E>
|
||||||
|
@param msg string
|
||||||
|
@return E | never
|
||||||
|
]=]
|
||||||
|
function Result.expectErr<T, E>(self: Result<T, E>, msg: string): E | never
|
||||||
if self:isErr() then
|
if self:isErr() then
|
||||||
return self._error
|
return self._error
|
||||||
end
|
end
|
||||||
|
@ -155,6 +568,25 @@ function Result.exceptErr<T, E>(self: Result<T, E>, msg: string): E | never
|
||||||
return error(`panic: {msg}; {self._error}`)
|
return error(`panic: {msg}; {self._error}`)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
--[=[
|
||||||
|
@within Result
|
||||||
|
|
||||||
|
Returns the contained [Result:Err].
|
||||||
|
|
||||||
|
@error panic -- Panics if the value is an [Result:Ok], with a panic message provided by the [Result:Ok]’s value.
|
||||||
|
|
||||||
|
```lua
|
||||||
|
local x: Result<number, string> = Ok(2)
|
||||||
|
x:unwrapErr() -- panics with `2`
|
||||||
|
```
|
||||||
|
```lua
|
||||||
|
x = Err("oh no")
|
||||||
|
assert(x:unwrapErr() == "oh no")
|
||||||
|
```
|
||||||
|
|
||||||
|
@param self Result<T, E>
|
||||||
|
@return E | never
|
||||||
|
]=]
|
||||||
function Result.unwrapErr<T, E>(self: Result<T, E>): E | never
|
function Result.unwrapErr<T, E>(self: Result<T, E>): E | never
|
||||||
if self:isErr() then
|
if self:isErr() then
|
||||||
return self._error
|
return self._error
|
||||||
|
@ -164,9 +596,41 @@ function Result.unwrapErr<T, E>(self: Result<T, E>): E | never
|
||||||
end
|
end
|
||||||
|
|
||||||
-- TODO: How the fuck do I implement this?
|
-- TODO: How the fuck do I implement this?
|
||||||
function Result.intoOk<T, E>(self: Result<T, E>) end
|
function Result.intoOk<T, E>(self: Result<T, E>): never
|
||||||
function Result.intoErr<T, E>(self: Result<T, E>) end
|
return error("Unimplemented: `Result:intoOk()`")
|
||||||
|
end
|
||||||
|
function Result.intoErr<T, E>(self: Result<T, E>): never
|
||||||
|
return error("Unimplemented: `Result:intoErr()`")
|
||||||
|
end
|
||||||
|
|
||||||
|
--[=[
|
||||||
|
@within Result
|
||||||
|
|
||||||
|
Returns res if the result is [Result:Ok], otherwise returns the
|
||||||
|
[Result:Err] value of self.
|
||||||
|
|
||||||
|
```lua
|
||||||
|
local x: Result<number, string> = Ok(2)
|
||||||
|
local y: Result<string, string> = Err("late error")
|
||||||
|
assert(x:and_(y) == Err("late error"))
|
||||||
|
|
||||||
|
local x: Result<number, string> = Err("early error")
|
||||||
|
local y: Result<string, string> = Ok("foo")
|
||||||
|
assert(x:and_(y) == Err("early error"))
|
||||||
|
|
||||||
|
local x: Result<number, string> = Err("not a 2")
|
||||||
|
local y: Result<string, string> = Err("late error")
|
||||||
|
assert(x:and_(y) == Err("not a 2"))
|
||||||
|
|
||||||
|
local x: Result<number, string> = Ok(2)
|
||||||
|
local y: Result<string, string> = Ok("different result type")
|
||||||
|
assert(x:and_(y) == Ok("different result type"))
|
||||||
|
```
|
||||||
|
|
||||||
|
@param self Result<T, E>
|
||||||
|
@param res Result<U, E>
|
||||||
|
@return Result<U, E>
|
||||||
|
]=]
|
||||||
function Result.and_<T, E, U>(self: Result<T, E>, res: Result<U, E>): Result<U, E>
|
function Result.and_<T, E, U>(self: Result<T, E>, res: Result<U, E>): Result<U, E>
|
||||||
if self:isOk() then
|
if self:isOk() then
|
||||||
return res
|
return res
|
||||||
|
@ -175,6 +639,36 @@ function Result.and_<T, E, U>(self: Result<T, E>, res: Result<U, E>): Result<U,
|
||||||
return Err(self._error)
|
return Err(self._error)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
--[=[
|
||||||
|
@within Result
|
||||||
|
|
||||||
|
Calls `op` if the result is [Result:Ok], otherwise returns the
|
||||||
|
[Result:Err] value of `self`.
|
||||||
|
|
||||||
|
This function can be used for control flow based on [Result]
|
||||||
|
values.
|
||||||
|
|
||||||
|
```lua
|
||||||
|
function sqThenToString(x): Result<string, string>
|
||||||
|
if typeof(sq) ~= "number" then
|
||||||
|
return Err("not a number: '" .. x .. "'")
|
||||||
|
end
|
||||||
|
|
||||||
|
return Ok(x ^ 2):map(function(sq)
|
||||||
|
return tostring(sq)
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
assert(Ok(2):andThen(sqThenToString) == Ok("4"))
|
||||||
|
assert(Err("string"):andThen(sqThenToString) == Err("not a number: 'string'"))
|
||||||
|
```
|
||||||
|
|
||||||
|
Often used to chain fallible operations that may return [Result:Err].
|
||||||
|
|
||||||
|
@param self Result<T, E>
|
||||||
|
@param op (...any) -> any
|
||||||
|
@return Result<U, E>
|
||||||
|
]=]
|
||||||
function Result.andThen<T, E, U>(self: Result<T, E>, op: (...any) -> any): Result<U, E>
|
function Result.andThen<T, E, U>(self: Result<T, E>, op: (...any) -> any): Result<U, E>
|
||||||
if self:isOk() then
|
if self:isOk() then
|
||||||
return op(self._value)
|
return op(self._value)
|
||||||
|
@ -183,6 +677,37 @@ function Result.andThen<T, E, U>(self: Result<T, E>, op: (...any) -> any): Resul
|
||||||
return Err(self._error) :: Result<U, E>
|
return Err(self._error) :: Result<U, E>
|
||||||
end
|
end
|
||||||
|
|
||||||
|
--[=[
|
||||||
|
@within Result
|
||||||
|
|
||||||
|
Calls `op` if the result is [Result:Ok], otherwise returns the
|
||||||
|
[Result:Err] value of `self`.
|
||||||
|
|
||||||
|
This function can be used for control flow based on [Result]
|
||||||
|
values.
|
||||||
|
|
||||||
|
```lua
|
||||||
|
local x: Result<number, string> = Ok(2)
|
||||||
|
local y: Result<number, string> = Err("late error")
|
||||||
|
assert(x:or_(y) == Ok(2))
|
||||||
|
|
||||||
|
local x: Result<number, string> = Err("early error")
|
||||||
|
local y: Result<number, string> = Ok(2)
|
||||||
|
assert(x:or_(y) == Ok(2))
|
||||||
|
|
||||||
|
local x: Result<number, string> = Err("not a 2")
|
||||||
|
local y: Result<number, string> = Err("late error")
|
||||||
|
assert(x:or_(y) == Err("late error"))
|
||||||
|
|
||||||
|
local x: Result<number, string> = Ok(2)
|
||||||
|
local y: Result<number, string> = Ok(100)
|
||||||
|
assert(x:or_(y) == Ok(2))
|
||||||
|
```
|
||||||
|
|
||||||
|
@param self Result<T, E>
|
||||||
|
@param res Result<T, F>
|
||||||
|
@return Result<T, F> | never
|
||||||
|
]=]
|
||||||
function Result.or_<T, E, F>(self: Result<T, E>, res: Result<T, F>): Result<T, F> | never
|
function Result.or_<T, E, F>(self: Result<T, E>, res: Result<T, F>): Result<T, F> | never
|
||||||
if self:isErr() then
|
if self:isErr() then
|
||||||
return res
|
return res
|
||||||
|
@ -195,6 +720,34 @@ function Result.or_<T, E, F>(self: Result<T, E>, res: Result<T, F>): Result<T, F
|
||||||
return error("called `Result:or()` with an invalid value")
|
return error("called `Result:or()` with an invalid value")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
--[=[
|
||||||
|
@within Result
|
||||||
|
|
||||||
|
Calls `op` if the result is [Result:Ok], otherwise returns the
|
||||||
|
[Result:Err] value of `self`.
|
||||||
|
|
||||||
|
This function can be used for control flow based on [Result]
|
||||||
|
values.
|
||||||
|
|
||||||
|
```lua
|
||||||
|
function sq(x: number): Result<number, number>
|
||||||
|
return Ok(x * x)
|
||||||
|
end
|
||||||
|
|
||||||
|
function err(x: number): Result<number, number>
|
||||||
|
return Err(x)
|
||||||
|
end
|
||||||
|
|
||||||
|
assert(Ok(2):orElse(sq):orElse(sq) == Ok(2))
|
||||||
|
assert(Ok(2):orElse(err):orElse(sq) == Ok(2))
|
||||||
|
assert(Err(3):orElse(sq):orElse(err) == Ok(9))
|
||||||
|
assert(Err(3):orElse(err):orElse(err) == Err(3))
|
||||||
|
```
|
||||||
|
|
||||||
|
@param self Result<T, E>
|
||||||
|
@param op (x: E) -> Result<T, U>
|
||||||
|
@return Result<T, U>
|
||||||
|
]=]
|
||||||
function Result.orElse<T, E, U>(self: Result<T, E>, op: (x: E) -> Result<T, U>): Result<T, U>
|
function Result.orElse<T, E, U>(self: Result<T, E>, op: (x: E) -> Result<T, U>): Result<T, U>
|
||||||
if self:isErr() then
|
if self:isErr() then
|
||||||
return op(self._error)
|
return op(self._error)
|
||||||
|
@ -203,6 +756,28 @@ function Result.orElse<T, E, U>(self: Result<T, E>, op: (x: E) -> Result<T, U>):
|
||||||
return Ok(self._value)
|
return Ok(self._value)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
--[=[
|
||||||
|
@within Result
|
||||||
|
|
||||||
|
Returns the contained [Result:Ok] value or a provided default.
|
||||||
|
|
||||||
|
Arguments passed to [Result:unwrapOr] are eagerly evaluated; if you are passing the
|
||||||
|
result of a function call, it is recommended to use [Result:unwrapOrElse], which is
|
||||||
|
lazily evaluated.
|
||||||
|
|
||||||
|
```lua
|
||||||
|
local default = 2
|
||||||
|
local x: Result<number, string> = Ok(9)
|
||||||
|
assert(x:unwrapOr(default), 9)
|
||||||
|
|
||||||
|
x = Err("error")
|
||||||
|
assert(x:unwrapOr(default), default)
|
||||||
|
```
|
||||||
|
|
||||||
|
@param self Result<T, E>
|
||||||
|
@param default T
|
||||||
|
@return T
|
||||||
|
]=]
|
||||||
function Result.unwrapOr<T, E>(self: Result<T, E>, default: T): T
|
function Result.unwrapOr<T, E>(self: Result<T, E>, default: T): T
|
||||||
if self:isOk() then
|
if self:isOk() then
|
||||||
return self._value
|
return self._value
|
||||||
|
@ -211,7 +786,25 @@ function Result.unwrapOr<T, E>(self: Result<T, E>, default: T): T
|
||||||
return default
|
return default
|
||||||
end
|
end
|
||||||
|
|
||||||
function Result.unwrapOrElse<T, E>(self: Result<T, E>, op: (x: E) -> T)
|
--[=[
|
||||||
|
@within Result
|
||||||
|
|
||||||
|
Returns the contained [Result:Ok] value or computes it from a closure.
|
||||||
|
|
||||||
|
```lua
|
||||||
|
function count(x: string): number
|
||||||
|
return #x
|
||||||
|
end
|
||||||
|
|
||||||
|
assert(Ok(2):unwrapOrElse(count) == 2))
|
||||||
|
assert(Err("foo"):unwrapOrElse(count) == 3))
|
||||||
|
```
|
||||||
|
|
||||||
|
@param self Result<T, E>
|
||||||
|
@param op (x: E) -> T
|
||||||
|
@return T
|
||||||
|
]=]
|
||||||
|
function Result.unwrapOrElse<T, E>(self: Result<T, E>, op: (x: E) -> T): T
|
||||||
if self:isOk() then
|
if self:isOk() then
|
||||||
return self._value
|
return self._value
|
||||||
end
|
end
|
||||||
|
@ -219,6 +812,26 @@ function Result.unwrapOrElse<T, E>(self: Result<T, E>, op: (x: E) -> T)
|
||||||
return op(self._error)
|
return op(self._error)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
--[=[
|
||||||
|
@within Result
|
||||||
|
|
||||||
|
Returns true if the [Result:Ok] value is `val`.
|
||||||
|
|
||||||
|
```lua
|
||||||
|
local x: Result<number, string> = Ok(2)
|
||||||
|
assert(x:contains(2))
|
||||||
|
|
||||||
|
x = Ok(3)
|
||||||
|
assert(not x:contains(2))
|
||||||
|
|
||||||
|
x = Err(2)
|
||||||
|
assert(not x:contains(2))
|
||||||
|
```
|
||||||
|
|
||||||
|
@param self Result<T, E>
|
||||||
|
@param val T
|
||||||
|
@return boolean
|
||||||
|
]=]
|
||||||
function Result.contains<T, E>(self: Result<T, E>, val: T): boolean
|
function Result.contains<T, E>(self: Result<T, E>, val: T): boolean
|
||||||
if self:isOk() then
|
if self:isOk() then
|
||||||
return self._value == val
|
return self._value == val
|
||||||
|
@ -227,6 +840,26 @@ function Result.contains<T, E>(self: Result<T, E>, val: T): boolean
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
|
|
||||||
|
--[=[
|
||||||
|
@within Result
|
||||||
|
|
||||||
|
Returns true if the [Result:Err] value is `err`.
|
||||||
|
|
||||||
|
```lua
|
||||||
|
local x: Result<number, string> = Err(2)
|
||||||
|
assert(x:containsErr(2))
|
||||||
|
|
||||||
|
x = Err(3)
|
||||||
|
assert(not x:containsErr(2))
|
||||||
|
|
||||||
|
x = Ok(2)
|
||||||
|
assert(not x:containsErr(2))
|
||||||
|
```
|
||||||
|
|
||||||
|
@param self Result<T, E>
|
||||||
|
@param err E
|
||||||
|
@return boolean
|
||||||
|
]=]
|
||||||
function Result.containsErr<T, E>(self: Result<T, E>, err: E): boolean
|
function Result.containsErr<T, E>(self: Result<T, E>, err: E): boolean
|
||||||
if self:isErr() then
|
if self:isErr() then
|
||||||
return self._error == err
|
return self._error == err
|
||||||
|
@ -235,6 +868,27 @@ function Result.containsErr<T, E>(self: Result<T, E>, err: E): boolean
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
|
|
||||||
|
--[=[
|
||||||
|
@within Result
|
||||||
|
|
||||||
|
Returns a formatted representation of the result, often
|
||||||
|
used for printing to stdout.
|
||||||
|
|
||||||
|
```lua
|
||||||
|
local x: Result<number, string> = Ok(123)
|
||||||
|
local y: Result<number, string> = Err("error")
|
||||||
|
|
||||||
|
print(x:display()) -- prints `Result::Ok(123)`
|
||||||
|
print(y:display()) -- prints `Result::Err("error")`
|
||||||
|
```
|
||||||
|
|
||||||
|
@param self Result<T, E>
|
||||||
|
@return string
|
||||||
|
]=]
|
||||||
|
function Result.display<T, E>(self: Result<T, E>): string
|
||||||
|
return tostring(self)
|
||||||
|
end
|
||||||
|
|
||||||
return {
|
return {
|
||||||
Ok = Ok,
|
Ok = Ok,
|
||||||
Err = Err,
|
Err = Err,
|
||||||
|
|
38
lib/util.luau
Normal file
38
lib/util.luau
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
-- From https://gist.github.com/sapphyrus/fd9aeb871e3ce966cc4b0b969f62f539
|
||||||
|
local function tableEq(tbl1, tbl2)
|
||||||
|
if tbl1 == tbl2 then
|
||||||
|
return true
|
||||||
|
elseif type(tbl1) == "table" and type(tbl2) == "table" then
|
||||||
|
for key1, value1 in pairs(tbl1) do
|
||||||
|
local value2 = tbl2[key1]
|
||||||
|
|
||||||
|
if value2 == nil then
|
||||||
|
-- avoid the type call for missing keys in tbl2 by directly comparing with nil
|
||||||
|
return false
|
||||||
|
elseif value1 ~= value2 then
|
||||||
|
if type(value1) == "table" and type(value2) == "table" then
|
||||||
|
if not tableEq(value1, value2) then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
else
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- check for missing keys in tbl1
|
||||||
|
for key2, _ in pairs(tbl2) do
|
||||||
|
if tbl1[key2] == nil then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
return {
|
||||||
|
tableEq = tableEq,
|
||||||
|
}
|
Loading…
Reference in a new issue