7 ms·
Many of the worst holes are indeed because of JavaScript compat. But, not all of them. See some of my other comments in this thread. There's no earthly reason
by ragnese 5y ago
Many of the worst holes are indeed because of JavaScript compat. But, not all of them. See some of my other comments in this thread.
There's no earthly reason that a `type` should automatically implement Record<string, unknown> and an `interface` shouldn't. That's just dumb and annoying- not necessarily "wrong". However, it's definitely wrong to allow me to pass `const o = {}` into a function that wants `Record<string, string>`. How in the world does TypeScript (with the strict flag set!) think that an empty object is able to return a string value for any arbitrary string key? That's absurd.
The issue I raised about arrays isn't just arrays, actually. It happens with object fields as well. See https://www.typescriptlang.org/play?#code/MYGwhgzhAECCB2BLAtmE0DeBfAsAKFEhgGEwAXaAUwA8zL4ATGBFNTfaT6ZSgewHcAFAEoAXNABuvRA3Z4uC6MF7wIvEJQB0IXgHNBAIh4CDwjl1x5LhKNAAieqrXpM4SVOgznOAIzAAnAGsRcSkZOUUuZVV1LR19Az8g029oS0t8RHg6fwAzMGBKaAAJSAAFSgoveS4AB0rxFg98DLwsnPzCksgHXQi6hvs9Fvx8XIBXeGAyRBVuMCRa8fA6CrJBXnFSiDXhfs5eTXqKAF5oeEp+aFJ1sytRghUICk3uiF7oM+qFY-ELq96IhGeHwqEWy3IlDWGzu+EOx00SWCewA9CilLxkLVEBp-NBEDB6nlKNMQABPaAACzAtVqZIAhEA https://www.typescriptlang.org/play?#code/MYGwhgzhAECCB2BLAt...
And, the `readonly` keyword has nothing at all to do with JavaScript. Yet, they added this keyword that is completely and utterly useless. To the point that I actually introduced a bug in my own code because I put `readonly` and `Readonly<>` everywhere and expected it to actually help me. It did help me... most of the time. And then it didn't. Because it turns out that you can always take a Readonly value, assign it to a non-Readonly variable and then just mutate the hell out of it, and the compiler won't even bat an eye: https://www.typescriptlang.org/play?#code/JYOwLgpgTgZghgYwgAgJIFt0FcxwEYA2EAYgPanIDeAsAFDIPJQRwAmpIBAnsjOQFzIQWdHmh0AvnTqhIsRCgCyOfETIUa9Rn1KDho8bSm06MLCARhgHZAAs4IVmvIAKHYIzZchEuQCUggBupMCsVHSMyAgcAM5gyKRgttDqgsrezhQAvLzkEYyJyVDqAHQ6yDkAtACMktK00SBxubpomCo+6hXhWgzuyLVG9Y0xpEQlBKQA5m7kZf509o6Zs6R+dCNjEBPTq-NrQA https://www.typescriptlang.org/play?#code/JYOwLgpgTgZghgYwgA...
That's just... so bad. I don't even know what else to say. Sure, it caught some of my mistakes, but if it won't even catch all of the class of errors that it's supposed to, then I have to basically be just as careful as if I didn't use the feature at all. So, it's not reducing my mental burden at all. If anything, it's just giving us/me a false sense of security and tricking me into thinking that I DON'T need to be as careful.
- exdsq 5y agoI think you're overthinking this. TypeScript definitely helps catch issues - there have been large migrations to TS that were well received by engineers and project managers alike. Yes there are issues in the type system but you can find them in any language. Haskell has very strong claims but then you come across 1/0 != 1`div`0
- ragnese 5y agoYour Haskell example doesn't actually violate its own type system, though. 1/0 is Fractional or Double or whatever, so it gives Infinity. 1`div`0 is Integer and Infinity is not an Integer, so it explodes, instead. Definitely confusing. Perhaps poorly designed. But it's not type-theoretically incorrect. I'm complaining about TypeScript being literally incorrect in its handling of types. Not "strange" or "weird" or "surprising", but incorrect.
- brundolf 5y ago> However, it's definitely wrong to allow me to pass `const o = {}` into a function that wants `Record<string, string>`. How in the world does TypeScript (with the strict flag set!) think that an empty object is able to return a string value for any arbitrary string key? That's absurd. It's not. `Record<string, string>` doesn't mean it has a mapping for every string key (that's not possible), it means it might or might not have a mapping for any given string key; it really is analogous to a Map, and it's perfectly valid to pass an empty map to an argument that takes a map of a particular type. The only thing wrong with it is that `MyRecord[someKey]` will by default have type `string`, not type `string|undefined`. This it presumably to help with JS code that commonly indexes objects by strings without checking that the keys return values. Luckily, there's a compiler option to make it return `|undefined` (and it works on arrays too, which have the same problem by default): https://www.typescriptlang.org/tsconfig#noUncheckedIndexedAccess https://www.typescriptlang.org/tsconfig#noUncheckedIndexedAc... > Because it turns out that you can always take a Readonly value, assign it to a non-Readonly variable and then just mutate the hell out of it I am surprised and disappointed that this works without casting, but I think with casting it's reasonable. TypeScript has to give you trap-doors to work around its checks when they don't serve your purposes; this is part of the "being overlaid on an imperfect language" thing. The important thing is that those circumventions are explicit and stand out. Though I will say, you definitely can't pass a readonly object/array to a non-readonly object/array function argument, so I'm doubly surprised your example works. I may dig into it further later just out of curiosity.
- ragnese 5y ago> It's not. `Record<string, string>` doesn't mean it has a mapping for every string key (that's not possible) Yes, it absolutely does mean that. Just because you can't correctly implement an object that satisfies the type doesn't mean that the type means something else (`never` is also a type that you can't implement). > The only thing wrong with it is that `MyRecord[someKey]` will by default have type `string`, not type `string|undefined`. Um, yeah. That's what's wrong. And therefore Record<string, string> literally does mean a string value for every string key. The problem here is that some of these "utility types" have a specific intention behind them, and you aren't "supposed to" use Record this way. You're "supposed to" give it more specific types, like Record<'a' | 'b', string>. But the compiler should be smart enough to know that nothing can possibly satisfy Record<string, string>, so if you write the type, it should be impossible to assign to it. > Luckily, there's a compiler option to make it return `|undefined` (and it works on arrays too, which have the same problem by default): https://www.typescriptlang.org/tsconfig#noUncheckedIndexedAc https://www.typescriptlang.org/tsconfig#noUncheckedIndexedAc... That's not the solution here, because it will cause a Record<'a' | 'b', string> to return string|undefined when I do `o['a']`, which is also wrong. The solution is for TypeScript to handle Record correctly. > I am surprised and disappointed that this works without casting, but I think with casting it's reasonable. TypeScript has to give you trap-doors to work around its checks when they don't serve your purposes; this is part of the "being overlaid on an imperfect language" thing. The important thing is that those circumventions are explicit and stand out. I have no problem with casting. Having hard type casts does not make the language imperfect or incorrect. > Though I will say, you definitely can't pass a readonly object/array to a non-readonly object/array function argument, so I'm doubly surprised your example works. I may dig into it further later just out of curiosity. Definitely? https://www.typescriptlang.org/play?#code/JYOwLgpgTgZghgYwgAgEoTgEwPYgDYCeAYsBHpsgN4CwAUMg8lBjvgcnAFzIgCuAtgCNodAL506oSLEQoSZCjXqMuPAcKhiJtTBAR44zZDF4gEYYLmPZsACmzd55AJTcAbtmCZtu-YZQIuADOYMaOpOR0vgZGgSAhTNgw3OhYuIRO3jp6MQHBoVDYhcloLOkEADyprBkRmAB82jA2tjDOdM12xe20nbaF3UA https://www.typescriptlang.org/play?#code/JYOwLgpgTgZghgYwgA...