Why Object.keys doesn't return (keyof T)[]
vladimirzdrazil.com
vladimirzdrazil.com
const base: BaseClass = new SubClass();
const keys = Object.keys(base); // should give you the subclass keys as well
(This is pseudo-code that might not quite work due to how prototypes and classes interact, but I'm trying to illustrate a principle.)What's perhaps a bit surprising is that all types in TypeScript behave like BaseClass in this example. Object types (e.g. {a: number, b: string}) specify the minimally required properties, not the maximally allowed properties -- they are effectively the same thing as interfaces. Any concrete object that implements the interface can have more properties than the interface requires.
That's so fundamental to the type system that I think if you want to work around it, you might not fully understand how TypeScript works yet.
I think most engineers who use (and swear by) TypeScript don't fully understand how TypeScript works (or JavaScript for that matter).
I've worked across multiple teams at multiple companies and always run into types I can't trust because they are just plain wrong and inconsistent with the runtime, or data that isn't typed correctly. I also found that everyone writes TS differently, and sometimes the verbose typings and weird non-idiomatic patterns introduced just to please tsc actually make the code harder to read (30-line JS modules become 120-line TS behemoths). I still prefer working with TS on teams (though I prefer JS on solo projects now), but after a while I ask "is this the best we can do?"
/end minirant
In contrast, OCaml's objects differs <a: int; b: int> (exactly those fields) from <a: int; b: int; ..> (may have more fields). Likewise for Purescript records etc
This stuff was figured out decades ago. There was no need for Typescript to be like this
typescript is strictly a superset of javascript, and javascript is like this.
I think it is also a large part of the reason why TypeScript is amazing and very useful in the real world.
I know this comes across as really hostile but I just feel very burned by TS's promises and hype. You can work in it more confidently than JS I think but a lot of that is due to the tooling. In exchange you massively increase the required knowledge and mental modeling to keep track of what's going on and why. I used to be all about it but now after using it in a few big projects I've lost the enthusiasm.
Someone on here once described it in an excellent way, saying it's great for working out "type puzzles," which smart engineers tend to experience as important and productive. But the actual practical benefits in "the real world" as you say I'm becoming less and less convinced of.
B You say you are less convinced of the "real world" benefit and actually specify the main one: "a lot of that is due to the tooling"... great tooling requires specific typing, this is a only a compile-time pass.
It's more complex than other languages with similar benefits. Languages with type systems as sophisticated as TS give you MUCH stronger assurances for it. Languages with less sophisticated type systems give you much stronger assurances for it. People are bringing up ocaml in here for very good reason. It has a fairly limited and simple type system compared to TS yet is able to make strong assertions based on it. Rescript is an excellent example of how this could have been applied to JS.
This is the core tradeoff of being a superset of JS, but as more time passes it's harder to see the other benefits of that. You need a compiler anyway.
Can you explain more about what additional knowledge and mental modeling you think is required for Typescript that you're exempted from knowing in Javascript? In my experience, Typescript only exposes the ugly realities of Javascript. Ignoring it doesn't eliminate it.
most type systems are not sound. Co-, Contra- or In-Variance is a extremly hard problem there are not many languages out there who offer complete soundness over all types.
most problems of typescript happen because people use interfaces a lot, since they are type hints for javascript objects, however once you start with classes way more and use strict mode, stuff is way better than just using js.
TS is very pragmatic in the way it allows you to convince it you know what you're doing and, really, this will be fine in reality even if the types don't really agree. It's a DX tradeoff I believe is usually worth making.
But it is comical on a certain level how you'll choose to use a tool to point out the potential of problems with your code but then sometimes ignore it / tell it it is wrong when it does :-)
Usually when the tool fails to be expressive enough for me to comfortably communicate that no, it’s definitely going to be T, not `string`
Using Object.entries/keys/fromEntries and Array.reduce are my least-favourite moments with TS because of how darn wordy and assertive I have to get.
I disagree here. Typescript allows you to lie about your code, and that's only a ticking time bomb that will work until it doesn't. The workaround might work in the current state of your application, but over time if things/people change, it makes it much more difficult to reason about things.
Because typescript allows interfaces to have additional properties, five levels up the call stack someone will pass in an object that has extra properties for whatever reason, and then the assumptions we once made about the code fall out and you ship an unknown bug.
Of course, you'd run into the same issue in regular Javascript by expecting a base class and receiving a child class of that base.
I've worked on many dynamically types systems and have no problems thinking in terms of compile time, run time, and macro expansion if you're so lucky, but some people just want the computer to say yes.
Oh and miss specification of the system will happen in all. Even when computer says yes.
What I don't understand is why you would chose to use Typescript, and then opt out of the only/main benefit it actually has. At that point you're just writing dynamically typed Javascript with more keyboard presses.
I could just as easily argue with equivalent experience the exact opposite.
If you’re acting like a type system suddenly means that you aren’t making trade-offs, that’s a huge sign of incompetence in my eyes. And if you AREN’T acting like this, what precisely is the issue here?
Not everything needs to be an argument. The parent commenter is quite rightfully pointing out some nuance, not necessarily arguing that Your Preferred Way To Do Things is wrong.
Those words do not appear in the comment that I replied to.
The argument that I'm challenging is this opening line:
> It's amazing how in practice it's not really a big deal.
I'm not discarding their experience, but from my own experience I would argue the opposite — in practice it really is a big deal.
That's a terrifyingly vague comment!
knownKeys(o, ["x", "y"]); // returns ["x", "y"]
Excess keys are not an issue, because they are ignored. And if I fail to list a key that the compiler knows about, the compiler will complain. const taskCounts = { work: 10, home: 5, personal: 3 };
This probably has the deduced type of `Record<"work" | "home" | "personal", number>`.I wonder if there is a hole in the type system that could be filled with an `ExactKeyTypeRecord<K, V>`, which is invariant in K and is a subtype of `Record<K, V>`. Maybe this could have Object.keys() defined as expected.
I have no experience with TS, but I believe Python's TypedDict has a bunch of similar holes in the type system that could be mitigated with a bit more elaborate type hierarchies.
const taskCounts = { work: 10, home: 5, personal: 3 };
const what : Record<string, number> = taskCounts; // OK, covariance?
const the : Record<"work" | "home", number> = taskCounts; // Also OK, contravariance?
const hell : Record<"work" | "home" | "personal" | "hello", number> = taskCounts; // This is an error
https://www.typescriptlang.org/play?#code/MYewdgzgLgBFCGEDWB...This really needs to be stated more clearly I guess.
Object.keys(someObject).reduce((acc, key) => ...., { const val = someObject[key] })
When I extract the value in Typescript, I always have to coerce the type like: someObject[key as keyof typeof someObject]
It would be nice if the behaviour with "as const" was different:
const person = { name: 'John Doe', age: 35 } as const
const result = Object.keys(person) // return type is not 'name' | 'age'> I feel like it just makes more sense to trust that the object type is what we've said it is
The object type is what we've said it is. The type is correct, it just doesn't necessarily specify every last detail of your value, because subtyping is a thing. For example:
class Pet { name: string }
class Dog extends Pet { isAGoodBoy: boolean }
const f = (pet: Pet): keyof Pet => Object.keys(pet)
// this call should be allowed, but that means `f`'s return type is wrong
f(new Dog()) let foo: number;
because at runtime you can assign a string to foo.Typescript's type system is for compile-time checking, not runtime checking. The above definition is correct if at compile-time you want to check that all assignments to foo are of type number.
That's why I occasionally list specific options in addition to the larger parent type, just for the IDE, not for the type checks.
TypeScript mixes a lot of things and tools that are actually separate things and presents it in one big package, for better or worse, not just in this case.
(keyof typeof someObject | (string & Record<never, never>))[]
which will give you autocomplete but still allow arbitrary strings. TS will still complain as soon as you use it as an object indexer though, defeating the point.Yes it does! I even tried it just to be sure with just that example "a" | "b" | string. The Playground and Webstorm both provided correct completions, Playground added a lot of wrong useless stuff, Webstorm - using tsc service I guess - only "a" and "b" as possible values (there is nothing concrete to other string after all).
It also works when the type is defined in another file/module.
Playground: https://www.typescriptlang.org/play?noUnusedLocals=true&noUn...
I don't know where exactly the suggestions come from, if it's the language service or something else. Why would that matter? The only point I care about is that those suggestions are made, making those useless-for-type-checks union components still useful. I found WebStorm to show them even after I unchecked the TS language service in the settings. So what?
Or just don't do it, it's not type safe.
Very interesting though, I have not thought that it could be a problem/necessity, maybe I'm programming on "automatic mode", in a way that I already know the workarounds of TS type system.