This is very cool! I only just started reading the section 0 intro, but one topic that I didn’t see covered that’s interesting to me about Typescript is gradual typing. How does that fit into a type system? How do the typed sections interact with the untyped, and what kind of options are there for the semantics around that?
TypeScript’s type system is best effort and is not actually sound. Anders is quite open about this - he wants to create a language that people want to use, and since this is his third successful programming language I’m willing to believe pretty much anything he says about language design. TypeScript’s big advantage here is that it compiles down to JavaScript, which does dynamic type checking, and so any unsoundness in TypeScript’s type system is caught at run time. This lets them focus on making the common cases easy to use, at the expense of making a few really uncommon cases fail. In particular, TypeScript’s idea of equality for recursive types in generics gives up after a certain depth and says ‘sure, these things are the same type’. You might get an exception at run time if they aren’t. The reference says this explicitly on soundness:
TypeScript’s type system allows certain operations that can’t be known at compile-time to be safe. When a type system has this property, it is said to not be “sound”. The places where TypeScript allows unsound behavior were carefully considered, and throughout this document we’ll explain where these happen and the motivating scenarios behind them.
Specifically in terms of gradual typing, I believe it just allows any untyped object to be cast to any type. This is fine for the same reason: if you got it wrong (and you can dynamically query whether a object matches a type, so it probably is your fault if you got it wrong) then it will be caught at run time. Here’s a simple example:
function mk()
{
var a : any = {};
a.foo = 12;
return a;
}
function x(obj : {bar : number})
{
console.log(obj.bar);
}
x(mk());
The mk function returns an object of type any', which is an unconstrained type. You pass it to xand there are some constraints and TypeScript accepts this, but at run time this will printundefined. If you change the definition of mk` to this:
function mk() : { foo : number }
Now the static type is something that has a foo field that is a number. The type checker will now say:
Argument of type '{ foo: number; }' is not assignable to parameter of type '{ bar: number; }'.
Property 'bar' is missing in type '{ foo: number; }' but required in type '{ bar: number; }'.
This is an explicit design choice because TypeScript has to fit in an ecosystem with JavaScript libraries. If you couldn’t use JavaScript from TypeScript before you’d added type information to every single JavaScript function then the language would be unusable. I don’t think that you’d end up here if you were designing a language from scratch. The dynamic checking that TypeScript requires incurs a performance (or, at best, a JIT-complexity) cost but TypeScript can get away with it because it’s running on a JavaScript implementation that is already paying this cost.
Even with these limitations, with TypeScript Anders and friends have managed to get normal programmers to be enthusiastic about using a language with a structural and algebraic type system, which makes me incredibly happy.
This is very cool! I only just started reading the section 0 intro, but one topic that I didn’t see covered that’s interesting to me about Typescript is gradual typing. How does that fit into a type system? How do the typed sections interact with the untyped, and what kind of options are there for the semantics around that?
TypeScript’s type system is best effort and is not actually sound. Anders is quite open about this - he wants to create a language that people want to use, and since this is his third successful programming language I’m willing to believe pretty much anything he says about language design. TypeScript’s big advantage here is that it compiles down to JavaScript, which does dynamic type checking, and so any unsoundness in TypeScript’s type system is caught at run time. This lets them focus on making the common cases easy to use, at the expense of making a few really uncommon cases fail. In particular, TypeScript’s idea of equality for recursive types in generics gives up after a certain depth and says ‘sure, these things are the same type’. You might get an exception at run time if they aren’t. The reference says this explicitly on soundness:
Specifically in terms of gradual typing, I believe it just allows any untyped object to be cast to any type. This is fine for the same reason: if you got it wrong (and you can dynamically query whether a object matches a type, so it probably is your fault if you got it wrong) then it will be caught at run time. Here’s a simple example:
The
mk
function returns an object of typeany', which is an unconstrained type. You pass it to
xand there are some constraints and TypeScript accepts this, but at run time this will print
undefined. If you change the definition of
mk` to this:Now the static type is something that has a
foo
field that is a number. The type checker will now say:This is an explicit design choice because TypeScript has to fit in an ecosystem with JavaScript libraries. If you couldn’t use JavaScript from TypeScript before you’d added type information to every single JavaScript function then the language would be unusable. I don’t think that you’d end up here if you were designing a language from scratch. The dynamic checking that TypeScript requires incurs a performance (or, at best, a JIT-complexity) cost but TypeScript can get away with it because it’s running on a JavaScript implementation that is already paying this cost.
Even with these limitations, with TypeScript Anders and friends have managed to get normal programmers to be enthusiastic about using a language with a structural and algebraic type system, which makes me incredibly happy.