I was missing this mostly for incoming requests. A good solution for me has been to generate json-schema from typescript interface definitions, using the typescript-json-schema npm package. Then incoming data can be checked vs the generated json schemas. Works fine and I get json schemas for api endpoints from it as well, so no complaints.
if (my_var instance_of MyCustomType)
in TS, but you could in normal statically typed languages, correct?
But in TS itself structural typing is used everywhere for actual type checking (except for classes with private members), and from my understanding there's no way to have TS help you check at runtime whether a type is structurally compatible with a value.
The issues tend to start cropping up as you get further into TS-specific types like generic types. In a statically typed language you can inquire about the specifics of an implementation of a generic directly, and often even dispatch directly on that type. JS has no concept of your generic wrapper type and you need some other flag or logic to determine the runtime type of your generic code.
I wrote a library [1] that does this in a readable pattern-matching way, hope you find it useful!
Typescript can't match them 100%, but it's getting there. e.g. It doesn't have type variance, but it does have f-bounded polymorphism (which is unheard of in most mainstream languages). The language is powerful enough to build your own constructs. I maintain a Typescript library[1] that emulates Scala options/trys/futures.
For day-to-day work, Typescript is a joy to work with. I describe it to others as the child of Scala and Ruby - pretty powerful type system but with a large community.
In a perfect world you'd go with the latter, but there's overhead involved (relatively slow compilation and large generated binaries) that is mostly absent in TS. Also worth noting that TS' community is gigantic in comparison to that of Scala.js and GHCJS.
Regardless, I don't see much of a difference between TS -> JS and haskell->JS and scala -> JS. It's all from one language to another. the fact that TS is still considered a form of javascript is kind of irrelevant other than for semantics
Are they different? If language X can be expressed in its entirety as language Y then we can speak about X and Y interchangeably -- that's literally the point: full interop between client and server.
Anyway, there's a world of difference between TS and GHCJS, Scala.js, Clojurescript, Js_of_ocaml, etc. One speaks javascript, the others speak both.
As for reliability, in so far as TS is typed it will provide the compile time guarantees that its type system provides; not surprisingly the same goes for Scala.js, GHCJS, etc.
So, the decision point probably hinges more on aforementioned overhead, size of community, and tooling than language/type system features/power. If it were otherwise we'd have seen a huge uptick by now wrt adoption rates for the compile-to-js languages. Maybe that will change in future but for now the big players drive the javascript bus...
// 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...