diff --git a/rbx/conversion.luau b/rbx/conversion.luau deleted file mode 100644 index d4871b5..0000000 --- a/rbx/conversion.luau +++ /dev/null @@ -1,210 +0,0 @@ ---[[ - 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(script.Parent.option) -local Option = option.Option -export type Option = option.Option -local None = option.None -local Some = option.Some - -local result = require(script.Parent.result) -local Result = result.Result -export type Result = result.Result -local Ok = result.Ok -local Err = result.Err - ---[=[ - @within Result - - Converts from [Result]`` to [Option]``. - - Converts `self` into an [Option]``, and discarding the error, if any. - - ```lua - local x: Result = Ok(2) - assert(x:ok() == Some(2)) - - x = Err("Nothing here") - assert(x:ok() == None()) - ``` - - @param self Result - @return Option -]=] -function Result.ok(self: Result): Option - if self:isOk() then - if self._value == nil then - return None() - end - - return Some(self._value) - end - - return None() -end - ---[=[ - @within Result - - Converts from [Result]`` to [Option]``. - - Converts `self` into an [Option]``, and discarding the success value, if any. - - ```lua - local x: Result = Ok(2) - assert(x:ok() == Some(2)) - - x = Err("Nothing here") - assert(x:ok() == None()) - ``` - - @param self Result - @return Option -]=] -function Result.err(self: Result): Option - if self:isErr() then - return Option.new(self._error) :: Option - end - - return None() -end - ---[=[ - @within Result - - Transposes a [Result] of an [Option] into an [Option] of a [Result]. - - [Result:Ok]\([Option:None]\) will be mapped to [Option:None]. - [Result:Ok]\([Option:Some]`(_)`\) and [Result:Err]\(`_`\) will be mapped to - [Option:Some]\([Result:Ok]`(_)`\) and [Option:Some]\([Option:Err]`(_)`\). - - ```lua - type SomeErr = {} - - local x: Result, SomeErr> = Ok(Some(2)) - local y: Option> = Some(Ok(2)) - assert(x:transpose() == y) - ``` - - @param self Result, E> - @return Option> -]=] -function Result.transpose(self: Result, E>): Option> - 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 - ---[=[ - @within Option - - Transforms the [Option]`` into a [Result]``, mapping [Option:Some]`(v)` - to [Result:Ok]`(v)` and [Option:None] to [Result:Err]`(err)`. - - Arguments passed to [Option:okOr] are eagerly evaluated; if you are passing the result - of a function call, it is recommended to use [Option:okOrElse], which is lazily evaluated. - - ```lua - local x: Option = Some("foo") - assert(x:okOr(0) == Ok("foo")) - - x = None() - assert(x:okOr(0) == Err(0)) - ``` - - @param self Option - @param err E - @return Result -]=] -function Option.okOr(self: Option, err: E): Result - if self:isSome() then - return Ok(self._optValue) - end - - return Err(err) -end - ---[=[ - @within Option - - Transforms the [Option]`` into a [Result]``, mapping [Option:Some]`(v)` to - [Result:Ok]`(v)` and [Option:None] to [Result:Err]`(err())`. - - ```lua - local x: Option = Some("foo") - assert(x:okOrElse(function() return 0 end) == Ok("foo")) - - x = None() - assert(x:okOrElse(function() return 0 end) == Err(0)) - ``` - - @param self Option - @param err () -> E - @return Result -]=] -function Option.okOrElse(self: Option, err: () -> E): Result - if self:isSome() then - return Ok(self._optValue :: T) - end - - return Err(err()) -end - ---[=[ - @within Option - - Transposes a [Option] of an [Result] into an [Result] of a [Option]. - - [Option:None] will be mapped to [Result:Ok]\([Option:None]\). - [Option:Some]\([Result:Ok]`(_)`\) and [Option:Some]\([Result:Err]\(`_`\)\) will - be mapped to [Result:Ok]\([Option:Some]`(_)`\) and [Result:Err]`(_)`. - - ```lua - type SomeErr = {} - - local x: Result, SomeErr> = Ok(Some(5)) - local y: Option> = Some(Ok(5)) - assert(x == y:transpose()) - ``` - - @param self Option> - @return Result, E> -]=] -function Option.transpose(self: Option>): Result, 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, -} diff --git a/rbx/future.luau b/rbx/future.luau deleted file mode 100644 index 90e32f6..0000000 --- a/rbx/future.luau +++ /dev/null @@ -1,218 +0,0 @@ -local isRoblox = _VERSION == "Luau" -local isLune = _VERSION:find("Lune") == 1 -local deps = if isLune then require(script.Parent.Parent.deps)else require(script.Parent.deps) -local requires = deps(isRoblox, isLune) -local util = require(script.Parent.util) - -local Signal = util.Signal -type Signal = util.Signal - -local task = requires.task - -local result = require(script.Parent.result) -type Result = result.Result -local Ok = result.Ok -local Err = result.Err - -local option = require(script.Parent.option) -type Option = option.Option -local None = option.None -local Some = option.Some - ---[=[ - @class Future - - A future represents an asynchronous computation. - - A future is a value that might not have finished computing yet. This kind of “asynchronous value” - makes it possible for a thread to continue doing useful work while it waits for the value to - become available. - - ### The [Future:poll] Method - The core method of future, poll, attempts to resolve the future into a final value. This method does - not block if the value is not ready. Instead, the current task is executed in the background, and - its progress is reported when polled. When using a future, you generally won’t call poll directly, - but instead [Future:await] the value. - - ```lua - local net = require("@lune/net") - - local fut: Future> = Future.try(function(url) - local resp = net.request({ - url = url, - method = "GET", - }) - - assert(resp.ok) - - return resp.body - end, { "https://jsonplaceholder.typicode.com/posts/1" }) - - local resp: Result = fut:await() - print(net.jsonDecode(resp:unwrap())) - ``` -]=] -local Future = {} - ---[=[ - @private - @type Status "initialized" | "pending" | "cancelled" | "ready" - @within Future - - Represents the status of a [Future]. -]=] -export type Status = "initialized" | "pending" | "cancelled" | "ready" - ---[=[ - @private - @interface Future - @within Future - - Represents the internal state of a [Future]. - - @field _thread thread -- The background coroutine spawned for execution - @field _ret T -- The value returned once execution has halted - @field _spawnEvt Signal<()> -- Event for internal communication among threads pre execution - @field _retEvt Signal, Status> -- Event for internal communication among threads post execution - @field _status Status -- The status of the Future - -]=] -export type Future = typeof(Future) & { - _ret: T, - _thread: thread, - _spawnEvt: Signal<()>, - _retEvt: Signal, Status>, - _status: Status -} - -local function _constructor(fn: (Signal<()>, Signal) -> ()) - return setmetatable( - { - _thread = coroutine.create(fn), - - _spawnEvt = Signal.new(), - -- This is a hack to make luau realize that this object and - -- Future are related - _retEvt = Signal.new() :: Signal, Status>, - - _status = "initialized", - } :: Future, - { - __index = Future, - } - ) -end - ---[=[ - @within Future - - Constructs a [Future] from a function to be run asynchronously. - - :::caution - If a the provided function has the possibility to throw an error, instead of any - other rusty-luau types like [Result] or [Option], use [Future:try] instead. - ::: - - @param fn -- The function to be executed asynchronously - @param args -- The arguments table to be passed to to the function - @return Future -- The constructed future -]=] -function Future.new(fn: (...any) -> T, args: { any }) - return _constructor(function(spawnEvt: Signal<()>, retEvt: Signal) - spawnEvt:Fire() - - local ret = fn(table.unpack(args)) - retEvt:Fire(ret, "ready") - end) -end - ---[=[ - @within Future - - Constructs a fallible [Future] from a function to be run asynchronously. - - @param fn -- The fallible function to be executed asynchronously - @param args -- The arguments table to be passed to to the function - @return Future> -- The constructed future -]=] -function Future.try(fn: (...any) -> T, args: { any }) - return _constructor(function(spawnEvt: Signal<()>, retEvt: Signal, Status>) - spawnEvt:Fire() - - local ok, ret = pcall(fn, table.unpack(args)) - local res: Result = if ok then Ok(ret) else Err(ret) - retEvt:Fire(res, "ready") - end) -end - ---[=[ - @within Future - - Polls a [Future] to completion. - - @param self Future - @return (Status, Option) -- Returns the [Status] and an optional return if completed -]=] -function Future.poll(self: Future): (Status, Option) - if self._status == "initialized" then - self._retEvt:Connect(function(firedRet, status: Status) - self._status = status - self._ret = firedRet - - -- Cleanup - coroutine.yield(self._thread) - coroutine.close(self._thread) - - self._spawnEvt:DisconnectAll() - self._retEvt:DisconnectAll() - end) - - self._spawnEvt:Connect(function() - self._status = "pending" - end) - - coroutine.resume(self._thread, self._spawnEvt, self._retEvt) - end - - if self._status == "pending" then - -- Just wait a bit more for the signal to fire - task.wait(0.01) - end - - local retOpt = if self._ret == nil then None() else Some(self._ret) - return self._status, retOpt -end - ---[=[ - @within Future - - Cancels a [Future]. - - @param self Future -]=] -function Future.cancel(self: Future) - self._retEvt:Fire(nil :: any, "cancelled") - self._status = "cancelled" -end - ---[=[ - @within Future - - Suspend execution until the result of a [Future] is ready. - This method continuosly polls a [Future] until it reaches completion. - - @param self Future - @return T -- The value returned by the function on completion -]=] -function Future.await(self: Future): Option - while true do - local status: Status, ret: Option = self:poll() - - if status == "ready" then - -- Safe to unwrap, we know it must not be nil - return ret - end - end -end - -return Future diff --git a/rbx/option.luau b/rbx/option.luau deleted file mode 100644 index ba71769..0000000 --- a/rbx/option.luau +++ /dev/null @@ -1,918 +0,0 @@ -local tableEq = require(script.Parent.util).tableEq - ---[=[ - @class Option - - Type [Option] represents an optional value: every [Option] is either [Option:Some] - and contains a value, or [Option:None], and does not. Common uses of an [Option] - may involve: - - * Initial values - * Return values for functions that are not defined over their entire input range (partial functions) - * Return value for otherwise reporting simple errors, where None is returned on error - * Optional object fields - * Values that can be loaned or “taken” - * Optional function arguments - - ```lua - function divide(numerator: number, denominator: number): Option - if denominator == 0 then - None() - else - return Some(numerator / denominator) - end - end - ``` -]=] -local Option = {} - -export type Option = typeof(Option) & { - _optValue: T?, - typeId: "Option" -} - ---[=[ - @within Option - - No value. -]=] -function None(): Option - return Option.new(nil) :: Option -end - ---[=[ - @within Option - - Some value of type `T`. -]=] -function Some(val: T): Option - return Option.new(val) :: Option -end - ---[=[ - @within Option - - Converts a potentially `nil` value into an [Option]. - - @param val T? -- The value to convert into an [Option] - @return Option -]=] -function Option.new(val: T?) - return setmetatable( - { - _optValue = val, - typeId = "Option", - } :: Option, - { - __index = Option, - __tostring = function(self: Option) - if self._optValue == nil then - return `{self.typeId}::None` - else - return `{self.typeId}::Some({self._optValue})` - end - end, - - __eq = function(self: Option, other: Option): boolean - if typeof(self._optValue) == "table" and typeof(other._optValue) == "table" then - return tableEq(self._optValue, other._optValue) - else - return self._optValue == other._optValue - end - end, - __lt = function(self: Option, other: Option): boolean - if self:isSome() and other:isSome() then - return (self._optValue :: any) < (other._optValue :: any) - end - - return false - end, - __le = function(self: Option, other: Option): boolean - if self:isSome() and other:isSome() then - return (self._optValue :: any) <= (other._optValue :: any) - end - - return false - end, - } - - -- TODO: Implement __iter, once iterators traits exist - ) -end - ---[=[ - @within Option - - Returns `true` is the [Option] is an [Option:Some] value. - - @param self Option - @return boolean -]=] -function Option.isSome(self: Option): boolean - return self._optValue ~= nil -end - ---[=[ - @within Option - - Returns `true` is the [Option] is an [Option:Some] value and the value inside of it - matches a predicate. - - @param self Option - @param op (val: T) -> boolean -- The predicate function - @return boolean -]=] -function Option.isSomeAnd(self: Option, 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 - ---[=[ - @within Option - - Returns `true` is the [Option] is an [Option:None] value. - - @param self Option - @return boolean -]=] -function Option.isNone(self: Option): boolean - return not self:isSome() -end - ---[=[ - @within Option - - Returns the [Option:Some]. - - @error panic -- Panics if there is an [Option:None] with a custom panic message provided by `msg`. - - ```lua - local x: Option = Some("value") - assert(x:expect("fruits are healthy") == "value") - ``` - - ```lua - local x: Option = None() - x:expect("fruits are healthy") -- panics with `fruits are healthy` - ``` - - @param self Option - @param msg string -- The panic message - @return boolean -]=] -function Option.expect(self: Option, 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 - ---[=[ - @within Option - - Returns the [Option:Some]. - - Because this function may panic, its use is generally discouraged. Instead, prefer to use - [Option:unwrapOr], [Option:unwrapOrElse], or [Option:unwrapOrDefault]. - - @error panic -- Panics if the self value is [Option:None]. - @param self Option - @return T | never -]=] -function Option.unwrap(self: Option): T | never - if self:isSome() then - return self._optValue :: T - end - - return error("called `Option:unwrap()` on a `None` value") -end - ---[=[ - @within Option - - Returns the contained [Option:Some] value or a provided default. - - Arguments passed to unwrap_or are eagerly evaluated; if you are passing the result of - a function call, it is recommended to use [Option:unwrapOrElse], which is lazily - evaluated. - - ```lua - assert(Some("car"):unwrapOr("bike") == "car") - assert(None():unwrapOr("bike") == "bike") - ``` - - @param self Option - @param default T -- The default value - @return T -]=] -function Option.unwrapOr(self: Option, default: T): T - if self:isSome() then - return self._optValue :: T - end - - return default -end - ---[=[ - @within Option - - Returns the contained [Option:Some] value or computes it from a function. - - ```lua - local k = 10 - assert(Some(4).unwrapOrElse(function() - return 2 * k - end) == 4) - assert(None().unwrapOrElse(function() - return 2 * k - end) == 20) - ``` - - @param self Option - @param default () -> T -- The function which computes the default value - @return T -]=] -function Option.unwrapOrElse(self: Option, default: () -> T): T - if self:isSome() then - return self._optValue :: T - end - - return default() -end - ---[=[ - @within Option - - Maps an [Option] to [Option] by applying a function to a contained value - (if [Option:Some]) or returns [Option:None](if [Option:None]). - - ```lua - local maybeSomeString: Option = Some("Hello, World!") - - local maybeSomeLen: Option = maybeSomeString:map(function(s) - return #s - end) - assert(maybeSomeLen == 13) - - local x: Option = None() - assert(x:map(function(s) - return #s - end) == None()) - ``` - - @param self Option - @param op (x: T) -> U? -- The function to apply - @return Option -]=] -function Option.map(self: Option, op: (x: T) -> U?): Option - if self:isSome() then - local val = op(self._optValue :: T) - - if val == nil then - return None() - end - - return Some(val) - end - - return None() -end - ---[=[ - @within Option - - Calls the provided closure with the contained value (if [Option:Some]). - - ```lua - local v = { 1, 2, 3, 4, 5 } - - -- prints "got: 4" - local x: Option = Option.new(v[4]):inspect(function(x) - print("got: " .. x) - end) - - -- prints nothing - local x: Option = Option.new(v[5]):inspect(function(x) - print("got: " .. x) - end) - ``` - - @param self Option - @param op (x: T) -> () -- The function to call - @return Option -]=] -function Option.inspect(self: Option, op: (x: T) -> ()): Option - if self:isSome() then - op(self._optValue :: T) - end - - return self -end - ---[=[ - @within Option - - Returns the provided default result (if none), or applies a function to - the contained value (if any). - - Arguments passed to [Option:mapOr] are eagerly evaluated; if you are passing the - result of a function call, it is recommended to use [Option:mapOrElse], - which is lazily evaluated. - - ```lua - local x: Option = Some("foo") - assert(x.mapOr(42, function(v) return #v end) == 3) - - local x: Option = None() - assert(x.mapOr(42, function(v) return #v end) == 42) - ``` - @param self Option - @param default U -- The default value - @param op (val: T) -> U -- The function to apply - @return Option -]=] -function Option.mapOr(self: Option, default: U, op: (val: T) -> U) - if self:isSome() then - return op(self._optValue :: T) - end - - return default -end - ---[=[ - @within Option - - Computes a default function result (if none), or applies a different function - to the contained value (if any). - - ```lua - local k = 21; - - local x: Option = Some("foo") - assert( - x:mapOrElse( - function() return 2 * k end, - function(v) return #v end - ) == 3 - ) - - local x: Option = None() - assert( - x:mapOrElse( - function() return 2 * k end, - function(v) return #v end - ) == 42 - ) - ``` - - @param self Option - @param default () -> U -- The function to compute the default value - @param op (val: T) -> U -- The function to apply - @return Option -]=] -function Option.mapOrElse(self: Option, 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(): never - return error("Unimplemented: `Option:iter()`") -end - ---[=[ - @within Option - - Returns [Option:None] if the option is [Option:None], otherwise returns `optb`. - - Arguments passed to and are eagerly evaluated; if you are passing the result of a - function call, it is recommended to use [Option:andThen], which is lazily evaluated. - - ```lua - local x: Option = Some(2) - local y: Option = None() - assert(x:and_(y) == None()) - - local x: Option = None() - local y: Option = Some("foo") - assert(x:and_(y) == None()) - - local x: Option = Some(2) - local y: Option = Some("foo") - assert(x:and_(y) == Some("foo")) - - local x: Option = None() - local y: Option = None() - assert(x:and_(y), None()) - ``` - - @param self Option - @param optb Option -- The other option - @return Option -]=] -function Option.and_(self: Option, optb: Option): Option - if self:isSome() then - return optb - end - - return None() -end - ---[=[ - @within Option - - Returns [Option:None] if the option is [Option:None], otherwise calls `op` with the wrapped - value and returns the result. - - Some languages call this operation flatmap. - - ```lua - function sqThenToString(x: number): Option - return Option.new(x ^ 2):map(function(sq) - return tostring(sq) - end) - end - - assert(Some(2):andThen(sqThenToString) == Some(tostring(4))) - assert(None():andThen(sqThenToString) == None()) - ``` - - Often used to chain fallible operations that may return [Option:None]. - - ```lua - local arr2d = { { "A0", "A1" }, { "B0", "B1" } } - - local item01: Option = Option.new(arr2d[1]):andThen(function(row) - return row[2] - end) - assert(item01 == Some("A1")) - - local item20: Option = Option.new(arr2d[3]):andThen(function(row) - return row[0] - end) - assert(item20 == None()) - ``` - - @param self Option - @param op (val: T) -> Option -- The function to call - @return Option -]=] -function Option.andThen(self: Option, op: (val: T) -> Option): Option - if self:isSome() then - return op(self._optValue :: T) - end - - return None() -end - ---[=[ - @within Option - - Returns [Option:None] if the option is [Option:None], otherwise calls `predicate` with - the wrapped value and returns: - - * [Option:Some](t) if predicate returns `true` (where `t` is the wrapped value), and - * [Option:None] if predicate returns `false`. - - ```lua - function isEven(n: number): boolean - return n % 2 == 0 - end - - assert(None():filter(isEven) == None()) - assert(Some(3):filter(isEven) == None()) - assert(Some(4):filter(isEven) == Some(4)) - ``` - - @param self Option - @param predicate (val: T) -> boolean -- The predicate function which must match an element - @return Option -]=] -function Option.filter(self: Option, predicate: (val: T) -> boolean): Option - if self:isSome() then - if predicate(self._optValue :: T) then - return self - end - end - - return None() -end - ---[=[ - @within Option - - Returns the option if it contains a value, otherwise returns `optb`. - - Arguments passed to [Option:or_] are eagerly evaluated; if you are passing the result of - a function call, it is recommended to use [Option:orElse], which is lazily - evaluated. - - ```lua - local x: Option = Some(2) - local y: Option = None() - assert(x:or_(y) == Some(2)) - - local x: Option = None() - local y: Option = Some(100) - assert(x:or_(y) == Some(100)) - - local x: Option = Some(2) - local y: Option = Some(100) - assert(x:or_(y) == Some(2)) - - local x: Option = None() - local y: Option = None() - assert(x:or_(y), None()) - ``` - - @param self Option - @param optb Option -- The other option - @return Option -]=] -function Option.or_(self: Option, optb: Option): Option - if self:isSome() then - return self - end - - return optb -end - ---[=[ - @within Option - - Returns the option if it contains a value, otherwise calls `op` and returns the result. - - ```lua - function nobody(): Option - return None() - end - function vikings(): Option - return Some("vikings") - end - - assert(Some("barbarians"):orElse(vikings) == Some("barbarians")) - assert(None():orElse(vikings) == Some("vikings")) - assert(None():orElse(nobody) == None()) - ``` - - @param self Option - @param op () -> Option -- The function to call - @return Option -]=] -function Option.orElse(self: Option, op: () -> Option): Option - if self:isSome() then - return self - end - - return op() -end - ---[=[ - @within Option - - Returns [Option:Some] if exactly one of `self`, `optb` is [Option:Some], - otherwise returns [Option:None]. - - ```lua - local x: Option = Some(2) - local y: Option = None() - assert(x:xor(y) == Some(2)) - - local x: Option = None() - local y: Option = Some(2) - assert(x:xor(y) == Some(2)) - - local x: Option = Some(2) - local y: Option = Some(2) - assert(x:xor(y) == None()) - - local x: Option = None() - local y: Option = None() - assert(x:xor(y) == None()) - ``` - - @param self Option - @param optb Option -- The other option - @return Option -]=] -function Option.xor(self: Option, optb: Option): Option - 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 - ---[=[ - @within Option - - Inserts value into the option, then returns it. - - If the option already contains a value, the old value is dropped. - - See also [Option:getOrInsert], which doesn’t update the value if the - option already contains [Option:Some]. - - ```lua - local opt: Option = None() - local val: number = opt:insert(1) - assert(val == 1) - assert(opt:unwrap() == 1) - - local val: number = opt:insert(2) - assert(val == 2) - ``` - - @param self Option - @param val T -- The value to insert - @return T -]=] -function Option.insert(self: Option, val: T): T - self._optValue = val - return self._optValue :: T -end - ---[=[ - @within Option - - Inserts value into the option, then returns it. - - If the option already contains a value, the old value is dropped. - - See also [Option:getOrInsert], which doesn’t update the value if the - option already contains [Option:Some]. - - ```lua - local opt: Option = None() - local val: number = opt:insert(1) - assert(val == 1) - assert(opt:unwrap() == 1) - - local val: number = opt:insert(2) - assert(val == 2) - ``` - - @param self Option - @param val T -- The value to insert - @return T -]=] -function Option.getOrInsert(self: Option, val: T): T - if self:isNone() then - self._optValue = val - end - - return self._optValue :: T -end - ---[=[ - @within Option - - Takes the value out of the option, leaving an [Option:None] in its place. - - ```lua - local x: Option = Some(2) - local y: Option = x.take() - assert(x == None()) - assert(y == Some(2)) - - local x: Option = None() - local y: Option = x.take() - assert(x == None()) - assert(y == None()) - ``` - - @param self Option - @return Option -]=] -function Option.take(self: Option): Option - if self:isSome() then - local val = self._optValue :: T - self._optValue = nil - return Some(val) - end - - return None() -end - ---[=[ - @within Option - - Replaces the actual value in the option by the value given in parameter, returning - the old value if present, leaving an [Option:Some] in its place without - deinitializing either one. - - ```lua - local x: Option = Some(2) - local old: Option = x:replace(5) - assert(x == Some(5)) - assert(old == Some(2)) - - local x: Option = None() - local old: Option = x:replace(3) - assert(x == Some(3)) - assert(old == None()) - ``` - - @param self Option - @param val T - @return Option -]=] -function Option.replace(self: Option, val: T): Option - local current: Option = self - self._optValue = val - - if current:isNone() then - return current - end - - return Some(current._optValue :: T) -end - ---[=[ - @within Option - - Returns true if `val` is contained in the [Option:Some]. - - ```lua - local x: Option = Some(2) - local y: Option = None() - - assert(x:contains(2)) - assert(x:contains(4)) - assert(not y:contains(2)) - ``` - - @param self Option - @param val T - @return boolean -]=] -function Option.contains(self: Option, val: T): boolean - if self:isSome() then - return self._optValue == val - end - - return false -end - ---[=[ - @within Option - - Zips `self` with another [Option]. - - If `self` is [Option:Some](s) and other is [Option:Some](o), this method returns - [Option:Some]({s, o}). Otherwise, [Option:None] is returned. - - ```lua - local x: Option = Some(1) - local y: Option = Some("hi") - local z: Option = None() - - assert(x:zip(y) == Some({ 1, "hi" })) - assert(x:zip(z) == None()) - ``` - - @param self Option - @param other Option>U> - @return Option<{T | U}> -]=] -function Option.zip(self: Option, other: Option): Option<{ T | U }> - if self:isSome() and other:isSome() then - return Some({ self._optValue, other._optValue }) - end - - return None() -end - ---[=[ - @within Option - - Zips `self` and another [Option] with function `op`. - - If `self` is [Option:Some](s) and other is [Option:Some](o), this method returns - [Option:Some](op(s, o)). Otherwise, [Option:None] is returned. - - ```lua - type Point = { - x: number, - y: number, - } - local Point: Point & { - new: (x: number, y: number) -> Point, - } = {} - - function Point.new(x: number, y: number): Point - return { - x = x, - y = y, - } - end - - local xCoord: Option = Some(17.5) - local yCoord: Option = Some(42.7) - - assert(xCoord:zipWith(yCoord, Point.new), Some({ x = 17.5, y = 42.7 })) - assert(x:zipWith(None(), Point.new), None()) - ``` - - @param self Option - @param other Option>U> - @param op (x: T, y: U) -> R? - @return Option -]=] -function Option.zipWith(self: Option, other: Option, op: (x: T, y: U) -> R?): Option - 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 - ---[=[ - @within Option - - Unzips an option containing a table of two options. - - If `self` is `Some({a, b})` this method returns `(Some(a), Some(b))`. - Otherwise, `(None(), None())` is returned. - - ```lua - local x: Option<{ number | string }> = Some({ 1, "hi" }) - local y: Option<{ number }> = None() - - assert((x:unzip() == Some(1), Some("hi"))) - assert((y:unzip() == None(), None())) - ``` - - @param self Option - @return (Option, Option) -]=] -function Option.unzip(self: Option): (Option, Option) - 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 - ---[=[ - @within Option - - Returns the inner value wrapped by the [Option]. - - ```lua - local x: Option = Some("lol") - local y: Option = None() - - assert(x:unwrapUnchecked() == "lol") - assert(y:unwrapUnchecked() == nil) - ``` - - @param self Option - @return T? -]=] -function Option.unwrapUnchecked(self: Option): T? - return self._optValue -end - ---[=[ - @within Option - - Returns a formatted representation of the option, often - used for printing to stdout. - - ```lua - local x: Option = Some(123) - local y: Option = None() - - print(x:display()) -- prints `Option::Some(123)` - print(y:display()) -- prints `Option::None` - ``` - - @param self Option - @return string -]=] -function Option.display(self: Option): string - return tostring(self) -end - -return { - Option = Option, - Some = Some, - None = None, -} diff --git a/rbx/result.luau b/rbx/result.luau deleted file mode 100644 index a9db7ac..0000000 --- a/rbx/result.luau +++ /dev/null @@ -1,900 +0,0 @@ -local tableEq = require(script.Parent.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 --- selene: allow(unused_variable) -function Result.iter(self: Result): 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 --- selene: allow(unused_variable) -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? --- selene: allow(unused_variable) -function Result.intoOk(self: Result): never - return error("Unimplemented: `Result:intoOk()`") -end --- selene: allow(unused_variable) -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, -} diff --git a/rbx/util.luau b/rbx/util.luau deleted file mode 100644 index ade2529..0000000 --- a/rbx/util.luau +++ /dev/null @@ -1,102 +0,0 @@ -local isRoblox = _VERSION == "Luau" -local isLune = _VERSION:find("Lune") == 1 -local deps = if isLune then require(script.Parent.Parent.deps)elseif isRoblox then require(script.Parent.deps) else error("Unsupported Runtime!") -local requires = deps(isRoblox, isLune) - -local LuauSignal: { - new: () -> Signal -} = requires.signal - -export type Signal = { - Fire: (self: Signal,T...) -> (), - Connect: (self: Signal, callback: (T...) -> ()) -> () -> (), - Once: (self: Signal, callback: (T...) -> ()) -> () -> (), - DisconnectAll: (self: Signal) -> () -} - --- 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 - - return true -end - -local RobloxEvent = {} -type RobloxEvent = Signal & { - _inner: BindableEvent -} - -function RobloxEvent.new() - local instance = Instance.new("BindableEvent") - instance.Parent = script - instance.Name = tostring({}):split(" ")[2] - - return setmetatable({ - _inner = instance - }, { - __index = RobloxEvent - }) -end - -function RobloxEvent.Fire(self: RobloxEvent, ...: T...) - return self._inner:Fire(...) -end - -function RobloxEvent.Connect(self: RobloxEvent, callback: (T...) -> ()) - local conn = self._inner.Event:Connect(callback) - - return { - DisconnectAll = function(self: RobloxEvent) - conn:Disconnect() - self._inner:Destroy() - end - } -end - -function RobloxEvent.Once(self: RobloxEvent, callback: (T...) -> ()) - local conn = self._inner.Event:Connect(callback) - - return { - DisconnectAll = function(self: RobloxEvent) - conn:Disconnect() - self._inner:Destroy() - end - } -end - -return { - tableEq = tableEq, - Signal = setmetatable({}, { - __index = if isLune then LuauSignal elseif isRoblox then RobloxEvent else error("Unsupported runtime!"), - }), -}