Repository navigation
Typed Function for better class inheritance.Β #60596
Description
π Search Terms
"Typed Function", "typed Callable object", "extends Function", "Function type definitions", "Function typed Parameters", "Function typed ReturnType", "generic Function types"
β Viability Checklist
- This wouldn't be a breaking change in existing TypeScript/JavaScript code
- This wouldn't change the runtime behavior of existing JavaScript code
- This could be implemented without emitting different JS based on the types of the expressions
- This isn't a runtime feature (e.g. library functionality, non-ECMAScript syntax with JavaScript output, new syntax sugar for JS, etc.)
- This isn't a request to add a new utility type: https://github.com/microsoft/TypeScript/wiki/No-New-Utility-Types
- This feature would agree with the rest of our Design Goals: https://github.com/Microsoft/TypeScript/wiki/TypeScript-Design-Goals
β Suggestion
I'd like to be able to define the actual Function type with some generic types for its parameters and return type.
In my head, something like:
type Function<P extends any[] = any[], R = any>;... where P is the type of the parameters and R is the return type.
Or -yet- better to support optional parameters:
type Function<T extends (...args: any[]) => any = (...args: any[]) => any>;... where T is the actual function type on which internals can perform Parameters<T> and ReturnType<T>.
This would allow developers to define types like this:
type MyFunction = Function<(value: number, base?: number) => boolean>;π Motivating Example
I do know well that -as described- it seems to be completely useless.
This hypothetical type MyFunction won't be different from:
type MyFunction = (value: number, base?: number) => boolean;So why this proposal?
The real reason to use this comes from the need to implement something like this:
class MyFunction extends Function<(value: number, base?: number) => boolean>
{
/* ... */
}By extending the Function class, you can make objects callable just like any other function, while still having their own properties and methods.
Right now, it is possible to make an object callable like so, while isn't allowed to strictly define what kind of parameters it takes or what kind of result it will return.
The current type will be (...args: any[]) => any which "disables" all TypeScript typing and doesn't allow relying on types when using such objects.
π» Use Cases
1. What do you want to use this for?
I'm going to use this like -as I said- to strictly type callable objects in a secure way.
2. What shortcomings exist with current approaches?
The type system isn't reliable when these kind of objects are involved.
3. What workarounds are you using in the meantime?
I'm not implementing a callable object, since I need to rely on type system.
I'm using some wrappers, which make things more complex to handle and less readable, while adding a little JavaScript overhead in a case where it's just a typing issue and shouldn't involve any additional JavaScript code at all.
Activity
jcalz commentedon Nov 25, 2024
What's in the
/* ... */there? How does the function body passed as a string ensure that it actually implements(value: number, base?: number) => boolean? Seems like nothing stopsnew MyFunction()which will always returnundefinedwhen called. I'm having a hard time imagining how TS would actually provide the type safety implied by this.Byloth commentedon Nov 26, 2024
Oh, sorry... My bad!
Of course, my
/* ... */is a comment to leave space to whatever implementation a developer would need.I would never imagine that the body of a generic class -in this case- would affect in some way what I was saying...
But... Anyway... I'm going to provide you what I was trying to implement:
At the end of the day, I perfectly know that if the first kind of use isn't type-safe, I can fall back on the second kind.
They do the same exact thing.
What I was pointing out is that this is a missing type-check in TypeScript.
Probably I'm the only one that ever dared to extend a
Functionclass to implement a callable object... Sad but makes sense. πBut if it were possible to define the type of the actual
Functionusing generics, this kind of problem would be solved.The defined generic type on
Function-of course- would be the actual function signature to let TypeScript know what kind of function it is, what kind and how many parameters it accepts and what value type it returns.At the moment, I'm using some functions to workaround this problem:
I also tried to define a decorator to do this and to make
@callableany class...But a decorator does not change the TypeScript type of the class so I wasn't able to solve this problem.
mkantor commentedon Nov 26, 2024
You may be aware of this, but TypeScript already has the ability to model well-typed functions with additional properties. Here's a silly example:
The type of such a function can be described using an
interfacewith a call signature:The handbook has more info.
Byloth commentedon Nov 26, 2024
Hi, Matt Kantor (@mkantor)!
Yeah... I tried it in one of my tests...
Sadly, I can't inherit it to a new class and extend the
foofunction.There's no inheritance in this case.
I also tried to define my class as follows:
... but -again- it ends up with an typing error since
CallableObjectdoesn't implement theIFunctionsignature... Apparently.During one of my attempts, I also tried something as follows:
... but then I wasn't able to inherit it in a child class, since you cannot inherit a
type.mkantor commentedon Nov 26, 2024
I wouldn't use classes at all for what you're trying to do. Classes aren't designed to produce callable instancesβeven in plain JavaScript there's no way to make this work without manual prototype hacking or unsafely creating a
Functionfrom a string like you're doing, both of which should put up red flags.Here's a simpler and safer way to handle your use case:
If you're convinced that you really do need to make a class with callable instances, I think this approach is better than extending
Function(but again I strongly advise against going down this path in the first place).jcalz commentedon Nov 26, 2024
I don't see a benefit in modifying
Functionfor everyone who uses TS, especially since the motivating use case looks like an exceptionally uncommon approach to implementing functions. Can't you just assignFunctionto a new variable and give it whatever construct signature you want? Maybe likePlayground link to code
I'm not saying the particulars of the above are what you want, just that nothing is stopping you from doing this locally in your own code. Is there something where the actual language needs to change in order to support this use case? The only thing I can think of is if you wanted TS to somehow verify that the string-valued constructor arguments actually satisfies the call signature, but that is almost certainly too complex. Maybe I'm just not seeing something obvious here?
Byloth commentedon Nov 26, 2024
The approach of Matt Kantor (@mkantor), as obvious as it may be, it doesn't work as intended.
The reference of
onPointerMovedefined like so, would change without let other handlers knowing about it.Using this approach -from now on- will always print
#0without any possibility of change.Using
() => onPointerMove()would solve this specific problem but -again- would also create other problems...Anyway...
The solution proposed from Joe Calzaretta (@jcalz) it actually work. Perfectly, indeed!
That's exactly what I was trying to do. Nothing less. Nothing more.
Thanks. β€οΈ
In conclusion, IMHO...
You actually do recognize what the use case is...
You actually do recognize that this is something that TypeScript doesn't handle properly...
But since this is an exceptionally uncommon approach I can handle it in my own code rather than make it available to everyone.
Have you ever thought about whether arrays were an extremely uncommon approach?
Fair enough... The important thing is that it's at least implementable, somehow...
That's enough for me.
Thank you for your support.
Hoping this could be useful to someone else, one day.