local tableEq = require("util").tableEq --[=[ @class Result Error handling with the [Result] type. [Result]`` 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 -- 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 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 = Ok(10) local badResult: Result = 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 = goodResult:andThen(function(i) return Ok(i == 11) end) -- Use `or_else` to handle the error local badResult: Result = 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 = {} export type Result = typeof(Result) & { _value: T, _error: E, typeId: "Result", } --[=[ @within Result Contains the success value. @param val T @return Result ]=] function Ok(val: T): Result return Result.new(val, (nil :: unknown) :: E) end --[=[ @within Result Contains the error value. @param err E @return Result ]=] function Err(err: E): Result return Result.new((nil :: unknown) :: T, err) 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 ]=] function Result.new(val: T, err: E) return setmetatable( { _value = val, _error = err, typeId = "Result", } :: Result, { __index = Result, __tostring = function(self: Result) if self:isOk() then return `{self.typeId}::Ok({self._value})` end if self:isErr() then return `{self.typeId}::Err({self._error})` end return `{self.typeId}` end, __eq = function(self: Result, other: Result) if typeof(self._value) ~= "table" and typeof(self._error) ~= "table" and typeof(other._value) ~= "table" and typeof(other._error) ~= "table" then return self._value == other._value and self._error == other._error end return tableEq(self._value, other._value) and tableEq(self._error, other._error) end, -- TODO: Implement equality and arithmetic metamethods -- TODO: Implement __iter, once iterators traits exist } ) end --[=[ @within Result Returns `true` if the result is [Result:Ok]. ```lua local x: Result = Ok(3) assert(x:isOk()) x = Err("Some error message") assert(not x:isOk()) ``` @param self Result @return boolean ]=] function Result.isOk(self: Result): boolean if self._value == nil then return false end return true end --[=[ @within Result Returns `true` if the result is [Result:Ok] and the value inside it matches a predicate. ```lua local x: Result = 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 @param predicate (val: T?) -> boolean @return boolean ]=] function Result.isOkAnd(self: Result, predicate: (val: T?) -> boolean): boolean return self:isOk() and predicate(self._value) end --[=[ @within Result Returns `true` if the result is [Result:Err]. ```lua local x: Result = Ok(3) assert(not x:isErr()) x = Err("Some error message") assert(x:isErr()) ``` @param self Result @return boolean ]=] function Result.isErr(self: Result) return not self:isOk() end --[=[ @within Result Returns `true` if the result is [Result:Err] and the value inside it matches a predicate. ```lua local x: Result = 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 @param predicate (val: T?) -> boolean @return boolean ]=] function Result.isErrAnd(self: Result, predicate: (val: E) -> boolean): boolean if self:isErr() then return predicate(self._error) end return false end --[=[ @within Result Maps a [Result] to [Result] 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 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 @param op (val: T) -> U @return Result ]=] function Result.map(self: Result, op: (val: T) -> U): Result if self:isOk() then return Result.new(op(self._value), self._error) :: Result end return Err(self._error) 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 = Ok("foo") assert(x:mapOr("bar", string.upper) == "FOO") x = Err("foo") assert(x:mapOr("bar", string.upper) == "bar") ``` @param self Result @param default U @param op (val: T) -> U @return U ]=] function Result.mapOr(self: Result, default: U, op: (val: T) -> U): U if self:isOk() then return op(self._value) end return default end --[=[ @within Result Maps a [Result] 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 = 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 @param default (val: E) -> U @param op (val: T) -> U @return U ]=] function Result.mapOrElse(self: Result, default: (val: E) -> U, op: (val: T) -> U): U if self:isOk() then return op(self._value) end return default(self._error) end --[=[ @within Result Maps a [Result] to [Result] 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 = Ok(2) assert(x:mapErr(stringify) == Ok(2)) x = Err(13) assert(x:mapErr(stringify) == Err("error code: 13")) ``` @param self Result @param op (val: E) -> F @return Result ]=] function Result.mapErr(self: Result, op: (val: E) -> F): Result if self:isErr() then return Result.new(self._value, op(self._error)) end return Ok(self._value) end --[=[ @within Result Calls the provided closure with a reference to the contained value (if [Result:Ok]). ```lua function parseInt(x: string): Result 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 @param op (val: E) -> () @return Result ]=] function Result.inspect(self: Result, op: (val: T) -> ()): Result if self:isOk() then op(self._value) end return self end --[=[ @within Result Calls the provided closure with a reference to the contained value (if [Result:Err]). ```lua function parseInt(x: string): Result 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 @param op (val: E) -> () @return Result ]=] function Result.inspectErr(self: Result, op: (val: E) -> ()): Result if self:isErr() then op(self._error) end return self end -- TODO: Iterator traits 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 = 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(var: string): Result 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 @param msg string @return T | never ]=] function Result.expect(self: Result, msg: string): T | never if self:isOk() then return self._value end return error(`panic: {msg}; {self._error}`) 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 = Ok(2) assert(x:unwrap() == 2) x = Err("oh no") x:unwrap() -- panics with `oh no` ``` @param self Result ]=] function Result.unwrap(self: Result): T | never if self:isOk() then return self._value end return error(`panic: \`Result:unwrap()\` called on an \`Err\` value: {self._error}`) end -- TODO: default values for types function Result.unwrapOrDefault(self: Result): never return error("Unimplemented: `Result:unwrapOrDefault()`") end --[=[ @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 = Ok(10) x:expectErr("Testing expect") -- panics with `Testing expect: 10` ``` @param self Result @param msg string @return E | never ]=] function Result.expectErr(self: Result, msg: string): E | never if self:isErr() then return self._error end return error(`panic: {msg}; {self._error}`) 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 = Ok(2) x:unwrapErr() -- panics with `2` ``` ```lua x = Err("oh no") assert(x:unwrapErr() == "oh no") ``` @param self Result @return E | never ]=] function Result.unwrapErr(self: Result): E | never if self:isErr() then return self._error end return error(`panic: \`Result:unwrapErr()\` called on an \`Ok\` value: {self._value}`) end -- TODO: How the fuck do I implement this? function Result.intoOk(self: Result): never return error("Unimplemented: `Result:intoOk()`") end function Result.intoErr(self: Result): 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 = Ok(2) local y: Result = Err("late error") assert(x:and_(y) == Err("late error")) local x: Result = Err("early error") local y: Result = Ok("foo") assert(x:and_(y) == Err("early error")) local x: Result = Err("not a 2") local y: Result = Err("late error") assert(x:and_(y) == Err("not a 2")) local x: Result = Ok(2) local y: Result = Ok("different result type") assert(x:and_(y) == Ok("different result type")) ``` @param self Result @param res Result @return Result ]=] function Result.and_(self: Result, res: Result): Result if self:isOk() then return res end return Err(self._error) 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 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 @param op (...any) -> any @return Result ]=] function Result.andThen(self: Result, op: (...any) -> any): Result if self:isOk() then return op(self._value) end return Err(self._error) :: Result 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 = Ok(2) local y: Result = Err("late error") assert(x:or_(y) == Ok(2)) local x: Result = Err("early error") local y: Result = Ok(2) assert(x:or_(y) == Ok(2)) local x: Result = Err("not a 2") local y: Result = Err("late error") assert(x:or_(y) == Err("late error")) local x: Result = Ok(2) local y: Result = Ok(100) assert(x:or_(y) == Ok(2)) ``` @param self Result @param res Result @return Result | never ]=] function Result.or_(self: Result, res: Result): Result | never if self:isErr() then return res end if self:isOk() then return Ok(self._value) :: Result end return error("called `Result:or()` with an invalid value") 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 return Ok(x * x) end function err(x: number): Result 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 @param op (x: E) -> Result @return Result ]=] function Result.orElse(self: Result, op: (x: E) -> Result): Result if self:isErr() then return op(self._error) end return Ok(self._value) 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 = Ok(9) assert(x:unwrapOr(default), 9) x = Err("error") assert(x:unwrapOr(default), default) ``` @param self Result @param default T @return T ]=] function Result.unwrapOr(self: Result, default: T): T if self:isOk() then return self._value end return default end --[=[ @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 @param op (x: E) -> T @return T ]=] function Result.unwrapOrElse(self: Result, op: (x: E) -> T): T if self:isOk() then return self._value end return op(self._error) end --[=[ @within Result Returns true if the [Result:Ok] value is `val`. ```lua local x: Result = 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 @param val T @return boolean ]=] function Result.contains(self: Result, val: T): boolean if self:isOk() then return self._value == val end return false end --[=[ @within Result Returns true if the [Result:Err] value is `err`. ```lua local x: Result = 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 @param err E @return boolean ]=] function Result.containsErr(self: Result, err: E): boolean if self:isErr() then return self._error == err end return false end --[=[ @within Result Returns a formatted representation of the result, often used for printing to stdout. ```lua local x: Result = Ok(123) local y: Result = Err("error") print(x:display()) -- prints `Result::Ok(123)` print(y:display()) -- prints `Result::Err("error")` ``` @param self Result @return string ]=] function Result.display(self: Result): string return tostring(self) end return { Ok = Ok, Err = Err, Result = Result, }