Typeable FastSignal

Introduction

So, this is a pretty hacky thing I discovered but it ends up being actually extremely useful.

In short, this is a modified version of FastSignal that allows for Type-Generics to be applied on so you can get type inference for your callbacks.

Example

local Signal = require(Signal)

local newSignal: Signal.Signal<number, string> = Signal.new()

newSignal:Connect(function(A, B): () -- A & B are Typed!
	print(A, B)
end)

newSignal:Fire(1, "Hello") -- number, string
newSignal:Fire(1, 2) -- 2 cannot be a 'string'!

Source

Notes

This is a hacky boilerplate workaround for this feature request, but since Type-Generics and strict inference are still a work in progress, this isn’t bug or future-proof. I’m only sharing this as a proof of concept.

4 Likes

Technically, it doesn’t care if you hackily type a BindableSignal’s event object as

TypedRBXScriptSignal<A...> = {
  Connect: (TypedRBXScriptSignal<A...>, func: (A...) -> ()),
  ...
}

local e = BindableEvent.Event :: TypedRBXScriptSignal<"string">

Obviously this only works under nonstrict mode.

1 Like

Is this possible for GoodSignal? I don’t like stanvarts’ FastSignal as it is very far from RBXScriptSignal in a lot of ways, I remember trying this with my signal implementation and something stopped me, some slight error or whatever and I gave up on it, it is something that I really wanted and still want to implement because typings in Signals would be crazy nice, but working with metatables and all that is very hard, I also wanted to make sure that it wouldn’t hurt the typings inside the Signal code, so that if optimizations are added to typed code that internal members wouldn’t suffer because of that.

Yes, absolutely. All I’m doing is overriding the class type with a pseudo-interface so I can achieve Generic capabilities. It’s hacky but as long as the type matches the actual functionality of the class, and you aren’t doing any metatable witchcraft, it should be fine.

All you would have to do is replace the return Signal in the module with

type Connection = {
	Disconnect: (self: any) -> ()
}

export type Signal<T...> = {
	Fire: (self: any, T...) -> (),
	Connect: (self: any, FN: (T...) -> ()) -> Connection,
	Once: (self: any, FN: (T...) -> ()) -> Connection,
	Wait: (self: any) -> T...,
	DisconnectAll: (self: any) -> ()
}

return Signal :: {new: () -> Signal<...any>}
1 Like