allow type parameters on type arguments
zpdDG4gta8XKpMCd opened this issue · 6 comments
interface Map<a> {
[key: string]: a;
}
// IMPORTANT: here my intention is that the `read` and `run` methods are agreed on using the same type parameter
interface Command<Options> {
read(args: string[]): Options;
run(options: Options): void;
};
interface CopyCommandOptions {
from: string;
to: string;
}
interface CleanCommandOptions {
directory: string;
pattern: string;
}
// currently in TypeScript there are 2 options to to declare `knownCommands` below:
// - Map<Command<any>> which contaminates my code with `any`
// - Map<Command<CopyCommandOptions | CleanCommandOptions>> which has different semantics
// ideally I wish I could declare it like this:
let knownCommands : Map<<a>Command<a>> = { // <-- hypothetical syntax
'copy': <Command<CopyCommandOptions>> undefined,
'clean': <Command<CleanCommandOptions>> undefined
};
// so that later I could the following do in a type safe manner:
let command = knownCommands[name];
command.run(command.read(args));
From reading your example, it looks to me like you're wanting existential types?
Keep in mind that I haven't really done any typescript before - so there's likely a nicer way to do what I came up with:
type Exists =
<r> (go : <t> (_ : Command<t>) => r) => r
const exists = <c> (command : Command<c>) : Exists =>
<r> (go : <t> (_ : Command<t>) => r) : r => go(command)
let knownCommands : Map<Exists> = {
'copy': exists(<Command<CopyCommandOptions>> undefined),
'clean': exists(<Command<CleanCommandOptions>> undefined)
};
let args : string[]
knownCommands[name](<t> (command : Command<t>) => {
command.run(command.read(args));
});
Does that roughly do what you want in this case? @Aleksey-Bykov
@LiamGoodacre
This is a neat trick. Didn't realize I could do it this way. Yes, it looks like existential types is the name of what I've been looking for, which now can be googled and read about closely. Thanks.
😄 Also to highlight that the read/run types match up:
knownCommands[a](<t> (x : Command<t>) => {
knownCommands[b]<s> (y : Command<s>) => {
//x.run(y.read(args)); // type error, `s` is not `t`
//y.run(x.read(args)); // type error, `t` is not `s`
x.run(x.read(args)); // fine
y.run(y.read(args)); // fine
});
});
This may be useful as an example of existential types as a library: https://github.com/purescript/purescript-exists/blob/master/docs/Data/Exists.md
This hasn't gotten a concrete proposal yet and I imagine there are more well-fleshed-out issues on it by now