The author didn't use and understand something, and rather than trying to they instead just defaulted to rejection. It's midly disapointing seeing this in people who label themselves as "Senior".
The author didn't use and understand something, and rather than trying to they instead just defaulted to rejection. It's midly disapointing seeing this in people who label themselves as "Senior".
type Thing = {
name: string,
};
function getThing(): Thing {
return JSON.parse('{"error": "invalid id"}')
}
const thing: Thing = getThing(); // lie to me!
console.log(thing.name);
This is a trivial example to illustrate the idea that it's relatively easy to make a type that cascades through a very large app but which is backed by a complete lie.I definitely think that there's also something to be said about meta-programming yak shaving (e.g. people wasting hours trying to write a "function" that takes an enum and outputs another enum w/ a fixed prefix, when simply writing out the enums would've taken 30 seconds w/ VSCode's multiple cursor feature)
Personally, the value I see in TS is in cementing documentable facts about a system: e.g. such and such function takes such and such well-defined entity, dot not treat this `options` argument as a generic bucket for pass-through garbage.
Typescript isn’t perfect by any means but it does a good job keeping code based easier to work with in a larger team. Much better than vanilla JavaScript
I'm working through the very lie you're showing now because the disclipline wasnt established from the start to avoid these anti-patterns. Typescript isnt the perfect magic bullet that will disappear all your problems - you still need to have good developers writing good code, but TS will make it easier.
Very true. Typescript is still immensely helpful in the sense that it will complain if you pass a Thing to a function expecting a Foo, regardless of the fact that Thing is meant to be a thing but is actually an error.
But IMHO, by sticking to a decision to be lean in terms of transpiled JS, TS suffers greatly from cases like the one I showed. It would be great if the compiler enforced that these types of unsafe casts had to be explicitly specified (even if via some strictness flag), and even better if it could do a better job at refining types through runtime constructs like `if (typeof thing.name == 'string')` (it does do it to some limited extent, but not enough to cover many common scenarios, due to the curse of targeting Javascript, as you mentioned)
function isThing(value: unknown): value is Thing {
return typeof value === 'object' && typeof value.name === 'string'
}
The `unknown` type in general is still pretty new, but will be greatly helpful for cleaning up a lot of code that assumes a type or used to use `any` or `{}` because there wasn't a better type to use in that situation. The Typescript team is hesitant to change the last few cases of `any` in lib.d.ts to `unknown` directly (such as `JSON.parse`) because that would be a big compatibility breaking bug. There was a proposal for a flag to do that (treat `any` as `unknown` in lib.d.ts), but I'm not currently aware of whatever happened to that idea. I would love to see it or something like it happen.Within Google we include a "strict" d.ts in every compilation that shadows this API (and one other) with one that instead returns "unknown". We have talked about going through the whole TS API and removing all the 'any' in this manner for this reason.
https://www.typescriptlang.org/play?#code/FDDGHsDsGcBcAJrwLz...
(I still think this is actually due to bug in TypeScript, see https://github.com/microsoft/TypeScript/issues/31990 .)
I don't know of any language that can stop someone from being a sloppy coder.
The code doesn't even throw runtime errors, but you wouldn't be able to tell something is wrong unless a) a request providing the json string actually responded with the error string in the example (an edge case that is very easy to overlook during regular development) and you managed to catch the problem in tests, or b) you specifically went digging into the file that defines getThing looking for the implicit any cast that the compiler didn't tell you about or c) there's a compiler update that turns that any cast into an error
What others are saying is that Java and friends do c) (throw compilation errors when implicitly casting between incompatible types)
[1] You can implicitly do unsafe casts using arrays, but arrays aren't really using generics, they're a special-case builtin.
It'll just return an empty Thing right? No ClassCastException.
The happy medium we've found with that module is using the runtime type-check on anything "unsafe" to bless the result using typescript-is's equals functionality, but still allowing programmers to use casting with a comment justifying its necessity. For us our list of unsafe is results pulled from the db, anything parsed from JSON, and incoming request bodies (which can be a special case of parsing from JSON, but not always).
* Zod: https://github.com/colinhacks/zod * io-ts: https://github.com/gcanti/io-ts (for the more functional-programming-oriented)
I hadn't heard of Zod before, but i really liked the "parse don't validate" blog post linked. Turns out that's exactly how we've been using typescript-is. But it still suffers from the problem of expecting to be the source of truth and thus not working well with generated types.
I forget why Zod didn't pass muster for me now, but I've looked at it before and didn't find a compelling reason to jump to it. Thought it seemed good, though.
I really wish at least one of these let you emit JSON schema back out, though. That'd be way easier for dealing with stuff like OpenAPI.
A downside to this is that you need to compile type changes before you can use the change in code, but it gives us a single source of truth for our type definitions and let's us generate a more exacting request and response verification functions that use the (sometimes) more precise open-api definitions (eg a an array with a maximum number of elements and specific string formats), and it's really easy to add a run-time type check to the result of calling into a 3rd party library as well.
For runtypes, you could write a script to use its Static function to spit out the interfaces in pure typescript and then use something like typeconv or ts-to-openapi to make your openapi definition, and then use that script inside your build script to output the openapi schema.
If you've got an example I'd love to see it. I've long been not a fan of OAS-first, but I'm always down to learn something new.
So the way we do it is a little meh because we have two separate run-time type validators, but it's because of the path we took where we adopted request and response validation almost from day 1, and other runtime validation came much later.
We're using express and express-openapi as the base of our project. Express-openapi has built-in request validation and type-coercion, and an example of how to quickly bolt on your own response validation, so for request and response validation we're using that. And it works. It's damn solid. But the docs for how to use the underlying tech to do similar run-time validation for say pulling values out of the db is lacking and I don't have enough expertise in the underlying tech to stand it up quickly, especially compared just dropping in typescript-is changing tsc to ttsc and adding an tsc plugin to our tsconfig.
If I were to do it again I'd either bite the bullet and spend the time figuring out how to get express-openapi's request validation infra to also do general type validation on just the schema, or i wouldn't use their run-time validation at all and just make my own middleware to do it, just so that I've only got a single run-time validation library. The nice thing about express-openapi is that since it "knows" the open-api definitions it can programmatically apply the types needed for validation, so rolling my own would require that functionality, because I don't want our team to have to remember to add the same middleware to every endpoint using a factory function that takes the typescript-is validator function as an argument, and the nice thing about typescript-is is that i can give it a typescript type and get a validator function exactly where i want it with no muss.
Anyway, our build looks like this: 1) compile typescript 2) compile the openAPI to a single file (we put the endpoint definitions in the endpoint file) using express-openapi (just initialize and then write out the JSON.stringify of the apiDoc field) 3) run dtsgen against the output of step 2, and pipe that through `sed 's/declare//g' | sed 's/namespace/export namespace/g'` and into some file.ts 4) compile the file from step 3 with typescript
step 1 gives us typescript compiled to js step 2 gives us a single openAPI.json file we can pass around or import into postman step 3 gives us all the types from openAPI in a ts file with a bunch of recursive exported namespaces step 4 gives us those types in a .d.ts file
We do 3 and 4 in a subdirectory so that we can publish the types as an internal package, which means we can give front-end a types package to consume for all the calls we want to make to backend. Well, we actually publish an sdk package that wraps the types and encapsulates the logic for making the HTTP request to the right endpoint, so we don't have to worry about calling POST when the endpoint is PUT or fat-fingering the response type cast through the entire front-end and can instead just call a function with well-defined types. Plus, now it's super easy to test the interface, because we have a single thing we need to test. So we write an exhaustive happy-path (ie 2xx status codes) integration test against the sdk that actually calls into backend gets the response and checks the response against the type and then the values we expect for each field.
- For every call to an API, or input from the user, there should be a few tests checking for unexpected data
This rule alone forces us to fix the majority of runtime errors such as this example (since JSON.parse() is usually called when handling an API response)
And because this "any" is coming from an external definition file, it can't be caught by tsc's --noImplicitAny nor eslint's no-explicit-any
Those two rules allow us to protect us from our own code, but not external definitions/libraries. Relevant GitHub issue here — https://github.com/microsoft/TypeScript/issues/26188
> Create a rule which uses type information to determine when you're inadvertently breaking type safety by using an any, potentially without knowing.
> Often libraries (or even the typescript defs themselves) can have weak(/lazy) types which return any. If you're not careful, you can inadvertently introduce anys within your codebase, leading to bugs that aren't caught by the compiler.
Metaprogramming shouldn't suck so much that this takes hours.
also, i think "avoid typing functions [...] and let their return types be implied" is terrible advice. argument and return types are the thing you should type explicitly, because it makes the errors local. otherwise you'll get all the fun of tracking down where that `null has no property "blah"` error came from, just at compile time
Being able to encode/decode IO safely has been a huge blessing when working with JSON.
So Zod it is. I like it.
It will make it a lot harder to return wrong types from your calls. Personally, I like gRPC + Protobuf.
A good application can be written in any technology, just like a bad application can be written in any technology.
Certain technologies will try to guide you to a better place, but it's up to developers to take advantage of it. Or to do crazy stuff like the above. . .
You can implement the JSON parser to be type safe. For example, this is how nest-dto can prevent this.
(edit: red flag is a bad description, because he ended up being self-reflective and looking deeper, which is just about the furthest thing from a red flag.)
Then Java is a verbose language in general (forced directory structure, one-class-per-file and yadda yadda) but that's besides the point as you're pointing to TypeScripts types being less verbose than Javas.
2) TypeScript has type aliases, meaning you can write `type MyTable = List<Map<string, number | string | Foo>>` and not have to repeat yourself everywhere.
3) TypeScript has anonymous unions and tuples, so instead of writing `Triple<Integer, String, Foo>` you can just write `[number, string, Foo]`, and likewise instead of `OneOf<Integer, String, Foo>` you can write `number | string | Foo`. These are also built in and very ergonomic, so you don't have to write a OneOf4 class whenever you want to have one of 4 things, and you don't have to write helper methods to convert between different sized OneOfs.
But... teammates were running in to issues with 'new' stuff - like, using 'var'. There were others, but this was sort of the archetypal argument. "Well, it doesn't match the rest of the style of the file/app - it'll make it hard to read". Well... hrm... any new feature literally doesn't match the style of what came before it, because you have new keywords/features/syntax to handle processes a new way. It's definitional. But just because there's a new feature doesn't mean it'll be adopted (for better or worse, I supposed, depending on your stance).
(That part has also gotten better, but especially the Java 8 -> anything later upgrade has been extremely painful because of modularization. Past Java 9 it usually isn't that much of an issue. However, JDK 11 was the first LTS release after 8, so most shops basically delayed the transition until JDK 11.)
VerboseTypeName foo = new VerboseTypeName();It wasn't until I started using Haskell that I saw the benefits of knowing exactly what I was working with. It also helped me be more deliberate and thoughtful as you had to define everything up front.
No, red flag is fair since it's just a warning sign. Even if we take it as a mark on the guy, it's true that he ought to get outside the scripty bubble.
or idk just say that no, its the kids that are wrong.
Front-end fads are fickle. At least TypeScript has Microsoft backing it so they'll likely continue to support even if the rest of the webdev world moves on to the next shiny object.
Typescript, meanwhile, is used for hugely popular apps, is used by very large companies, has Microsoft's full backing, etc.
The two are not comparable.
Edit: Clearly I'm misremembering its popularity.
JSX is kind of xml done "right". Differences between children and attributes in xml is very hard to grasp, but JSX has a much better defined structure to it and is super well suited to generate html because of that.
We ported off of Coffeescript when ES6 and Babel came out, and you could start writing `class` and using `() => {...}` function expressions with lexical `this` scoping.
Coffeescript lost because it stood still next to the ecosystem and next to the new Ecmascript language standards, which obliviated its advantages. The only advantage Coffeescript had over ES6 was “some of your code is already written in it”.
Once Ecmascript formalizes a type system, we’ll see the same process happen with Typescript. But, I think it will be many years before that sort of feature is standardized. In the mean time, I’m happy to use Typescript today.
Took over a year to convert to ES6.
JavaScript was a minefield of compatibility problems. Many intentional, cough: Microsoft.
Jquery was awesome because it dealt with most of that
Maybe eventually with WebAssembly...
It will still be a while before all the brownfields in the larger ecosystem get cleaned up (if some of them ever do), but that's no longer a language problem, that's a a long tail ecosystem problem.
Is scoping an issue for you? Just use arrow functions!
Is callback nesting an issue for you? Just use `async/await`!
I love it so much.
Sparse arrays, all object keys are strings (or symbols), crazy coercion rules, the existence of `null`, weird float vs int rules.
---
†: I haven’t checked, but I bet there are some libraries out there that provide proper optionals using Proxy.
Map goes part of the way to what I want. But I really want to control which objects are considered equivalent keys, rather than being limited to reference equality.
It's also easy enough to find or write quick simple Map wrappers that use a hash function on an object as keys when provided (either piggy-backing on the existing Object.prototype.valueOf, which always exists on every object and easily falls back to the reference-based current behavior, and expecting classes to have custom overrides for that, or using a Symbol named function of their own to avoid polluting own-property-keys/name clashes with other libraries).
Typing up constants in Babel JS is so much uglier than it needs to be.
But Coffeescript also introduced things like arrow functions that bind `this` to JS. Transpiling to ES5 allowed JS to evolve.
TypeScript brought something good for the JS ecosystem (although Flow did it too, in a different way) and it doesn't look like anything will be able to replace it at what it does anytime soon.
It adds to countless attempts of other transpiled *scripts to extend Javascript.
They, and Coffeescript in particular, are good historical references to what will happen to TS in a few years.
I doubt Coffeescript ever saw this scale of adoption.
As much as I like Anders work, it is impossible to understand modern TypeScript without looking into tsconfig.json before looking into source code.
Many toolings make it so you don't even need to worry about the tsconfig anymore (though in reality it is very helpful to understand it).
Out of all pull requests on GitHub in Q4/2020, 18.8% were JavaScript and 6.7% were TypeScript.
For reference CoffeeScript was at 0.25%. At its peak in 2014 it was at around 1.5%, when plain JavaScript was at over 20%.
https://blog.isquaredsoftware.com/2020/03/codebase-conversio...
That unlocked the ability to add React and TypeScript code embedded directly in the existing AngularJS UI, using the `react2angular` library, as a short-term migration approach. I was also able to migrate some of our existing AngularJS controllers and client logic to TS to get a bit more safety.
Since then, I've set up a new Next.js subproject that sits behind the existing Express app server, and was able to show content from Next by inserting iframes into the AngularJS templates and proxying those URLs from the Express app to the Next app. That's let us build new features from scratch inside of the Next codebase while showing them seamlessly in the AngularJS UI, giving us a long-term migration path.
We've been using Flow as a type checker but are converting our code to TypeScript for the tooling, ecosystem, support, and hopefully improved performance.
TS has been running as a project for quite a while and a whole lot of folks have a stake in it. I think it's a pretty good bet.
Additionally, I was there when Angular 2 was announced, and the community had no idea what to do or when it was coming out and didn't know whether to dig their heels in and stay on 1 or what the migration path to 2 would look like. I think that's a slightly different story given the context.
The thing is that many good ideas of Coffee ended up being integrated in newer versions of JS, so of course the need for it decreased; one could argue that Coffee's legacy lives in ES201x.
But there are no types in JS, and none planned.
But this article reads like "Developer who only used dynamic typing learns about static types." You could sub in any two such languages and get the same article.
I'm not critiquing any of those languages, in a perfect world any of them in the browser would have been preferable to Javascript. The issue is the tower of dependancies we are building, and you have to stop somewhere.
Somewhere there's probably an optimal expected lifespan of a program where the line between preferring a better language and fewer development dependencies is crossed, but I'm also starting to learn that software stays around years longer than I ever anticipated.
If this article started with legitimate reasons for not wanting to adopt TS it would be different. "It is hard for people to learn" is actually a legit reason but "I don't understand it" is probably not!
That doesn't imply all new things are good or that senior engineers need to understand all new things either. I think there is some confusion about this.
Skepticism !== reject everything without grokking.
JS has only been around for 25 years. I’m a “Senior person” and I’ve been seriously working with JS for less than 5 years.
Unit tests have similar advantages to types: they catch errors early and let you refactor your code with confidence.
But unit tests go even further, because they test the _correctness_ of the returned value, not just its type.
And a massive turnoff for me is that the types are compile-time only.
So you see, if your code is tested, and the types are just partial static checks; I feel like it's just not worth it.
I enjoy using types in other languages, for example it feels nice to add type hinting to old PHP codebases. But in PHP, types are much simpler (less work) and more effective.
If they added optional run-time instrumentation like "spec" in Clojure, I would definitely use TS. But for now, I'm out.
But testing with a weak type system isn't going to give you anywhere near the guarantees you'd get from a well-designed type system in addition to testing: https://kevinmahoney.co.uk/articles/tests-vs-types/
One of the projects I worked on during my transition to typescript was to convert an existing project into it. Sure there was a lot of head banging against walls at some point to get the types to work out, but during that process I finally really understood how everything fit together and gained a sense of assurance that I could use a return of a function with complete confidence. Developing on that project afterwards was also a breeze, moving faster and more confidently knowing that the object types were set, and that guardrails were in place to prevent myself from shooting my own foot.
It's a new system to work within the guidelines of, but guardrails are there for a reason, to prevent you from falling off a cliff.
For me, typed python saves me a bunch of time.
I've barked up that tree so many times, I don't think I'll ever go back to Python for that stuff in the future.
We spent more time screwing around with our Typescript types, getting the compiler to be happy, working around bugs with type assertions, etc. than we did focusing on the app logic at some points.
This kind of stuff, to compare, never really happens with C++. The type system is simple enough (compared to TS) that you can pretty confidently fix type errors quickly. I don't like Rust, but I know that the same occurs with Rust's type errors, too. Why is TS so difficult?
Because Microsoft created a type system that is so complex it can represent (almost) any type in a dynamically typed language. It's impressive, but not at all practical.
It’s like I’m a metal worker and I get along fine with a ball peen hammer. You come along and give me your claw hammer with great excitement. Sure, it can mostly do the same job, but my ball peen hammer is what I’m used to so I’m sticking with it.
Seniors occasionally rejecting this or that, seniors occasionally blindly accepting this or that are both completely normal in majority of seniors.
I was afraid to introduce complex generic types because it isn't easily represented in typescript, or to change a struct from "person" to "employee" will need to map each properties individually.
Thankfully that wasn't the case.
I for one am building a large CRUD application with dozens of models spanning hundreds of fields, and Typescript is helping me (though not a panacea) keeping things correct.
I'm comparing it to a project I did years ago, first Backbone, then Angular, but both before 'typesave JS' was a thing, and that one relied heavily on reading and comparing keys and a deep knowledge of the domain.
But nearly ten years later, I don't have the mental capacity for that anymore and I need the crutches that typed languages offer me. By crutches I mean securities, safeguards and IDE assistance.
It makes sense for anything nontrivial IMO. Types allow you to be able to free up mental space. You can trust the type checker instead of having to rely on your own abilities.
Sorry for the off topic rant!
P.S: Yes I know source maps exist. They suck/aren't reliable.