Writing types in JavaScript instead of TypeScript
See original GitHub issueOkay, so I’ve been pondering (and talking to @axefrog). If TypeScript’s type system itself is turing complete and that offers great opportunities, why are we writing the types in this stunted turing tarpit language, instead of writing the types themselves in JavaScript too? Here’s the idea: you write a program, that when run, outputs itself without the type annotations. This is exactly like what TypeScript does, but in this case, the input is JavaScript, the output is JavaScript and the original JavaScript is both the source and the compiler at the same time (running the program is compilation, the output is the stripped version).
Here’s what that would look like:
import { Type, isType } from "js-types";
// Record definition
const Complex = {
real: Number,
imaginary: Number
}
// Function definition
const ComplexMult = (a, b) => {
if (isType(a, Number) && isType(b, Number)) {
return Number;
} else {
throw TypeError();
}
};
Type(
ComplexMult,
function mult(a, b) {
return {
real: (a.real * b.real) - (a.imaginary * b.imaginary),
imaginary: (a.real * b.imaginary + a.imaginary * b.real)
};
}
);
// Alternate ways to write the definition:
const ComplexMultAlt1 = (a, b) => {
if (isType({ a, b }, { a: Number, b: Number })) {
return Number;
} else {
throw TypeError();
}
};
// With arrays instead of objects:
const ComplexMultAlt2 = (a, b) => {
if (isType([a, b], [Number, Number])) {
return Number;
} else {
throw TypeError();
}
};
// Helper definition, notice it returns a function
const Func = (inputTypes, outputType) => {
return (...inputs) => {
if (isType(inputs, inputTypes)) {
return outputType;
}
else {
throw TypeError();
}
};
};
// With this helper, we could've done this:
Type(
Func({ a: Complex, b: Complex }, Complex),
function mult(a, b) {
return {
real: (a.real * b.real) - (a.imaginary * b.imaginary),
imaginary: (a.real * b.imaginary + a.imaginary * b.real)
};
}
);
// Example of function returning function, very simple
Type(
Func(Complex, Func(Complex, Complex)),
function add(a) {
return (b) => ({
real: a.real + b.real,
imaginary: a.imaginary + b.imaginary
});
}
);
// Generics, again, super simple
const IdentityFunction = a => a;
Type(
IdentityFunction,
function identity(a) {
return a;
}
);
// Variadic kinds
// Note that f would also be a type-checking function
// when it comes in
const CurryFunction = (f, ...a) => {
if (isType(f, Function)) {
return (...b) => f(...a, ...b)
}
else {
throw TypeError();
}
};
Type(
CurryFunction,
function curry(f, ...a) {
return (...b) => f(...a, ...b);
}
);
And here’s the output of running that program:
function mult(a, b) {
return {
real: a.real * b.real - a.imaginary * b.imaginary,
imaginary: a.real * b.imaginary + a.imaginary * b.real
};
}
function add(a) {
return b => ({
real: a.real + b.real,
imaginary: a.imaginary + b.imaginary
});
}
function identity(a) {
return a;
}
function curry(f, ...a) {
return (...b) => f(...a, ...b);
}
I know this is quite the silly idea, but I think there’s merit to it. As you can see, the sample seems to indicate it can easily type a curry function, something that is apparently quite hard in TypeScript.
Issue Analytics
- State:
- Created 6 years ago
- Reactions:1
- Comments:14 (3 by maintainers)
Top GitHub Comments
My example was very crude, if I manage to hook into the TS system, you’d still be able to throw decent errors of course. And yeah, I agree that with just JS types, that might be a serious step backwards from what we have now (even with a good inference engine).
I think the next step is for me to put the money where my mouth is and provide a PoC, so we can further discuss merits 😃
And yeah, I keep wanting to play around with tcomb and schemas, I should do that at some point.
I’m gonna mark closed in the sense of ‘not actionable within the scope of this lib’. Discussion though is always welcome.