-
Notifications
You must be signed in to change notification settings - Fork 12.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Generic namespaces #19728
Comments
Maybe allowing a type alias in a class would help here? class MyStackModule<T> {
type Stack = T[];
create(): Stack { return []; }
push(s: Stack, value: T) { s.push(value); }
pop(s: Stack): T | undefined { return s.pop(); }
}
// Instantiate the module like an ocaml functor. Now everything will work on `number`.
const NumStack = new MyStackModule<number>();
const stack: NumStack.Stack = NumStack.create();
NumStack.push(stack, 1);
const one = NumStack.pop(stack); More practically, it might help to know why type inference isn"t working for |
(Please note that I revised my original proposal above) The functions are mostly just helper object factories to cut down on typing/noise. Here"s a cartoon of the library: // Library
export class Class<T> {
constructor(private lambda: (t: T) => void);
twice(): Class<T> {
return new Class<T>(t => {
this.lambda(t);
this.lambda(t);
});
}
static makeTwice<T>(lambda: (t: T) => void): Class<T> {
return new Class(lambda).twice();
}
}
export function func<T>(lambda: (t: T) => void) {
return new Class<T>(lambda);
}
// Library consumer
import { Class, func } from "my-library";
// the consumer creates lots of objects like this
const object = func<Bot>(t => t.reply("I hear you, brother"));
// or
const object = func((t: Bot) => t.reply("I hear you, brother")); There"s no way to infer the This also might explain why allowing a type alias in a class won"t help. I"m not using classes to build data structures. I"m using them as bags of methods that operate on lambdas whose parameters are always the same type. There are several classes, and they need access to each"s constructors, static methods, and types. Here"s the way I want to write the above: export namespace MyLibrary<T> {
export class Class {
constructor(private lambda: (t: T) => void);
twice(): Class{
return new Class(t => {
this.lambda(t);
this.lambda(t);
});
}
static makeTwice(lambda: (t: T) => void): Class {
return new Class(lambda).twice();
}
}
export function func(lambda: (t: T) => void) {
return new Class(lambda);
}
} Much cleaner without all those extra generics. |
Seems like the general problem is generic variables. |
@aluanhaddad can you expand on that thought? |
@billba Seems like you could replace a.ts export class Class<T> {
constructor(private lambda: (t: T) => void) {}
twice(): Class<T> {
return new Class<T>(t => {
this.lambda(t);
this.lambda(t);
});
}
static makeTwice<T>(lambda: (t: T) => void): Class<T> {
return new Class(lambda).twice();
}
}
export default class Library<T> {
func(lambda: (t: T) => void): Class<T> {
return new Class<T>(lambda);
}
} b.ts: import Library from "./a";
const lib = new Library<number>();
lib.func(n => { n.toExponential() }); We"re unlikely to add any new features to namespaces in the future as they are not a JS feature and have been replaced by real ES modules. (See the design goals.) |
That"s interesting. It"s not a complete solution, because sometimes the developer will need access to the interfaces and/or fundamental classes, which would still require type decorations. But all the helper functions could be piled into Library, which would probably cover most of the common use cases. I"ll ponder that. Thanks for the info about namespaces. |
The slightly annoying thing about the "put the helper functions in a generic class" approach is that the generated JS includes an unnecessary object creation. I hate it when using TypeScript introduces unnecessary JS code just to make typing work. But it does work. |
@billba I meant that allowing a namespace to be generic is a special case of allowing a variable to be generic. declare const Promise: Promise<T> |
Yeah, this is the heart of the matter. Ultimately what I"m looking for here, and I don"t think I"m alone, is a way to "clamp" a generic class or function or module to a specific type. I think this would require a new kind of alias: a.ts export class GenericClass <T> { ... }
export function genericFunction <T> { ...} b.ts import { GenericClass, genericFunction } from "./a";
export alias StringClass = GenericClass<string>;
export alias stringFunction = genericFunction<string>; c.ts import { StringClass, stringFunction } from "./b";
const stringClass = new StringClass(...);
stringFunction(...); These are essentially macros. They are immutable, and every time TypeScript sees the alias, it substitutes in its value. If you mouse over The nice thing about the alias approach is that it doesn"t generate any JavaScript. |
For a class you can do e.g. |
With functions you can create a delegating function that fixes the type parameter. With type-inference it"s actually rather terse. export const stringFunction = (x: string) => genericFunction(x); Of course this creates an extra value but if we"re talking about extending classes it"s reasonable. |
Right but this is all a lot of unnecessary JS to get around a simple typing operation. |
Basically if you"re working in JS then you can just import the classes and functions from my library and you"re done. Intuitively, using TypeScript for the same purpose shouldn"t be harder or heavier weight than adding a type declaration. |
Actually, this would be covered by #17574 |
Closing in favor of #17574 |
Automatically closing this issue for housekeeping purposes. The issue labels indicate that it is unactionable at the moment or has already been addressed. |
REVISED: My initial proposal was wrong, here is a revised version.
I have a library that exports a collection of interrelated interfaces, classes, and functions, which operate collaboratively on a single generic type. For instance, you can use them to perform operations on bot messages, game actions, or IoT events, but typically you"d only ever be working with one of those at a time. So the fact that the library is generic gives it broad appeal to a wide variety of developers, but is not itself of value to a given developer. (Contrast with RxJS where solid generic support has specific value to a given developer, because they will likely use it with a wide variety of types in a given project.)
An unfortunate consequence of the way the components of this library come together is that they are sometimes very resistant to type inference, so many invocations end up requiring a type decoration, which creates an enormous amount of visual noise. It"s a terrible developer experience.
What I have now is:
Type aliases help a little, because I can do:
But there is no equivalent for functions and classes. And even if there were, that would still make it painful to write my library, because it would be using generics everywhere when what I really want to do is write my library with just a single top-level generic. And it would still make it overly painful to consume my library, because aliases would have to be manually created for every component in the library. What I want is a generic namespace:
Workarounds: I tried mimicking this by creating a generic factory class, but it is a huge hack and not powerful enough. There is a good reason namespaces exist in TypeScript, and adding generics would make them much more powerful. As it stands, the only real workaround -- which I have been forced to take, and is obviously unsustainable and unmaintainable -- is to fork the library into one version for each type.
The text was updated successfully, but these errors were encountered: