Problems with TypeScript
blog.logrocket.com
blog.logrocket.com
Let me elaborate.
If you are starting a new project, yes use typescript over JS. And configure it with all strict options.
This doesn't magically give you well architected and well-tested code, it still takes a good engineer to do that. But what it does allow is for the engineer to refactor the code to support new functionality with more speed and confidence, rather than shoehorning it in in fear of breaking something or it taking too long. Furthermore, as people join your team, it will be a lot easier for them to figure out where some piece of data is coming from and how the code flows from one spot to the next.
If you have a massive JS project and you want to convert it to typescript. The answer might be no. Oftentimes when people port a JS project to TS, they end up using non strict typescript, and you get the syntactic overhead of writing TS, without really any benefit because nearly everything is any typed. If your only concern is a better codebase, and it doesn't matter how long it takes, then sure it is worth it, but we all know that is never our only concern. It still might be worth it, but I can almost guarantee you from experience, you will still end up with TypeErrors in your console at some point, because people will have just any typed things.
Also if you're writing a tiny node script in a single file, to do some simple operation, then no it is probably not worth setting up a command to compile it and a tsconfig to configure it. Unless it is doing multiple things, and/or someone else in the future will have to use it, then it is probably still worth it.
Typescripts inferred types, can do some crazy things, and can be very powerful. This to me is really fun, but has definitely caused me when I first started using it to get lost in having fun doing crazy things, to ensure my strings were exactly some set of values depending on what some other value was, when a simple unit test and a simple "string" type, would've been a lot quicker and less confusing.
https://www.typescriptlang.org/docs/handbook/enums.html#enum...
https://www.typescriptlang.org/play/index.html?useDefineForC...
Really just a matter of preference though.
It's just you doing something stupid thinking you were being clever, as we all have.
One kind of weird thing we did, was we had these JSON Schema definitions that were const, so all the strings like "type" were of a literal string type. Then using recursive type definitions, and the `extends` key word we were able to extract the actual shape of the data into a type.
Not exactly that useful, compared to typing it out, but it was still kind of cool.
The rest of the project is ESNext/babel - we have to interop with an existing codebase, typing everything is very low value compared to overhead - similar to writing unit tests for UI components.
I'm shopping for this. Also property based testing.
I'm a big static language proponent, but we have to ask ourselves, is typescript simply popular as it's kinda statically typed?
Isn't that the overt main point of TS, hence the name?
I have grown rather weary of type systems that don't require you to be exhaustive and, for my money, types like `Omit` and `Pick` are nasty hacks that just let you pretend like you aren't doing dynamic types, something TypeScript does a lot. The TypeScript pattern of `someFunc<typeof someInput>` is just dynamic types with extra steps. Using `any` wouldn't really lose you much of anything, the "type" is just "whatever this input is". That's not a real type, that's a dolled-up dynamic type. But hey, it typechecks!
TypeScript's major weakness is that it doesn't want to break away from Javascript. That strict dedication to being a superset means the type system explodes into ten thousand builtin types the come, seemingly at times, from nowhere, simply to control for the wildness of Javascript. It feels like a joke sometimes, and like I need to have encyclopedic knowledge of all these highly-specialized types that are included in the language and are often being used in the background.
This leads to nearly impenetrable error messages regarding types, especially if you have a stack trace involving more than one object/record type (whatever you want to call it), it will expand all of them and just, no. It's unreadable. It can be read, but it cannot simply be grokked.
TypeScript just doesn't impress me. I get far better results from other languages for a whole lot less work. TypeScript claims to want a balance between correctness and productivity but the language gives me neither of those things. My typical experience is that I wrote almost the same amount of actual code, but now also with types. Elm lets me write far less total code. Less code means less bugs and more productivity, and better types and better correctness is what gives you those things.
So I think the main thing is typescript should make things better in the javascript world, but due to its ability to gradually adopt typescript syntax over javascript syntax you can live in a middle ground where it's not going to do much for you (or even make it worse).
Ultimately I think compiled WASM based languages are going to take over, but we aren't there yet.
Typescript on the other hand has had massive growth and has been adopted by a large amount of libraries. Now, web ts/js libraries usually suck but it still speaks to the overall health of the language in a community. Even then, typescript still only has a very small percentage of usage.
Elm is basically a rounding error on some of the surveys I've seen.
Long term support, tooling, etc are often much more important for languages past a threshold. Typescript as a language may not be super exciting. Typescript as a technology is wonderful.
Sadly, Elm was never able to build a true core development team outside of Evan. Or at lest it didn’t do it fast enough.
Evan is a smart programmer but not a strategic organizer or a communicator.
As a result, one of the most exciting and promising approaches to front end development was relegated to a nice-toy project status.
If you want to take the risk of building something using Elm’s approach, you’ll do it with Reason. Maybe not 100% as nice as Elm, but you’ll feel confident you’re not risking your code base and company on the whims of one person.
I still think Elm is a nice educational language for getting people (and kids) into FP.
No, it is not. That is exactly the major strength of Javascript! It is an addition to the landscape, for people, who can make use of it.
For everyone else, there is already Javascript "successors", that break with Javascript, but compile to Javascript. But they also are a new language, or, another language. You named one: Elm.
Not so TypeScript. I can gradually upgrade my codebase. And I can reuse knowledge and components, I already have. I am so happy about Typescript. How else could I continue using ES3 on the Windows Scripting Host and have modern Javascript tooling? For me TypeScript is the "better" Javascript. An evolution, rather than an exit strategy.
> TypeScript's major weakness is that it doesn't want to break away from Javascript.
This seems to also be the source of it's popularity, it's the path of least resistance, sprinkling types on top of your existing codebase is a way easier sell.
Also from a business/management POV, it's quicker to migrate JS->TS than JS->Elm and easier to find TS devs (because nobody uses Elm btw) or teach TS to JS devs.
But TS then goes further and layers a lot of really nice features on top of the JS underpinnings. Being able to strongly type string-ly typed things are, again, such a useful feature to have for a codebase that interacts with JSON/JS.
All this said, in my experience `Record` is not very heavily used, but is really useful when you do need to reach for it.
That's what led to the wild productivity and popularity. If you're answer is to just use an entirely different language then why stop at Elm? There are dozens of strongly typed languages available.
Nitpick: In the situations you described, the type is known to the compiler at compile time no? Do you have the same problem with "auto" in other languages?
It might be better to ask what those npm packages are doing, exactly. There’s a decent chance that either the npm package is filling a shortcoming of the JS standard library (e.g. moment) where that shortcoming doesn’t exist in Elm, or an equivalent Elm package exists.
Config it to maximum strictness!
Add in runtime type checks Parse, don't validate https://lexi-lambda.github.io/blog/2019/11/05/parse-don-t-va...
Bring type to business logic, e.g.
RegisteredUser { userId: number, email: string }
AnonymousUser { userId: number, email: null }
User = RegisteredUser | AnonymousUser
Handle errors ala Rust,
Return errors/errortype rather than throws
Utilize structure to determine result, e.g.
Failure { error: Error, value: null }
Success<T> { error: null, value: T }
It is worth it. My experience with TypeScript is a whole lot different.
Conspiracy theory inducing facts:
- LogRocket's main use is monitoring bugs in browser application
- Airbnb is LogRocket's customer
Failure { error: Error }
Success<T> { value: T }
I assume you want value or error not value and error.```
if (result.error) { ... }
```
on a variable that possibly returns { value: T }
It'll complain that result may possibly have no attribute error
type Success<T> = { type: 'success', value: T};
type Error = { type: 'error', value: string };
type Result<T> = Success<T> | Error;
if (result.type === 'error') {
result.error;
} else {
result.value;
}
[1] https://www.typescriptlang.org/docs/handbook/advanced-types.... if ('error' in result) {
console.error(result.error); // ok: result inferred as 'Failure'
} else {
doThings(result.value) // ok: result inferred as 'Success<T>'
}
Even though you used string there, it's pretty type-safe because if you have a typo in your string, the inferred types will propagate other type errors if ('errors' in result) { // typo
console.error(result.errors); // Type error: result inferred as 'never'
} else {
doThings(result.value) // Type error: result inferred as Success<T> | Failure
}What do you expect here? The compiler is doing exactly what is expected.
{:ok, value}
or
{:error, err}
so, you would be correct.
Here's a small example:
```
function fetchCurrentWeather(){ return fetch(...) .catch(() => ({ error: new NetworkError(), value: null}) .then(res => res.json()) .then(maybeWeather => Weather.is(maybeWeather) ? { value: maybeWeather, error: null} : { error: new DecodeError(), value: null}) .catch(() => ({ error: new DecodeError(), value: null})) }
// And then you use it somewhere in you react app
async initialFetch(){ try { if (this.state.isLoading) return; this.setState({ isLoading: true, weather: null, error: null }); this.setState({ weather: await fetchCurrentWeather().then(reault => { if (result.error) throw result.error; return result.value; }) }); } catch (error) { this.setState({ error }) } finally { this.setState({ isLoading: false }); } }
render(){ return ( <div> {this.state.isLoading && ...} {this.state.weather && ...} {this.state.error && ...} </div> ) }
```
I know it is inefficient because creating a new error with a stack trace each time is bad for performance. However, you can also throw errors which do not extend Error to avoid this. Destructuring the output and explicitly checking for errors every time seems to be quite the hassle.
If you know that something truly isn't going to error then you can just force cast it as `foo as Success<T>`. That will still blow up at runtime if its not a `Success`.
Alternatively, you could introduce a monadic chaining that is able to pipe `Result<T>` objects through many functions then handle at the end.
Not mentioned: type annotations serve as a form of documentation that gets statically verified. The verification strategy has some cracks, and there's a (fairly small) chance that the documentation will end up misleading. But API documentation for javascript projects generally sucks, and typescript helps to fill a lot of the common gaps.
You can already write decent docs in plain JS, but I've found that having the types already documented by the compiler helps devs move up that level of thinking automatically.
That said, the appropriate reaction is to laugh louder the longer they make the claim.
It's very likely what's driving adoption right now.
There's plenty of people saying that it effectively augments documentation, and provides a form of it that's quite useful.
Have you ever looked at a compiled js file? Things like spreading objects I to each other
{...a,...b}
End up being a nest object assign which is actually slower than spreading.
And as mentioned by another user, you can choose your compile target to a specific version of JS which does support object spreading.
Whereas on Typescript the compilation process erases all this information, making it impossible to evaluate many such expressions.
There are multiple 3rd party solutions to this, such as: https://github.com/pelotom/runtypes
How does that work? I know next to nothing about the GHC, but in Haskell I would assume some tag is associated with each constructor (probably an integer) for the ADT and that the actual pattern matching gets compiled down to simple comparisons to either that integer or the right function for the more complex cases (pattern matching strings and other values).
TypeScript essentially does that, except that the tags are not going to be optimized by the compiler (they will remain strings if the dev uses them), and the "pattern matching" is just regular conditional checks (if, ternary or switch) with even the exhaustiveness check being done as a manual hack introduced by the dev and checked at compile-time (the else or default case assigning the value to the never type).
As for runtime values, Haskell also needs to validate types when deserializing, although that will be done by the deserialization libraries such as Aeson instead of deserializing and then optionally validating like in TS.
These strings should all be === at least if they are compiled together (the JITter could even intern them to make sure), I believe -- so it will typically be a single 64-bit ptr compare whether it's a string or int. Only in the case where you're constructing the DU tag at runtime for some reason would you need to compare the strings.
> TypeScript essentially does that, except that the tags are not going to be optimized by the compiler
What do you mean by this? Typescript erases type information during compilation. So you would not be able to emulate pattern matching against types on TS. Or you could if you manually added some common tag to every single data type like interface.
Or are you talking about just compile-time type checking on TS?
This is how you emulate them, I took it for granted and neglected to explicitly mention this is a moderately popular convention in TypeScript (those are the tags I referred to). fp-ts for example uses it all over the place.
With this the end result is essentially the same with compile-time type-safety for everything, and compiled down to an untyped binary or an untyped JS blob.
And C, Rust, Haskell, etc., all compile down to native code where you lose all type checking, so what? Outside of compiler bugs, what is statically proven at compile time doesn't change at runtime. (Yes, C also has a notoriously loose and abusable compile time type system, but that's a different issue.)
> Things like spreading objects I to each other
> {...a,...b}
> End up being a nest object assign which is actually slower than spreading.
Yes, if you use the default target (ES3), typescript emits JS compatible with downright ancient browsers which is super portable at the cost of being less efficient than targeting modern JS features, like spread syntax which was introduced in ES6.
You can also set the target to newer ECMAScript levels, including “ESNext”, at the cost of compatibility.
Yes and C still compiles to assembly with no type checking. What's your point?
Feel free to insert Rust or Haskell or whatever your language of choice is in place of C and my point remains the same. Sure you can include type information inside the assembly output and some sort of runtime code to handle that, but that doesn't change the fact that your compile target has no type system. Typescript could do the same if they desired, and in fact I think they offer the option.
Compiling a language with a type system to a language without is not a knock against that language's type system. Compiling typescript's decently comprehensive/expressive compile-time type system to javascript's basic runtime type system actually seems like a step up compared to a lot of other compilation targets.
> Typescript could do the same if they desired, and in fact I think they offer the option.
They very explicitly do not and have stated severally that it is not within the goals of the project to do so, which is something anyone actually familiar with the language should know.
> Compiling a language with a type system to a language without is not a knock against that language's type system
When you don't cherry-pick statements out of context, the point of what I said becomes rather obvious; if a language doesn't actually guarantee type safety at compile time, having a primary compilation target that takes a fascinatingly lax approach to data types is objectively worse (wrt type safety) than having one that's more strict.
Yes, obviously. Almost as though the fact that all languages end up as x86 or ARM opcodes at the end of the day has literally nothing to do with the merits and demerits of their respective type systems, but people keep bringing it up like some kind of gotcha when type systems get criticized.
Do you think that's an opinion I have expressed here?
While true, what's the point of this argument? You cannot get language-level runtime typechecking in the browser. At all. With any toolchain.
Runtime type checks is the lost link between static checks at compile time and arbitrary i/o data type in compiled js code.
You define a type in a similar way you would define TS types. At compile time it would give you the typescript type checks. At runtime it will help you determine if data has the correct type exactly as your typescript app expected.
There is extra work though, handling error if there is a faulty data type
Also iirc you can set the compile target to esnext and it will compile to the object spread expression.
Haskell compiles down to machine code, so...
I disagree. The point to which I refer was stating that because language A with type safety of some degree compiled to another form B without it obviates A's type safety altogether.
The destination isn't the point; it's the compilation of the beginning.
In my 15 years of engineering, exceedingly rare is the case where the underlying cause is due to using wrong types. I could count the number of times types has been problem on one hand. Instead, almost all issues are due to logic errors and missing or poorly understood business requirements.
For me, the time spent resolving TypeScript specific issues (missing type definitions, lack of support in third party libraries, general finickiness etc.) does not make it a worthwhile investment. I also did Scala for several years professionally, and while it instils rigor, I feel much the same that the price you pay isn't remotely worth the benefit you get. Conversely, I think one of the best investments you can possible make into tooling is to reduce the edit-compile-view cycle time, and you should aim for seconds at the most if not instantaneous updates. That speed of iteration will pay dividends.
The thing I've come to realize when using languages with more robust type systems is this:
All errors in code are type errors, but not all type systems are expressive enough to fully express the invariants that logically apply to a piece of code.
That said, in nearly 40 years of programming, I've frequently encountered type errors in both my and other people's code that a type system like TypeScript’s is sufficient to statically avoid.
iirc Webpack uses it extensively in its codebase
Just having the ability to tag a functions param type as `string` is otherwise useless, other than trivial callsite validation, for instance.
That just means you've never used a powerful enough type system. With a powerful type system, it's incredibly easy to make type errors. You'll find that you spend a ton of time fixing all of these type errors that suddenly appeared in your code.
TypeScript is not sound; JavaScript isn’t either. The first one will catch some type errors, the second will catch none.
Syntax highlighting, linting, testing, and now type checking: every step can make you more confident about the code you ship, before it even hits the browser.
You can forgo using any help and probably you’ll code faster, but, again, you lose confidence.
With that being said, I still prefer Flow. Not sure how relevant this is, but at my company we mostly write Flow in the teams. For libraries I tent to provide both (code being written in flow, with with .d.ts provided). I also maintain both 3rdparty ts and flow type defs.
TS tooling blows Flow out of the water, though. But, I've heard flow team at facebook is focusing a bit more on DX and on making the typesystem a bit more ergonomic and expressive. :)
This is the dealbreaker. Building Flow in OCaml was a mistake, full stop.
I'm not convinced the level of soundness we're talking about is important except for the people who like to wax philosophical about PL theory. TypeScript has never been "unsound" in a way I actually care about and I would argue if you get caught up in a soundness issue you're already doing something completely crazy.
I understand OCaml is great for writing meta languages (ha) but it's bad for the end user.
[0] https://github.com/appliedblockchain/parser-combinators [1] https://github.com/appliedblockchain/helpers
I've lost a stupid amount of time in regular JS because of things like this that make me want to pick up the computer and throw it out the window.
You're right that it's not independent from TS.
Unless it's a massive overhead to use typescript, autocomplete, renaming etc seems like they would make it worth it on their own.
Explainer video: https://www.youtube.com/watch?v=iRf9l3Bz7nA
Original paper: https://cs.brown.edu/~sk/Publications/Papers/Published/tgpk-...
Their conclusion that "erasure" is disliked and unexpected is effectively saying the underlying dynamic language's behavior is disliked and unexpected.
Of course it's possible to deliberately or accidentally coerce TypeScript into violating its type constraints and behaving in JavaScript's disliked and unexpected ways, but the survey questions would have to be substantially different, and likely the responses as well.
In my very much opinionated opinion, all the issues highlighted in this article, are avoidable with:
1. Don't use the `any` type (I'm actually pretty shocked this article doesn't talk about using `unknown` instead of `any`. I have found `unknown` can be used where `any` tends to be used, most of the time, if not all the time, and it comes with the benefits of things like assertion control & tsc's type interference (it acts almost as a marker, in some ways, basically, for the type that gets fed through. It also forces you to think about validating the input)
2. Enforcement of strict options for the compiler
A non-goal to me is using TypeScript to avoid writing tests. This should never be someone's idea in adopting any typed language. I think this is a smell of a bigger problem. Types or no types, it does not influence why or how many tests I write, as testing is about validating your programs ability to handle its inputs & the correctness of its outputs (your testing algorithms, at the end of the day), so I do not see how this factors testing at all. Types also do not negate the fact you need to do validation (beyond perhaps, trivial validation of arguments at the call site, if your codebase is actually following good structural patterns)
Is TypeScript perfect? No.
Should you use it? I think you should probably make that decision after a careful reading of the documentation & discussing it with your team.
Do I personally think this article has a strong case against TypeScript? No, as its (to me) very trivial to avoid any of these problems.
You know what would likely be more effective with most code bases I've seen though? Good documentation & comments, with explanations of what things do if it isn't obvious enough to pick up in a second or less glance. Preferably following the JSDoc standard.
The problem here is two-fold:
1. Culture and existing code. Popular libraries use any when they should be using unknown or just not de-serializing stuff and making you explicity parse it yourself. The reason why these are popular is because culture and because of #2.
2. Creating type guards is a pain, especially if you're trying to go from unknown to something with a complex structure. If types were something you could reflect on in run time, like most other interpreted languages, this would be a lot easier.
Creating type guards might be a pain, sure. I think assertion functions will go a long way in this regard
https://www.typescriptlang.org/docs/handbook/release-notes/t...
I also think that its an overestimated pain. If you are using interfaces to type your objects (commonly, this seems to be the pain point) then I don't think its terribly difficult to construct type guards around that.
There is also the reflect-metadata polyfill (that I think TC39 is eventually going to make a standard) for run time type information. Using decorators could be valuable combined with `emitDecoratorMetadata` option
here's a less well known, more rigorous study: http://earlbarr.com/publications/typestudy.pdf - To Type or Not to Type: Quantifying Detectable Bugs in JavaScript
TS introduces some friction for setting things up, it slows you down in initial development, increases LoC, etc. I do like the type documentation in function signatures, the autocompletion and the refactor support, but I think the project needs to be of a certain size for the cost/benefit trade off favors TS, typically a project that goes on for 6+ months and with 2 team members or more. Anything smaller than that and I wouldn't choose it myself.
In my experience TypeScript's unsoundness is a feature, not a bug. You cannot compare it to languages that were designed from the ground up to be sound. JavaScript was fundamentally not designed to be used in a 100% sound way, and trying to do so introduces an enormous developer burden in terms of idioms and language features that you can't use, and syntactic hoops you have to jump through. You find yourself constantly fighting against the language.
I know this because at my company we started with Flow, which is (very proudly) sound. It was impossible to be productive. For a small example, every reference to document.body had to have a null-check, even though we could guarantee by where we placed our <script> tags that the body would never ever be undefined. The experience was rife with situations like this, which created tons of cruft and made code harder to read.
"any" is nearly always bad. But the other escape hatches, like casting, have proven essential to building a reasonable codebase that doesn't try to ignore the reality of JavaScript.
> Runtime type checking would be beneficial when dealing with JSON payloads returned from API calls, for example.
This I agree with, but I don't think it belongs in the core TypeScript project. We used Flow Runtime with Flow (https://gajus.github.io/flow-runtime/) which creates runtime assertions automatically based on your static types, on a file-by-file basis. It was wonderful, and is the only thing I miss about Flow, although it was a third-party project and there's no reason something similar couldn't be made for TypeScript. I would be giddy to hear that someone was building one.
2nd it's more convenient for both the end-user and the developers that errors are handled this way
interface A {
x: number;
}
let a: A = {x: 3}
let b: {x: number | string} = a;
b.x = "unsound";
let x: number = a.x; // unsound
a.x.toFixed(0); // WTF is it?When it comes to typing systems you have to pick 2: Depth, Mutability, Soundness
No, but not because the tired diatribe on typing, but because JavaScript nowadays is mostly good enough... As long as we remember what its goal is and we don't use it for anything more than its intended purpose.
That aside, everything in that post re soundness vs unsoundness was what I used to know (and was taught in college) as "strong" and "weak" typing, is the redefinition recent? Was I taught incorrectly? That aside, C is weakly typed (and there are long, long discussions on it) but I'll say that, if it's good enough for C, then being for or against it becomes pretty much subjective (obligatory IMO, my saying that at all is also subjective).
After all, it's a flamewar that's been going on for decades. Typescript certainly isn't going to solve it.
Type systems become unsound when they, as a total system, cannot uphold their own promises. The promises of a weak system are weaker, but not (always) non-existent. In an unsound system you can generally, through trickery, take any expression reducing to a value of some type and modify it so that it appears to reduce to any other type at whim.
I see, but at least the literature I read on typing actually mentioned them. Nothing I read in college regarding type systems ever included the words "sound" or "unsound".
> some type and modify it so that it appears to reduce to any other type at whim.
I saw instances of this as examples of "weak" typing.
Do you happen to have papers or books you could recommend so that I can learn more on type system soundness? This is a subject that I find really interesting.
Anyway, more on soundness. I can recommend a couple papers:
Wadler (inventor of the monad) wrote a paper called "Well-Typed Programs Can’t Be Blamed" [0] about soundness.
There was a recent paper that made the rounds about how java's type-system is provably unsound as well that's worth reading [1].
I second the parent comment that weak and strong are ill-defined in my experience, while sound/unsound have well-accepted definitions and are commonly used in academia. Your experience obviously differs for whatever reason.
You can find many other references to soundness in the citations of those papers, as well as scattered around the internet.
[0]: https://link.springer.com/chapter/10.1007%2F978-3-642-00590-...
Static: Statically checked by a compiler.
Dynamic: Not Statically checked by a compiler
Strong: Types enforced by the runtime
Weak: Types not enforced by the runtime
Of these,
JavaScript: Dynamic, Weak
TypeScript: Static, Weak
C#: Static, Strong
As to if you think TypeScript is worth it, of course its worth it. Whether or not you're writing JavaScript or TypeScript, you're still thinking in types, function signatures and data structures. TypeScript allows you to encode that information so you don't need to hold it in your head (or have other programmers figure it from usage, as would be the case for JavaScript). But that aside, TypeScript's tooling alone makes it worth it.
As for this flame war, i strongly suspect that most type system advocates have mostly likely used BOTH dynamic and static languages (C#, Java programmers have had to deal with JavaScript for decades), while I expect the majority of dynamic advocates (say Python, PHP, JavaScript) have primarily stuck with dynamic.
Those with the wider perspective have the better insight imo.
JavaScript is dynamically typed. This allows you to to do funny things like [1,2,3] + ",4" Because it's strongly typed, all values will be converted to a type.
JavaScript does type checking during compilation, but it's not considered a static language, because it allows "duct typing" meaning you can add or remove properties and methods on objects during runtime.
> Whether or not you're writing JavaScript or TypeScript, you're still thinking in types, function signatures and data structures. TypeScript allows you to encode that information so you don't need to hold it in your head
Static vs dynamic, a flamewar that's over sixty years old to which I am not going to contribute.
> But that aside, TypeScript's tooling alone makes it worth it.
JavaScript has pretty good tooling too.
> As for this flame war, i strongly suspect that most type system advocates have mostly likely used BOTH dynamic and static languages (C#, Java programmers have had to deal with JavaScript for decades), while I expect the majority of dynamic advocates (say Python, PHP, JavaScript) have primarily stuck with dynamic.
Should we ask LISP developers? How about its derivates like Clojure or Scheme? There are many of them here in HN (in fact, I've dabbled on Clojure a little bit myself), though that would probably lead to a different flamewar entirely (functional vs oo).
In any case, I don't follow what point you were trying to make writing this suspicion, it was clear that you were going to get counterexamples and you didn't provide sources that at least gave support to having such a suspicion. I would appreciate if you elaborated what was your implication.
By the by, I worked in java during 2013 and 2014, and I've been working with Go exclusively for nearly a year now. I still don't really care about what type system any given language I've worked on has, there's good and garbage code everywhere. Though, since all this can only be opinion until someone conclusively demonstrates a given type system's superiority, I still prefer writing python code.
> Those with the wider perspective have the better insight imo.
Maybe we should ask Guido van Rossum's perspective.
I believe most of the best in class tooling for JavaScript borrows on leveraging TypeScript compiler for inference. And its still below the bar offered by TypeScript.
> Should we ask LISP developers? How about its derivates like Clojure or Scheme? There are many of them here in HN (in fact, I've dabbled on Clojure a little bit myself), though that would probably lead to a different flamewar entirely (functional vs oo).
OOP is not at odds with FP. They are different things entirely and can be leveraged in equal measure within a codebase. Consider C# and LINQ (with LINQ derived from lazy expressions in Haskell). As for LISP, TypeScript services as a good test case for layering dynamic languages with type systems, so why not TypeLISP?
Anyway, the point im trying to make is, irrespective of if the programmer is working with a type system or not, the programmer is still reasoning about software with types. The dynamic language programmer is still thinking about function signatures (arguments, return types), they are still thinking about 'the properties of some object' and they still think about generic abstractions (as in .map()). I don't understand why JavaScript programmers are adverse to encoding that information in a type system when it solidifies and communicates their intent. (both to other programmers as well as the compiler)
JavaScript on its own requires the programmer to infer the original developers rationale from usage, and assertions of 'correctness' can only truly be inferred by running a software (by test or otherwise). Obviously, both TS and JS need tests, but in the TS case, you've removed a whole class of issues around types and call signatures, where as in JS, one might be compelled to test both 'types' and 'logic'. A type system can at least narrow huge classes of problems, allowing a programmer to focus on testing logic, not the inadequacies of their (or others) brains to hold mountains of implied type information in their heads.
If you want one practical example..... refactoring. While JavaScript is dynamic, just change the name of a function, or move functionality elsewhere...what assertions can JavaScript or its tooling provide that all dependent code is appropriately updated as a result of that refactor?
Honestly, choosing to leverage of a type system, imo, is an open admission of the complexities of software, and the fallibilities of the human brain. Rejecting the benefits of a type system to me demonstrate a dangerous over-estimation of one own abilities, or a lack of personal introspection with respect to reasoning. While this debate seems to continue, I don't think my views on it ever will.
Lisp users think that a 'dynamical' and a 'dynamically typed' language are two different things. Lisp is both. Being 'dynamically typed' supports or simplifies a lot of dynamical features.
There are a bunch of languages which implement a subset of Lisp features and which were adding type systems. Historically much of typed FP was developed out of Lisp tradition.
Common Lisp for example has CLOS definitions like:
(defclass person (living-thing)
((name :type string)))
So the definition makes clear that are a class/type which inherits from the class/type living-thing. It also defines that contents of the slot name are of type string.A CLOS method then is:
(defmethod say ((from person) (to person) (m message))
...)
Which means that an object of class person sends to another object of class person an object of class message.Multi-methods in CLOS allow us to define and dispatch on the class of several arguments.
Common Lisp then also has type declarations:
(defun collide (a b)
(declare (type moving-object a b))
...
)
This allows programs to document types, compilers to use types for optimization purposes and some compilers to do type checking.What Common Lisp lacks is a more extensive/expressive type system which would be competitive with what Haskell/or similar would offer.
You don't need to tell me that.
I'm a bit shocked that you haven't seen a discussion on the "virtues" of functional programming and "evils" of object oriented programming (and vice versa) here. If you truly haven't seen it, you might soon enough.
> Anyway, the point im trying to make is, irrespective of if the programmer is working with a type system or not [...]
Everything after this is one of the arguments in favor of static typing, and I've read variations of it for years. There are just as many, and just as old, counterarguments.
I wish there was a way to conclusively prove which type system is truly better, if only to put that discussion to rest and for the industry to converge upon that type system. And then we'll be able to focus on other things.
Until then, this is mostly a matter of opinion, which is why...
> I don't think my views on it ever will.
... Neither will almost anyone else's, there's no absolutely compelling reason to.
Soundness = Progress & Preservation.
From Pierce's Types And Programming Languages:
"Progress: A well typed term is not stuck (either it is a value or it can take a step according to the evaluation rules).
Preservation: If a well typed term takes a step of evaluation, then the resulting term is well typed."
There are not degrees to Soundness and it is a well defined term.
That doesn't mean that there's no way to give useful meaning to "more sound" or "less sound".
Consider the following family of languages:
L has a sound type system.
LD (D for "dynamic") is L plus the ability to escape from it, in specially marked "unsafe" sections of code, into a dynamically typed system. (So evaluation can produce runtime errors, but they can be handled safely.)
LC (C for "crash") is L plus the ability to escape in "unsafe" sections into a system like Ld's except that now type errors aren't checked for and caught at runtime, they just produce memory-stomping and buffer overflows and the like.
D is just LD's "unsafe" language.
C is just LD's "unsafe" language. (Any resemblance to actual real-world languages named C, living or dead, is purely coincidental.)
The only one of these languages that is sound is L, of course. But there is some quality closely related to soundness by which there's an obvious partial ordering where L > LD > D, L > LC > C, LD > LC, and D > C, and if someone describes this situation by saying that LD is "sounder" than C then I don't see any reason why we should stop them.
Similarly: a federal state is either a "perfect union" or not, for any reasonable definition of "perfect", but it's reasonable for the US constitution to aim at a "more perfect union". A thing is either "unique" or not, but despite the cries of pedants it's reasonable to describe a limited-edition Ferrari of which only two were ever made as "almost unique" and as "more unique" than a standard-issue Ford Fiesta. A given region of space is either "vacuum" or not, but most of the things we describe as "vacuum" actually contain some very rarefied gas and it's common to use terms like "high vacuum" to quantify how vacuum-like a "vacuum" is.
TypeScript is a superset of JavaScript, so it brings a lot of baggage along with it, but if you're writing frontend code it's a real pleasure to work with compared to JS.
Still, for me, TS is a day and night difference with JavaScript. While it does not solve everything, at least I know what is code about. E.g. when I see `volume` in plain JS the first question I ask myself is: is it a number, plain object, instance of a class, a simple boolean, or what?
It is not only about tests. It is also about readability.
Especially if you avoid making everything global.
A great many developers have found themselves having to write JavaScript because that's where they got pigeonholed. Then, because they'd rather be doing Haskell, Java, or whatever, they bring that crap to JS and enforce it on developers that possibly liked doing regular JS.
Type inference is older than I am. Yet you would think this is some new technology. When in reality, we could have had it on Ruby. On Python. On Perl. And on Lisp. Decades ago. It's not new and it's not special. But it certainly has a lot of undeserved hype at the moment.
There is no conclusive proof static typing reduces bugs. This is some weird myth that keeps circulating. The trend pendulum will continue swinging though. TS won't be immune.
In reality, the reason TS has been so successful, and likely will continue to be so successful (until they screw up the execution, or forget what made the language so successful) is thst TS is an entirely pragmatic language. It uses theory not to decide its roadmap, but rather, uses actual problems and needs faced by JS developers to decide the roadmap, and then uses type theory and other CS concepts to solve those problems.
For example, the reason TS uses Types is not because a group in MS decided that static typing is better than dynamic typing, and what JS really needs is a layer of types above it (that was coffeescript...). Typescript was created because MS wanted to make better IDEs and tooling for JS (specifically, they wanted to bring Intellisense which is a huge VS selling and marketing point, to JS) and they realised they couldn’t do that without adding type information to JS.
Hence, a practical need drove the decision to create TS.
So the IDE stuff isn’t extremely advantageous for me.
But that’s the reason Typescript was created.
A convenient side effect of that for me is that I can manually use the same clues an IDE used to understand the code base I’m working on easier, faster and with more confidence than I would be able to in a vanilla JS project.
This alone gives me a huge productivity boost and I can't imagine working without it. It just so happens that anecdotally I see runtime errors inversely frequently to my discipline with using hints (go > python w/ hints > python w/o hints).
I like having some wiggle room for rapidly hacking stuff out. interface{} and Any are nice in small doses. Namely as parameter types and only when I'm the only one using the code and just need to ducktype my way through something.
Look, I wish TS type system was sound too, but that's not a "proof", it's just a data point. TS doesn't have the same goals as Dart, there are lots of variables which aren't the same, so you can't just take Dart's approach and say "look I proved it". There are lots of popular languages with unsound type systems; Java for example.
I don't know how you could have a criticism of TS without mentioning the lack of sum types. Yes, you can fake it by having an object with the same field name differentiated by a string constant, it's still not as nice as having first class support for sum types.
https://medium.com/javascript-scene/the-typescript-tax-132ff...
It's apparently the second fastest growing language based on whatever metric the chart is using and I've never heard of it.
I wrote a blog post recently about my experiences learning and using TypeScript as both an app dev and a library maintainer for Redux:
https://blog.isquaredsoftware.com/2019/11/blogged-answers-le...
As part of that, I listed some of the tradeoffs and takeaways. In particular, I strongly recommend a "pragmatic 80%" approach to tackling TS.
Except this time, this particular assault on ECMAScript is much better funded.
Don't get me wrong, I think there's a place for strong typing in web based UIs. But for one, WASM is a better idea for the choices and flexibility it offers, and more importantly to me, I think the bigger challenges in UI programming are not solved by strict typing.
The trade-off for dynamicness is more productivity and (potentially) easier-to-read code because less code is needed on average to express an idea since type-related code isn't needed as often. But if TypeScript is not type-safe enough to reduce the need for fine-grained testing, then you get the worse of both worlds: the verbosity of types and the busy-work of micro-testing.
That being said, I wish JavaScript would add a feature to allow optional parameter checking such as:
function foo(a:intReq, b:datetime, c:datetimeReq, d) {...}
Here, the "Req" ("required") suffix means the field can't be null or white-space. Parameter "d" has no type indicator. It's still "soft" type checking, but would catch a lot of problems earlier.
Yes, it's not perfect. And, yes the inherent problems of javascript still leak through in many places. But with a little configuration, you can make the static code analyzer and compiler help you avoid most of that.
IMHO the amount of verbosity it adds is negligible. I actually measured a slight decrease in LOC when I was converting code. You add some type annotations (which IMHO also help human code readers) but then you also get access to syntactic sugar to offset that. It's a fair compromise considering that most tools trying to help the developer have more to go on and basically work much better. Ballpark your number of LOC is not going to grow when switching to typescript.
But IMHO it's just a gateway drug for developers to find their way to more capable languages. Personally, I'm interested in the direction that Jetbrains is taking with Kotlin-js, which with the next version will have some nice tooling around it to leverage typescript type definitions for integrating npms and hides most of the build tool madness behind gradle. The kotlin compiler has a much more sound/strict typesystem than typescript and also the language has quite a few language features that the typescript developers have not gotten around to supporting yet (they've been making progress catching up with recent releases though). They are also addressing size of the minified code. With the recent improvements, hello world is now a lot more reasonable than it used to be (<80kb instead of ~1MB). It eliminated unused code more effectively.
If you feel brave, you can get started with this on a modern react project right away. Most of the dependencies you'd use for that should just work fine with kotlin-js. But realistically it's probably going to take another few years to stabilize. IMHO Kotlin is interesting because it is already widely used by frontend developers on Android and has a lively ecosystem of frameworks that might also make sense in a browser. Also co-routines are nicer than async await in typescript.
And of course WASM is opening up this space further to just about any language. So for people finding themselves a taste for more/better typing, there will be plenty of alternatives for typescript.
Folks seeking a Haskell-like type system in javascript certainly have options.
// unsound, val.toFixed(0) compiles
let val: number = ("string" as string | number)
vs. // sound, val.toFixed(0) does not compile
let val = ("string" as string | number) // sound
In general, it's unnecessary (and as demonstrated, potentially harmful) to annotate assignments like these.The `: number` isn't a cast either, it's declaring the type of the variable.
You can't help but explicitly name the type of a variable in many cases. What if they were passing val to a function as an argument? You can't have 'function foo(x)', you have to have 'function foo(x: number)' and name the type.
So yes, their example is contrived, but no, what you said is not sound. It has the same problem as the one the author mentions. The typescript compiler intentionally does not catch their issue.
If I'm misunderstanding what you're trying to demonstrate, please make a typescript playground of it.
It is nowhere close to important enough to fuel the question of 'Is TypeScript Worth It?'
With the precision ML requires, why aren't data scientists rushing from Python, to a language with a stronger type system?
Do people try to write a core in simple (sound) types and then have an outer shell of IO/frail typechecks around ?
I use TS to write components and JS to write the app using those components.
Which outside of its natural realm of application (i.e. the browser) is a highly questionable value proposition.
"A bandaid over a machine-gun wound" is what we might call it.
For context, I base the above sentiment on our team's last two years of effort in migrating key parts of our app to TypeScript (25 devs, ~500k lines of code). We've found that it has substantially reduced the number of defects that make it into production, and it has also reduced the number of round trips through QA. Clearly, this is only one data point, but hopefully it offers some perspective on why TS can be valuable in a codebase.
TS/JS don't allow for overloading the way that other languages do - it does not allow you to differentiate between functions via number of parameters, only types of parameters. If you try to differentiate based on number of parameters, it will simply use whichever definition of that function name it finds first.
Declaration file style:
---
// add.d.ts
export declare function add(a: number, b: number): number;
export declare function add(a: number, b: number, c: number): number;
export declare function add(a: number, b: number, c: number, d: number): number;
--- // add.js
function add() {
switch (arguments.length) {
case 2:
return arguments[0] + arguments[1];
case 3:
return arguments[0] + arguments[1] + arguments[2];
case 4:
return arguments[0] + arguments[1] + arguments[2] + arguments[3];
default:
throw new Error("Too few or too many arguments. Number of arguments: " + arguments.length);
}
}
module.exports = { add };
--- // adding.ts
import { add } from "./add";
console.log("4 + 4 =", add(4,4));
console.log("4 + 4 + 4 =", add(4,4,4));
console.log("4 + 4 + 4 + 4 =", add(4,4,4,4));
---(Gist: https://gist.github.com/Robert-Fairley/98a2da3f0361e524f4e05...)
It will default to the first function name, but it definitely allows for overloading the number of parameters.
It's not perfect as you do still have to implement the function as it would need to be implemented for JS
This might be news to you, but JS is full of types regardless of whether you document and automatically check them.
I prefer to document my types and have them properly checked, plus the IDE advantages.
But what about programs that can run fine but are excluded by the type system? They are censored. This is what's less visible until you actually try to build something substantial, pushing the boundaries of expression, performance, or scale. Then you'll find that a sound type system can become more and more of a straight-jacket. One that forces you to write code in ways that are limited by what its type designers could envision expressing (or could prove that was safe to express).
The trouble is that it's hard to know when you might reach the point where the type system begins to limit you. It might never happen, as you frolick happily within the walled garden. Or it might happen when you write your first line of code. In a large system that must deal with external requirements, it's something that in my experience becomes inevitable. That's not even touching upon comprehensibility, which becomes an increasing challenge for purely sound type systems as they attempt to increase expressiveness.
The biggest innovation of TypeScript is JavaScript. That is, TypeScript started with a huge established corpus of JavaScript that showed what developers wanted to express and how they wanted to express it. And, crucially, how popular various kinds of expression actually were in the developer community. This forced TypeScript to take practical expressiveness of millions of lines of JS seriously, that ordinarily would have been censored by type system design before the first line of code was written.
That's why it's entirely unfair to harp on TypeScript's unsoundness, without also exploring the corresponding gain in expressiveness and comprehensibility relative to other type systems.
BTW, this line of reasoning is the reason so many "sound" type systems also have their equivalent of "any": Rust has unsafe[1]. C# has unsafe[2]. Flow has any[3]. And all practical languages have strings, which are kind of a lowest common denominator when it comes to dealing with a type system that simply can't express what you need.
[1]: https://doc.rust-lang.org/book/ch19-01-unsafe-rust.html [2]: https://docs.microsoft.com/en-us/dotnet/csharp/language-refe... [3]: https://flow.org/en/docs/types/any/
Converted it to TS. In the process of fixing up types, it found bugs for us. Value demonstrated
Why? The answer to the question is utterly obvious, and that one word is really all you need to answer it.
Non-typescript users: ANOTHER layer? /sigh/
Cynical, yes, but that's because I haven't had a smooth onramping experience the times I thought about switching. Would love a recommendation.
Moving an existing codebase to it is a giant pain, otherwise it's not bad.
It fits you into better practices, but don't treat it as types are always true. When debugging issues I'll commonly assume Typescript's types do not exist or how it would act if they are wrong. This is notably true of consuming API responses with mixed data types.
Generally, what I really like about typescript is that you normally blend it with linting, minifying, and strong IDE integrations. These things, when combined, really help code readability and adherence to TS conventions and eventually save you from massive bugs that might not have been caught prior. Obviously, at the end of the day, how you use it really defines how helpful it is.
All that said, I haven't ever really had major issues with the tools around TS.
Then I started working on my own and decided to use Node a lot in the situations where I would have to use c# at my previous job. There were no more bugs, no problems with refactoring, and it was much more enjoyable than writing c#.
Now this TypeScript is infecting javascript, and all the jobs and so on require "typescript" instead of javascript. It puts all the things I didn't like about c# back into javascript. People will make all kinds of ridiculous claims that it is irresponsible not to use TypeScript, and that it improves productivity and so on. None of these claims are true and don't get to the real reason that TypeScript exists.
Most well-structured programs aren't one giant function that is hard to refactor. They are usually a series of isolated, small functions. There is even a trend towards "microservices" and function as a service and so on. These are things that reduce the claimed reasons for TypeScript, rather than increase it. Many times I see the reason for using TypeScript being "it is good for large teams". Yet still, I see it used in teams of 3 people all the time.
The real reason TypeScript is used is because there are a very large number of programmers now, which means the quality of the average programmers is much lower than it has ever been. Many people who program often just copy and paste code from stack overflow. Many of these people were the same people who looked in fear at JavaScript, and couldn't even understand it because it wasn't the one language that they knew. TypeScript gives these people more safety, feedback, and familiarity to be able to do their job.
That is fine, but lets not pretend it is anything else.