Skip to content

Instantly share code, notes, and snippets.

@deter0
Created January 25, 2022 18:22
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save deter0/e467f8d4658374d7280b089596f69aaa to your computer and use it in GitHub Desktop.
Save deter0/e467f8d4658374d7280b089596f69aaa to your computer and use it in GitHub Desktop.
Type Safe Signal Class
--!strict
--@Signal Class (deter)
--Example:
-- local mySig = Signal.new();
-- mySig:Connect(function(a: number)
-- print("A");
-- return 1;
-- end) --> Connection type is infered as `(a:number) -> number`
-- mySig:Connect(function(a:number)
-- print("B")
-- return 1;
-- end) --> Fine
-- mySig:Connect(function(a:string)
-- return "";
-- end) --> Incorrect since the function provided has type `(a:string) -> string` not `(a:number) -> number`
-- mySig:Dispatch(); --> Find due to luau bug
-- mySig:Dispatch("A"); --> Incorrect
-- mySig:Dispatch(1); --> Correct and intended
local Signal = {};
function Signal.new<P, R>()
type T = (P) -> (R);
type self = typeof(Signal.new());
return setmetatable({
ConnectionPool = {} :: {T},
Connect = function(self: self, Callback: T)
self.ConnectionPool[#self.ConnectionPool + 1] = Callback;
end,
Dispatch = function(self:self, ...:P)
local args: {P} = {...};
for _, Callback in ipairs(self.ConnectionPool) do
xpcall(function()
(Callback :: T)(unpack(args) :: P);
end, coroutine.wrap(function(Err)
error(Err);
end));
end
end,
-- NOTE(deter): Unsafe meaning that if any function in `ConnectionPool` throws an error it will not fire the rest of the functions.
-- This is probably faster as it gets rid of any `coroutine.wrap`, `xpcall`, `unpack` calls.
DispatchUnsafe = function(self:self, ...:P)
for _, Callback in ipairs(self.ConnectionPool) do
(Callback :: T)(...);
end
end,
}, {__index = Signal});
end
@deter0
Copy link
Author

deter0 commented Jan 25, 2022

Rant about luau generics

Inferred generics are a horrible idea all in all. I don't know why they just wouldn't make them required or any if they're not provided. Why do they have to break programming traditions this code could've been like every other luau class written but it had to be changed because all the other methods I tried wouldn't comply with generic types. They could've simple added a Signal.new<() -> ()>(); and that would've been perfect! Zero complaints. But for whatever reason that I do not know they decided that generics should be inferred. This makes it way harder to write code that is type safe but it really makes you think that weather it's worth having to change the way you write code instead of just doing x :any I don't know I could be wrong about how I wrote this but I tried like 10 methods and this is the only one that worked. If anyone knows a better way to write generic classes, please tell me.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment