6 ms·
I would really love to enjoy TypeScript, especially now that they've added HKTs, but I am constantly running into type errors when the types clearly match, lead
by sli 7y ago
I would really love to enjoy TypeScript, especially now that they've added HKTs, but I am constantly running into type errors when the types clearly match, leading to weird workaround code where the type of some key in some object is specified as:
false | 'x' | 'y' | undefined
Meaning passing in `'x'` should work just fine. But instead, it specializes it to just a string, then throws a type error. The workaround requires me to write the following value to avoid a type error:
'x' as 'x'
This is not the only time I've run into this kind of problem -- I run into this problem regularly -- this is just the simplest version of it that I've experienced. I just cannot take a type system seriously when workarounds like that are needed.
Add to that, in many cases being unable to import a JS library in that doesn't include typedefs, and I fail to see how TS can be considered a superset of JS. It's certainly sold that way, but it's clearly not totally true. TS+React requires --noImplicitAny, which cannot be worked around (it's labeled as being required due to a platform limitation).
I also find Microsoft's documentation of TypeScript to be very poor (and has no search feature -- I'm sure they have some convenient excuse for that). I tend to learn how things work from random Github comments or source code after reading the documentation over and over. What is `declare` used for in practice? They don't really say, they just say it's for declaring things, and give highly specific examples.
Maybe I'm the odd one here and maybe I'm missing some piece of information that a lot of TypeScript developers have, but I've yet to find it and given all the choice I have these days, there are numerous languages I would choose well before TypeScript.
- simplify 7y ago> I just cannot take a type system seriously when workarounds like that are needed. Are you familiar with type theory and how easy it is for a type system to require too much work to be useful? Especially with conditional types. TypeScript has been doing a phenomenal job. For this particular use case, since version 3.4 you can write `'x' as const` which will work no matter how large your type is.
- panpanna 7y agoSpeaking of which, is there any tutorials on type theory that targets developers instead of mathematicians?
- simplify 7y agoHindley-Milner is a solid place to start. Here's an introduction: http://akgupta.ca/blog/2013/05/14/so-you-still-dont-understand-hindley-milner/ http://akgupta.ca/blog/2013/05/14/so-you-still-dont-understa...
- rezistik 7y agoTry `as const`?
- KenanSulayman 7y agoI second that, to a degree. I write mostly TypeScript and Rust these days and it still behaves unexpectedly sometimes. Example: for a service we wanted to port Rusts’ Result. https://gist.github.com/KenanSulayman/34a40daa3ebbd1e1bdf7c734afc815b1 https://gist.github.com/KenanSulayman/34a40daa3ebbd1e1bdf7c7... Notice the ‘as any’ and ‘_T!: T;’ et al. hacks to make it work. Other than that it’s completely a one to one port from the Rust core implementation.
- wereHamster 7y agoBecause you're trying to manually optimize code, which is kindof like saying 'I konw what I'm doing so TypeScript please shut up'. Take for example the `map_err` function of the `Ok` class. The function returns a different `Result`, but you're using the `… as any` trick to shut up the compiler. The correct implementation would be: map_err<U>(fn: (arg: E) => U): Result<T, U> { return new Ok<T, U>(this.value) } I see why you're not doing that, because that allocates a new object, and that might seem bad. Even the implementation in Rust 'allocates' a new object, but then it relies on the rust compiler to optimize that out. Here's the relevant code: pub fn map_err<F, O: FnOnce(E) -> F>(self, op: O) -> Result<T,F> { match self { Ok(t) => Ok(t), Err(e) => Err(op(e)) } } See the `Ok(t) => Ok(t)` line? That's the same as `return new Ok(…)` in the TypeScript code. Unfortunately TypeScript is not an optimizing compiler, so it's less powerful than rust. You can either use tricks (`… as any`) and give up type safety or be truthful at the expense of performance.
- steveklabnik 7y agoTo be clear, the option type in rust does not heap allocate at all; no need for the compiler to optimize things away.
- phiresky 7y agoYour problem there seems to mostly be that you tried to directly port it without any modifications, and your code is actually incorrect in some places. For example, if you implemented Result<T, E> as a single class that had a `if(this.is_ok()) ...; else ...` it would work fine without any type assertions or any anywhere. As it is, Ok only depends on T and Err only depends on E, so why do you have them depend on both? One problem is that TS does not want to merge unionized function signatures like this: map: (<U>(fn: (arg: "yep") => U) => Ok<U>) | (<U>(fn: (arg: any) => U) => Result<U, "nope">) This can be mostly fixed by removing arguments that you don't need. I couldn't figure out .map_or_else, but .map().or_else() works. Admittedly, with this way you can get an output of Ok<a> | Ok<b> from some functions, but I don't know where that would be a problem (still fully type checked, you would just get the expected error further outside. Here's a updated gist: https://gist.github.com/phiresky/621f8d8ed6eb00c9a0ddb47217eae38a https://gist.github.com/phiresky/621f8d8ed6eb00c9a0ddb47217e...
- phiresky 7y ago> in many cases being unable to import a JS library in that doesn't include typedefs Make sure you have "allowJs": true in your tsconfig.json. Otherwise, you'll have to create a minimal file with `declare module "xyz";` > I fail to see how TS can be considered a superset of JS You have to interpret all TS "errors" as warnings for that to be true (which they mostly are considering tsc still emits code regardless of non-syntax-error errors) > I just cannot take a type system seriously when workarounds like that are needed. Your problem really just seems to be that the type inference does not have magical powers. There is no other programming language that has union types as powerful as TypeScript (as in discriminating by whatever you want), but yeah when you do `return {x: 1, y: 2}` the compiler doesn't always know whether you mean to return an object where the type of x is `1` or where it is `number`. In some cases it could be better still, but doing this in general is just not possible.
- Dylan16807 7y agoThis doesn't need magic powers. All you have to do is treat literal values inside type descriptions differently.
- phiresky 7y agoInside of type descriptions, literal values are always treated as literals. The problem only appears when you declare a variable _without_ an explicit type like `const foo = {k: 'x'}`, which the compiler then infers to be of type `foo: {k: string}` and not `foo: {k: 'x'}`. Both of these are valid inferences, and it's impossible to know which one the user wants in the general case, which is why you have to explicitly say `const foo = {k: 'x' as const}`.
- Dylan16807 7y agoSo the example of `false | 'x' | 'y' | undefined` turning into string is invalid? I feel like I'm missing something.
- phiresky 7y ago
- jahewson 7y agoTypeScript 3.4 has const assertions which solve the type widening issue you describe.
- likeabbas 7y agoFor the comment about not being able to use JS libs that don’t have typedefs, this is why I wish Facebook’s Flow was more popular. You can use as much or as little of typing as you want and don’t need to to write typedefs for every JavaScript lib you wish to use. It also had static code analysis that could infer types.
- WorldMaker 7y agoTS can infer types in some cases (when the --allowJs option is on and the JS files are a part of the project, for instance), and it is on the roadmap to continue to make that stronger. TS also doesn't need typedefs for every JS lib. It now defaults to assuming any module import is type `any` if it doesn't have typedefs and can't infer from the JS files for whatever reason. In the case of the comment above the complication is using JS libs and also the compiler flag --noImplicitAny which causes an error on importing a module that defaults to type `any`. Making that an explicit `any` (with a `declare module "modulename";` in a .d.ts file) is all that is needed.
- AgentME 7y agoFlow won't infer types of arbitrary Javascript libraries. It will let you use them as untyped values, just like Typescript does when you set the allowJs option. (I've used Flow for a long time. I'm starting to transition a large codebase from Flow to Typescript, and I'd highly recommend anyone to just start with Typescript rather than Flow. Flow's type inference isn't all that useful, and causes Flow to be magnitudes slower than Typescript. In a large codebase, it can take Flow tens of seconds to minutes to react to a code change and show you errors or auto-completions in your code after typing in an editor, compared to practically instantaneous updates with Typescript.)
- deleted 7y ago[deleted]
- WorldMaker 7y ago> 'x' as 'x' As others point out, 3.4 added `as const` which is more powerful and more generic. > Add to that, in many cases being unable to import a JS library in that doesn't include typedefs, and I fail to see how TS can be considered a superset of JS. It's certainly sold that way, but it's clearly not totally true. TS+React requires --noImplicitAny, which cannot be worked around (it's labeled as being required due to a platform limitation). No implicit any just restricts the type system from falling back to `any`, it doesn't stop you from explicitly using `any`. Most places where you get a noImplicitAny error you can add an `as any` or a `: any` assertion nearby. Explicit `any` is still often useful, even if only as a TODO marker for that day you can search for `any` in the codebase and try to remove it. There's also `as unknown` or `: unknown` type assertions as another option for `any` in some case (`unknown` is stricter than `any` and implies you are going to be testing the value for runtime type information). Use an explicit `any` applies to modules too. That problem with importing a JS library but getting a noImplicitAny error can be solved with an explicit `any`. That's where one particularly useful real world case of `declare` comes in. Add a file called something like `modules.d.ts` (the `.d.ts` is a convention that it won't have actual TS code, just declarations) and then to add explicit `any` for any module you want to import is as simple as: declare module "modulename"; `declare` is how you write type definitions for JS code. The simplest declaration is that a module exists and could be anything. > I also find Microsoft's documentation of TypeScript to be very poor There's a better documentation site being built with better search and with a lot more interactive examples. I don't know when it might be expected to be released, but BUILD talks seemed to indicate that Microsoft is aware that Typescript's documentation hasn't kept up with its release cadence and needs love.
- maxdamantus 7y ago> especially now that they've added HKTs Hmm .. as far as I'm aware, they haven't added this yet: https://github.com/microsoft/TypeScript/issues/1213 https://github.com/microsoft/TypeScript/issues/1213 Note that higher-kinded types (though I'd rather call it "higher-kinded polymorphism" or something—type-level functions are not really types) would allow code similar to this: function foo<F, A>(f: (v: A) => F<A>, v: A): F<A> { return f(v); }
- Rapzid 7y agoMy biggest hope is that one day they will make type error formatting way, way, way better in VSCode. There are some gnarly type errors that take a lot of concentration to work out from that tiny, unformatted error text box :|
- platz 7y agoTS does not have HKT. The post simply described improved inference on types with several generic parameters.
- nsonha 7y ago"they've added HKT" no they did not, if they did it would've been a major news.
- deleted 7y ago[deleted]
- spion 7y agoSince this is the top comment, for anyone that got overly excited like I did - no, there are no HKTs in TypeScript yet unfortunately.