9 ms·
A 'CSS reset' for TypeScript, improving types for common JavaScript API's
- redmorphium 4y agoInteresting. Do these represent oversights in TypeScript's builtin type definitions, or are they artifacts of legacy considerations?
- jeroenhd 4y agoI think TypeScript's decisions about these methods were done either because early versions of the transpiler couldn't detect certain behaviour or to make the language friendlier to work with. If you have a function `widgetifyFoo(object: Foo)` and some JSON that you're pretty sure returns a `Foo`, you'd like to be able to do `widgetifyFoo(JSON.parse(data))`. With `any` the method call should just work, but with `unknown` you need to be explicit in what kind of data you're expecting (`widgetifyFoo(JSON.parse(data) as Foo)`). That may be too verbose for some. The `[1, 2, undefined].filter(x => !!x)` typing is just a limitation of the transpiler. The end result will be an array containing two numbers, but you need to do some complicated validation to get the type right; `filter`s can be very complex if you want them to be, and they do by contract return a (sub)selection of the input. The type guarantee you want as a developer is based on the implementation rather than the underlying type system. I suspect the `Array.includes` implementation to be a choice by the devs. The check will only succeed if the type matches, so you have a choice between setting the right type on your array (`"matt"|"sofia"|"waqas"|"bryan"` if you want to check for `"bryan"`) or using the type error you receive as an indication your check makes no sense. If you have an immutable array of specifically `["matt","sofia"]`, why even check for `"bryan"`? It won't be there, unless you make a mistake! Javascript will obviously happily call the method for you and do what you expect, so TypeScript is breaking valid JavaScript standard API code here. That said, I agree with the TypeScript devs that they made the right choice on that one.
- Dylan16807 4y ago> I suspect the `Array.includes` implementation to be a choice by the devs. The check will only succeed if the type matches, so you have a choice between setting the right type on your array (`"matt"|"sofia"|"waqas"|"bryan"` if you want to check for `"bryan"`) or using the type error you receive as an indication your check makes no sense. If you have an immutable array of specifically `["matt","sofia"]`, why even check for `"bryan"`? It won't be there, unless you make a mistake! That logic only applies to searching for a constant, and in that case it should go a step further and complain that you're using .includes at all. There's no reason to search for the constant "bryan" or the constant "matt". The only time it makes sense to use .includes on this array is with a string of [semi-]unknown contents. And yet that's what gets blocked. The typing is wrong.
- jeroenhd 4y agoBut TypeScript does have typed strings. If the array contains arbitrary strings, then the includes() call would just succeed without error.
- Dylan16807 4y agoIf you typed the array as string[], it would generally work, but then the compiler wouldn't be able to warn you that users.includes("matt") is always true. And it wouldn't be able to warn you that users.includes("bryan") is always false. I could at least understand the use case for a narrow includes if you explicitly typed the array as `("matt"|"sofia"|"waqas")[]`. But that's not the type of the array. The type is `readonly ["matt", "sofia", "waqas"]`. There's always exactly one of each. There is never a reason to feed a string of type `"matt"|"sofia"|"waqas"` into includes. The only sensible parameters for includes are types that intersect with `"matt"|"sofia"|"waqas"` but can be other things too. Which means the most sensible parameter type to derive is `string`.
- CGamesPlay 4y agoIt's a weird choice that this library makes some JS APIs more strict, and others less strict. Specifically, the changes to JSON/Array.isArray make sense, the value is unknown, and coercing it to any encourages leaking that type further, or runtime errors. However, the changes to includes/Set basically amount to "I said that this array contained this enum, but I changed my mind when I wrote this if statement". This actually allows more bugs to slip through, rather than fewer.
- kabes 4y agoIIRC, in the past .json on fetch was taking the data type as a generic parameter. Then suddenly it got changed where the generic was removed and you need to cast it yourself. I wonder why that was done.
- ht85 4y agoIt was likely done to force the use of a cast (`as ...`) that is easily identified as an unsafe operation, whereas type parameters are not.
- deleted 4y ago[deleted]
- raydiatian 4y agoI’m not sure anybody has ever convinced me that unknown is meaningfully better than any, in any scenario (pun). I get that it’s “typesafe” on the surface but we both know you’re going to turn around and cast it to the type you think it is so what’s the point
- moritzwarhier 4y agoUsing type predicates. But yes that can be tedious.
- jeroenhd 4y agoIt depends on your setup. I've worked with a tslint setup that treats `any` as an error, only accepting `unknown` (or in bad situations, `never`) for stuff like Javascript code. If you treat `unknown` as C's `void*`, then you're not getting much out of your type checks.
- cypress66 4y agoProbably to make you parse it with a schema validation library, which will throw if it fails to do so.
- anderskaseorg 4y agoYou can safely cast an `unknown` to a desired type with a runtime check from a library like Zod (https://zod.dev/ https://zod.dev/). The `unknown` makes sure you don’t forget a check.
- bluepnume 4y agoIn that case what's the point of any type system at all? Any type can be cast to another. If you don't just arbitrarily cast whenever you like, unknown is extremely useful to remind you to type check or parse before you use a value.
- johtso 4y agoNo, I'm going to parse it with zod and make sure it is what I think it is.
- jcparkyn 4y ago
- olalonde 4y agoWhy not just send a pull request to TypeScript if some of the types are incorrect?
- gl-prod 4y agoSo many libraries depends on TS and a PR like that would break a lot of them. I would say they need to put it under a flag.
- ocimbote 4y agoSo... a PR with a flag? :)
- makkesk8 4y agoThat's what semantic versioning is for, breaking changes are inherently allowed in major version bumps.
- rgoulter 4y agoIn theory, yes. In practice, here are a couple of examples: A thread in relation to "replace `fgrep` with `grep -F`": https://news.ycombinator.com/item?id=33189503 https://news.ycombinator.com/item?id=33189503 How people feel about Python's handling of Python 2 vs Python 3: https://news.ycombinator.com/item?id=34227760 https://news.ycombinator.com/item?id=34227760
- undefinedzero 4y agoSemantic versioning isn’t really usable for typed languages as the majority of fixes would be considered breaking changes.
- hombre_fatal 4y agoBeing able to declare major changes in a version number says nothing about whether it's a good idea to make them.
- purge 4y agothey made the change from any to unknown in the catch handler so its not without precedent
- quickthrower2 4y agoWith the ‘unknown’ type available is there a good case for ‘any’ anymore? I really like the ‘unknown’ type. In catch statements for example the error is unknown but you can do dynamic checks to see what it is, then typescript lets you treat it that way.
- spankalee 4y agoThe TypeScript team has said that if they could do it over, `any` would behave like `unknown`.
- Kiro 4y agoThat sounds extremely cumbersome. Sometimes I just want to throw an any on something and call a method on it without having to deal with types. How would unknown help with that? I can't call a method on a variable with unknown type.
- haburka 4y agoYou can use ‘as’ to cast unknown to any type. This is extra typing but it does signify to other developers that you’re calling a function on an unverified object, which I believe makes the code more maintainable in the long run.
- anentropic 4y agoWhy bother using typescript then?
- Kiro 4y agoThat 1% won't invalidate the remaining 99%.
- golergka 4y agoThis is a good thing for serious, long lasting projects. However, it could be an overkill for something fast and dirty. One thing I love about typescript that I've never seen in other languages is that you can adjust the type checker exactly for the level of rigidness that project at hand needs. Any is okay for 200 LOC quick prototypes.
- jillesvangurp 4y agoI've been using kotlin-js for the past few years. Not an obvious choice for a lot of typescript users but IMHO something that would lead to a lot of obvious improvements to typescript if more people would. Like being more strict by default and getting rid of a lot of the javascript compatibility. This library seems like a noble attempt to patch it up somewhat. But why not go all the way and just fix the language properly? Kotlin and typescript are actually similar enough that transitioning from one to the other isn't that big of a deal. The key difference is that typescript tries to maintain compatibility with javascript whereas it's just a compilation target for kotlin-js. Like with typescript, you use type safe wrappers for any javascript code you need to access. You can actually reuse typescript type definition files and generate Kotlin ones from them. Not perfect, sadly, but it works for simple libraries and you can manually deal with the more complicated ones. I use things like fluent-js, maplibre, and a few other libraries. I don't use react, but a lot of kotlin-js developers do. There are no real limitations on what you can use here. The one compromise kotlin-js makes to enable interfacing with javascript code is the dynamic keyword, which you use to do the bait and switch style APIs you see a lot in the javscript world. It could be a list, a number, or a string, etc. Dynamic allows you to work with such APIs. It's a necessary evil. But otherwise, it's all good. It's strict by default. It doesn't have a mode where it is not strict. And this is what enables IDEs like intellij to be a lot smarter with Kotlin than it is with typescript. If you ignore the few syntax and language features that are unique to either Kotlin or Typescript, the key difference is that typescript is more sloppy and it's mostly because of js compatibility. And since kotlin-js has almost none of that and can manage fine without it, it kind of proves that this level of sloppiness is simply unnecessary and redundant. A whole lot of downsides and not a lot of upsides. Typescript is much more sloppy than it needs to be. Making it less sloppy is the obvious way to improve it. It's why I use kotlin-js instead.
- giraffe_lady 4y agoThis is basically how I feel about rescript these days. Typescript is really amazingly complex for what it ends up giving you. It's a big improvement over js, but I don't think that's the right comparison. Using typescript we're already paying the costs of friction & indirection in tooling and learning another language. I know typescript has a ton of momentum and is unstoppable at this point but I wish things had turned out differently.
- suction 4y ago[dead]
- mrblampo 4y agoInteresting! The example [1, 2, undefined].filter(Boolean). // number[] is interesting because it makes use of the fact that the types of individual array members are known at compile time. I can think of some times when I have dealt with arrays that are defined at compile time, but not where I've needed to call .filter on such an array. Is there a common use case here?
- mrblampo 4y agoReading https://github.com/total-typescript/ts-reset/blob/main/src/entrypoints/filter-boolean.d.ts https://github.com/total-typescript/ts-reset/blob/main/src/e..., I'm more confused than before. The generic NonFalsy<T>[] looks to me like it should evaluate to never[] if the entire array is false, and to T[] (the original array type, not narrowed) otherwise. But I can see that the test case demonstrates that it works. What am I missing?
- spiffytech 4y agoI think that's just a contrived example. It looks like it should work with types like `(string | null)[]`, where individual members are only known on runtime. That's very appealing to be; I commonly .map() an array to a nullable type, then want to filter out the nulls.
- mrblampo 4y agoHow could it help with anything at runtime? The type system only exists at compile time, right?
- spiffytech 4y agoYeah, it only exists at compile time, so in "real" code you won't know the type of e.g., array index 2 at compile-time, but you will know "each item in this array is either type A or type B". You'll often want to work on only the A's in the array, and so you need to whittle down the data, but also inform TypeScript that you've done so. The return type on your .filter() callback tell TS what type the entire resulting array should be. When filtering out nulls, you normally have to write a user-defined type guard everywhere you call .filter(), which is irksome, so this project configures the built-in Boolean() function to behave similarly when passed to .filter().
- myfonj 4y agoAs a side note, in editors using js/ts language server from VSCode, you can benefit from these types even in "vanilla" JavaScript with JSDoc. It could look like: // @ts-check /// <reference path="./ts-reset-0.3.7/src/entrypoints/recommended.d.ts"/> // = contents of the archive from // https://github.com/total-typescript/ts-reset/releases const allCurrencies = /** @type {const} */(["EUR", "USD"]); /** * @typedef {typeof allCurrencies[number]} currency */ allCurrencies.includes('XXX'); // No 'Argument of type '"XXX"' is not assignable…' error any more. // Would be that error without ts-reset. /** @type {currency} */ let foo = 'EUR'; // OK /** @type {currency} */ let bar = 'XXX'; // that error, expected
- FractalHQ 4y agoI really can’t stand the appeal. It’s so much more verbose and clunky than Typescript. I want to understand why people choose it but I can’t justify using a worse, more verbose, less readable, and less powerful version of Typescript when I can just change a j to a t and have the real deal.
- commotionfever 4y agoit doesn't require a build step though
- latchkey 4y agoI love it when my code fails to compile. Machines are good at finding problems. It is a feature, not a bug.
- myfonj 4y agoThat's the point. Suggested setup (editor with JS/TS language server constantly checking your code) means you will know about every problem that would possibly break something, you just don't need separate "source code" producing "build package". It means you can ship your code directly to target platform that only understands JavaScript. AFAIK that's not what you currently can do with TypeScript (except Deno).