Can someone comment on the difference in reliability between using typescript and a natively statically typed language like haskell or scala? Is there any? Or is the type safety really as good when you use ts
// this should not be assignable, functions should be
// contravariant WRT arguments, so if o2 is subtype of o1
// then F1 is subtype of F2, which means you can't assign
// a value of type F2 to F1: there are functions in the
// set of functions F2 that don't belong in the subset F1
type O1 = { a: string; b: string }
type O2 = { a: string; b: string; c: string }
type F1 = (o: O1) => string
type F2 = (o: O2) => string
// should not compile!
let f: F1 = (o: O2) => { return o.c.toString() }
f({a: '1', b: '2'}) // throws at run time (o.c undefined)
Other than that, TypeScript has been pretty safe since the addition of "strictNullChecks".Haskell and Scala have algebraic datatypes (or case classes) which make it easier to model data (especially in Haskell where the syntax is really nice). TypeScript also supports disjoint unions (union of several types with a tag field that has a concrete string value as the type) and control flow analysis, so with some effort it can provide "idiomatic" JS-style ADTs. Haskell and Scala can do exhaustiveness checks, making sure you've covered all the cases. TS can also sort of do that (when strictNullChecks are on):
type T1 = { tag: 't1', value: string }
type T2 = { tag: 't2', value: number }
type T3 = { tag: 't3' }
type T = T1 | T2 | T3
function f(t: T):string {
switch (t.tag) {
case 't1': return t.value;
case 't2': return t.value.toString()
// Compiles only if you uncomment this line.
// Otherwise inferred return type string|undefined is
// incompatible with specified return type string
//case 't3': return 'N/A'
}
}
Not as pleasant or as general as Haskell, but better than most other mainstream languages.Both Haskell and Scala have higher kinded generics, something that TypeScript still lacks. This means importing category theory concepts in TypeScript is pretty much impossible. The problem isn't just with category theory though; the need to parameterise generic types with other generic types as type arguments can come up everyday JS code too. For example, a library that accepts a promise constructor as an argument is parameterized by a generic type [1]
Before v2.1 TypeScript already had some powerful features for working with record types, such as record unions and intersections. With mapped types introduced in 2.1 however, you could say it surpasses Haskell and Scala in this regard - if not with features, then at least with pleasantness / ease of use. I believe its possible to achieve the same things in Haskell with vinyl [2][3], but those are definitely not Haskell's native records. Not sure if Scala's shapeless [4] can provide something similar (the answer is probably yes).
Haskell has type classes, and Scala can provide the "equivalent" via implicits+traits. Both of these allow a very nice and generic style of programming with implementation flexibility (you can write new typeclass instances for old code). TypeScript has structural interfaces, which offer some flexibility compared to nominal interfaces: you can write an interface that is a subset of old code, then use both old and new code under that interface without modifying the old code. Still, its not as flexible as typeclasses (the implementation must already be a subset of the original old code). They also can't offer the return-type-based implementation selection that typeclasses can.
Of course, Haskell and Scala both have various type / macro / template superpowers that TypeScript doesn't have and isn't likely to ever have. For everyday programming though, the above list should cover the vast majority of interesting features.
[1]: https://github.com/Microsoft/TypeScript/issues/1213 [2]: https://hackage.haskell.org/package/vinyl-0.5.2/docs/Data-Vi... [3]: https://hackage.haskell.org/package/vinyl-0.5.2/docs/Data-Vi... [4]: https://github.com/milessabin/shapeless/wiki/Feature-overvie...