Hacker News new | past | comments | ask | show | jobs | submit login

There is a good reason for not doing this. Typescript would become some kind of runtime on top of JavaScript. A new language that compiles to JavaScript. Currently TS is only JavaScript with type annotations.

There are many languages that compile to JavaScript. Pick one of them and use it!

And I have the feeling, that people who want runtime typed Typescript would rather like to write Java/OOP style code instead of JavaScript. But JavaScript is a dynamically typed language, and that's also nice. Be happy with what you have!




Not necessarily.

Consider

    const fooType = generateTypeInfo!<Foo>();
Where `generateTypeInfo!` is a macro that expands to a JS object encoding the “Foo” type (e.g. if `Foo` a record type, `fooType` will be a record with its encoded field types). It still compiles to readable JavaScript.

What this macro does break is that TS = JavaScript with type annotations, and all you need to do to compile is remove those annotations (excluding enums, but they are all-but-deprecated and obsoleted by string unions) without even type-checking. Since now you also need to expand the `generateTypeInfo!`, which requires actually computing the structural type of `Foo` (and if you want any sort of nominal type metadata, that too).

And that’s still a problem because type-checking is slow, but removing the annotations is fast. If there’s a limited way to resolve types for these annotations which restricts the resolution scope, that would be a good candidate.

—-

There's also more issues with TypeScript having a separate runtime besides it not being JavaScript. TypeScript types are structural, so they currently are available at runtime: to determine an object’s type, inspect its structure. Anything more and you quickly run into non-trivial cases being literally impossible: if you want erased nominal information like whether a string is part of a string union, TypeScript’s type system is Turing complete; and if you want the nominal type name, implicit structural conversions mean that once a value leaves its annotated cast or definition it’s effectively undefined.


What if this macro could be expanded into valid typescript by preprocessor that runs before typescript compiler?


That's an option, and maybe even the ideal one: an extension which takes a TypeScript+macros file and converts it into TypeScript before feeding to the TypeScript compiler.

However, such a preprocessor will basically need to re-implement TypeScript's type checking. So either TypeScript must expose it via an API, or the preprocessor needs to itself implement a subset (which as mentioned, could also be faster)


This already exists. One example posted elsewhere in these comments is tst-reflect[1], with which you can write this:

    const fooType = getType<Foo>();
Which is exactly what you wanted, just as a plugin to TypeScript rather than built into the official compiler.

[1]: https://www.npmjs.com/package/tst-reflect


I'm not sure what `generateTypeInfo!<Foo>();` is supposed to be, but as far as I'm aware there is no way to execute anything in a JS engine from a type generic.


It's meant to represent a hypothetical TS compiler builtin that does codegen for you.


Ah, yeah I get it.


You can have both. This is the beauty of lisp - https://en.wikipedia.org/wiki/Homoiconicity

But in all seriousness you can gain reflection without a runtime. Just expose the types as data.

Seems foolish to not do something like this. Particularly when you look at the lengths so many devs have gone to try and replicate it.


I'm misunderstanding your reasoning here. TS is already a new (superset) language which compiles to JS. Runtime types solve the problem of maintaining two duplicate, separate type systems -- one for compilation, one for validating data. I'm not seeing how that's related to OOP. For instance, my preferred workaround lib for this problem, `io-ts`, leans hard on functional programming.


Yeah but just because types exist at compile time doesn't mean they exist at run time.

For instance with Generics in Java a List<String> is the same as a List<Integer> at run time, the compiler can enforce rules that let you add a String to one but not add a String to the other but the runtime has no idea. This has various negative consequences but it also let them retrofit generic collections on top of the old collection implementation in Java unlike the disaster in .NET where both had to coexist for years.

Similarly C compiles to machine code and in machine code there are just memory locations and registers, types are implicit in how you use those things but not spelled out explicitly. C++ does have RTTI but is one of the many open pits, like Exceptions, in C++.

Typescript types are the same way, once the compiler has done its type checking you know the code is going to behave according to the type system even if the types have been "erased".


Java's generic type information (aka parameterized types) is available at runtime through reflection.


Depends on the situation. If you just make an ArrayList<X> (for some specific class X) you can call getClass() on that object and see it is an ArrayList but you cannot find out about the X.

If you make an ArrayListX that is not generic but extends ArrayList<X> it is possible to see the type parameter that was extended, the same is true for implementations.

There are other kinds of type unerasure that you can use w/o the language supporting it explictly, such as you could pass the type parameter X.class into the constructor and put it in a field so that you could call some method like getContainedClass() and get a copy of X.

Now the introspection/reflection API does have support for talking about parameterized generic types that doesn't mean that information always exists at runtime.

I worked on a crazy project where I did a lot of fighting with ordinary types and parameterized types and unerasing types by rewriting the names of methods to avoid conflicts, if you have methods like

   A doSomethingTo(B)
   A doSomethingTo(C)
you can rewrite these to

   Expression<A> doSomethingToB(Expression<B>)
   Expression<A> doSomethingToC(Expression<C>)
but you can't have an overload that takes two differently parameterized expressions, this lets you write Java code in a lisp-like syntax that can be metaprogrammed on:

https://github.com/paulhoule/ferocity/blob/main/ferocity-std...

that project got me thinking a lot about the various ways types manifest in Java and I made the discovery that when you add Expression types you practically discover an extended type systems where there are many things that are implied by the existence of ordinary Java types.


I know how it works. The point of the topic was to have type information available, so we can use it for things like dynamic binding. No one asks for the runtime to be as strict as the type system (the compiler).

I like Venkat's presentations: https://www.youtube.com/watch?v=34oiEq9nD0M

BTW. I do not agree with the opinion in the topic. Since Typescript is compiled to something else (usually JS), I'd prefer Typescript to have proper annotations (there are "decorators") and compiler hooks and just use code generation for concerns like (de)serialization.


Emitting types would run counter to several of TypeScript's Design Goals [0]. In particular, it would violate:

> 3. Impose no runtime overhead on emitted programs.

> 9. Use a consistent, fully erasable, structural type system.

It's also explicitly called out as a non-goal in that doc:

> 5. Add or rely on run-time type information in programs, or emit different code based on the results of the type system. Instead, encourage programming patterns that do not require run-time metadata.

> 6. Provide additional runtime functionality or libraries. Instead, use TypeScript to describe existing libraries.

[0] https://github.com/Microsoft/TypeScript/wiki/TypeScript-Desi...


Emitting type information to some kind of separate common format wouldn't undermine most of these goals. There are already apparently dozens of tools that people can use for this task in a dozen different ways. Just have TS emit the data.

In this way there is no runtime overhead (point #3) -- it's pure data to be optionally consumed. They wouldn't have to prescribe any particular reflection module (point #6).

The only point this would violate is #5 but that is, of course, the point. We want them to reconsider because it's such a powerfully useful feature. And people are doing it on top of and outside of TypeScript where TypeScript would be best tool to give this information for TS users.


TypeScript dev lead here.

Today, you can already write a program using the TypeScript API to inspect all of this information to accomplish whichever scenario is at hand. The existence of all these tools, each with different opinions on design direction and implementation trade-offs, demonstrate that this is possible. Yet it's not demonstrated how those tools would benefit from reading from a data file as opposed to using the TypeScript API.

Something like api-extractor has different constraints from io-ts which has different constraints from typescript-schema. The API exists today and is apparently sufficient to meet all these tools' needs, yet what's proposed is a data file that can encapsulate any possible operation you might perform with that API.

Having TypeScript try to imagine, implement, and maintain a data file format that can satisfy _all_ of these tools' use cases, plus any future tool's use case, is a tremendous effort with no clear upside over using the existing API.

It's easy to gain support for the idea of "put the types in a file" because anyone reading that can imagine a straightforward implementation that just achieves their particular goals, but everyone's goals are different and the amount of overlap is not actually all that high when you look at the wide breadth of tools in the list. There's a very different amount of information you'd need to enable a type-aware linter (which is basically type info on every expression in the program!), as compared to a simple documentation generator (which might only need top-level declaration info).


Hard to argue with that! I wonder why this isn't considered a solution to the author of this site.


The author explicitly calls out the existence of all of these tools as being the problem. They seem to want a canonical version that TypeScript itself officially supports.


Situation: There are 14 competing type representation formats

TypeScript: We can write our own type file format!

Situation: There are 15 competing type representation formats


If typescript itself defines a format, it's hard to imagine that the majority of people won't switch to it.

Though while there's a lot of type-data tools, are there actually a lot of type-data formats out there right now? I've been using zod, and it doesn't have one. You have to use code to tell it about a type. Someone did make a library to let zod load types from pure data, but the format used there is... typescript source code.


It would violate #9 no matter what implementation you use, and #9 is absolutely essential to tools like ESBuild, Deno, and Bun, which all rely on the assumption that they can nearly instantly strip out the type annotations and execute the code as JS.


You'd still be able to execute the code as JS. Your library that uses type reflection wouldn't work anymore but that's true of all these solutions.


No, it's not. I can take a library that uses zod, strip the annotations, and it will execute perfectly anywhere I want to run it.


That's because Zod doesn't use type annotations as input. We're talking about wanting to use type annotations as input so that we don't have to make our code as ugly as Zod makes it.


If some design goals are stupid they must be changed. It's not god word. It's human word. And this human was wrong.


This isn't just some of the design goals, type erasure is the fundamental guiding principle of TypeScript that allowed it to become successful. Throwing it away because it complicates certain programming patterns should not be done lightly.


In my opinion they are not stupid, they make a lot of sense. And many people seem to agree and use typescript. If you don’t agree, use something else. There are many alternatives, pick something that is „smart“ in your opinion.


Unfortunately i can't. Like most of the people here i work in a company, with other people and i can't chose $language that is less dumb for obvious reasons. Typescript IS the only practical solution for web development at scale. I'm stuck with the design philosophy of a guy that's not facing my problems and obviously doesn't care.


Small ot, if you like io-ts, @effect/schema is the spiritual successor from the same ecosystem.

The fp-ts, io-ts author Giulio Canti has decided that the best way to proceed was to merge the two communities of fp-ts and effect, even though it's his work that is mostly known with effect being much more niche.

It's essentially io-ts squared in its powers. The amount of things you get from schemas are endless, not just decoders and encoders but also apis, lenses, constructors, and many other things.

It's obviously heavily fp-leaning, albeit I would say effect systems such as effect, based on Scala's ZIO, are much more approachable than haskell/purescript-like languages.


> TS is already a new (superset) language which compiles to JS.

Yes, but also no. The point is that it doesn't really add more semantics over plain JS. You can erase all the type annotations (well, replace with "any") and it'll do the same thing if you ignore the type checking.

If there's any "transformation" from TS to different-looking JS[0], it's mostly just syntactic embellishment (think LISP macros and such), not semantic.

[0] Not sure if there is, tbh


For any sufficiently large project, dynamically typed languages are not nice, they are a series of interweaved disasters consisting of constantly tripping over what you thought was your feet but is actually NaN and trying to fall forward rather than collapsing entirely.

In every workflow I've seen, TypeScript is already a language that gets compiled to JS. It might as well take full advantage.


Typescript is not really compiled to JS, only the type annotations are removed.

To make dynamically typed languages nice for bigger projects we have typescript for type annotations (it's does not make it a statically typed language!). It is there, it is used, it scales and it works. If your type script project "collapses entirely", it's probably not the languages fault.


TypeScript is not JavaScript with type annotations. One of the modes, may be. TypeScript supports emitting old JS constructions which look nothing like original typescript code. So it's more like type annotations + babel. Adding one more thing to this set. Extremely useful thing. I think it's a good idea. I miss it. It's crazy that I can't JSON.parse string into typed structure safely. Every other language can do that.


No, this is not crazy, this is the design goal of TypeScript.

If you want a statically and runtime typed language in the browser, you should not use typescript. Use Kotlin, Rescript, …


You can easily parse json into types using something like typebox. Not only does this work, it works much better than many other languages. Have fun trying to represent even a simple patch request, or discriminated union, in something like c#


You can use Kotlin and compile to JavaScript. It's type system is better and the standard library greater scnr


This is the way. Best of both worlds although using js libraries is kind of a pain


Exactly. Pick whatever language you like. There are many more options.


Kotlin's type system works great for interop with a nominally typed lang like java but for interop with a dynamically typed lang like js and myriad of its libs not designed with type safety in mind, structural typing is far more convenient.


The reason we want typescript to have java-like features is because we work on teams which have decided, outside of our control, that we are going to write our backend in JS/TS simply because it's easier for the bootcamp grads to transition to backend since they already know JS on the frontend. If we wrote our backend in Java, it would require the bootcamp grads to learn a new language, which they're not prepared to do. Choosing typescript or javascript on the backend is not about choosing the right tool for the job (because they are objectively not the right tool for backend development). It is mostly about minimizing the cost of our labor sourcing

As such, RTTI would make typescript go from a compromise language (we use it because we are stuck in the JS ecosystem, not because it's a good tool) to a legitimately useful backend language (We use it because it has the right features for the use-case)

If I could choose what language we use on the backend, I would leave the JS ecosystem entirely and write everything in .Net. But it's difficult to find .Net developers since every bootcamp these days produces react developers


So you want TypeScript to be Java, but you don't want to use Java, because your developers only know TypeScipt. Sounds like an unsolvable problem.


The solution is to pile features onto typescript that were never intended to be there, the same way we have done with Javascript, HTML, CSS, and HTTP. None of these things are pure, and all of them have been ravaged by competing interests and committees who were all making financially driven decisions when writing the standards.

It would be best to abandon javascript and typescript as backend languages. I don't mind them on the frontend because a typescript compiler is useful when the application you're writing is originating data and not receiving it. And javascript does DOM manipulation admirably. However, we live in a world where humans are more expensive than computers. If I had my way, we'd write C# backends and Angular/Typescript frontends, but that's not the world we live in. Everything has to be JS because most people employed as "software engineers" are not truly very good at their jobs


I, on the other hand, would pick TS over .NET or Java any day. But I am sure you will conclude that it can only be because I am no good at my job.


No, I'm sure you have your reasons, but deserializing and validating is much more painful in typescript than it is in .Net or Java so I'm not sure why you would choose that


> Typescript would become some kind of runtime on top of JavaScript. A new language that compiles to JavaScript.

Typescript isn't a runtime on top of JavaScript, but it very much is "a new language that compiles to JavaScript".


Yes, but it's special in that it compiles by erasure. (Mostly -- the cases where it doesn't are considered historical mistakes.)


Ok. And why is that important? So one could meticulously add typing to your JavaScript application, and then strip it all out with a regex in order to deploy? What real-life benefit does 'compile by erasure' offer?


That's essentially what all the fast transpilers do -- esbuild, babel etc. They don't typecheck the code at all, they just parse it and strip out the type annotations.

This means that you can run and test code that doesn't typecheck yet -- very useful during development. Also, production builds can run the typechecker in parallel with other build steps. That's how we optimized our build system at work.

But essentially, it's a design choice by the TypeScript team. They've decided that TypeScript shouldn't add runtime features on top of JavaScript, but instead should simply type JavaScript, and I think this has contributed to the success of TypeScript.


I think the point is that TypeScript isn't really "a new language". It's an existing language with an additional feature. The fact that TS maintains the promise that types can be stripped means that TS is upholding a contract that it will not stray from Javascript. It can't, for example, add new language features.


typescript enums exist and are compiled to javascript (other than const enums)


That TypeScript fails to live up perfectly to its design goals doesn't mean it should adopt additional anti-features.


And that's one of the features of typescript that really sucks. String union types work so much better.


Why does typescript enum really suck?


There’s a ton written about this if you search for “typescript enums.”

I just finished removing them from a major project. Here’s a few of my personal notes:

1. They don’t play well with duck typing. (Eg. show me a subset of an enum)

2. They require an import every time you want to utilize them.

3. They are pretty wordy compared to union strings.

4. Their string value version encourages misuse as a key-value pair.

5. Unless you use the string value version, they suck to debug because logs just show 0,1,2,3.


Another one: they conflate Type and (Locator) Instance in a unique way that just about nothing else in TS does. Those are two very different things with the same name with Typescript's (antiquated) enums.

There are too many ways to accidentally import/redeclare/rescope the Type of an enum so that TS "knows" the Type, but because that type (generally) has the same "name" as the most likely (Locator) Instance it assumes the same access applies leaving runtime errors behind when that Instance isn't actually imported/available. Typescript has no easy way to tell the difference between access to the Type isn't access to the (Locator) Instance (nor vice versa). Reasoning about those runtime errors or preventing them is additionally tough for people too because of the same "name" problem for two different things.

This is something that's painfully hard to avoid in cases where you are trying to encapsulate an API's Types separate from its imports/exports because they might be introduced or manipulated at runtime (plugins, sandboxes, proxies, etc). Unfortunately, this is also too easy to accidentally do even when you aren't intentionally doing something complicated like that (trying to generate automated .d.ts files in a bundling toolchain, for example, when APIs are in the boundary space between unintentional public API and internal tree-shaking or optimized symbol renaming).


Thanks for putting into words why they just "feel" so wrong.


Let's turn it around, union types are so much easier to use and so much more powerful. Enums have only a small subset of the features, are not compatible to JavaScript code and are hard to understand (read the docs about type script enums and you will see).


Typescript enums emit a really weird object at runtime

enum CheckboxState { On; ParentOn; Off; }

Becomes

{ [0]: “On”, “On”: 0, [1]: “ParentOn”, “ParentOn”: 1, [2]: “Off”, “Off”: 2 }

So things like Object.keys give bizarre results. It’s done this way so you can use the name or the value as an index.


To be clear, this kind of structure is only emitted for numeric enums. String enums with explicitly declared static values are roughly equivalent to the equivalent Record<string, string> (runtime) and a corresponding type T[keyof T] (type check time).

IME, most of the complaints about enums apply only to numeric ones.

The major exception to that AFAIK is the fact that enum members of any type are treated as nominally typed (as in A.Foo is not assignable to B.Foo even if they resolve to the same static value). I am among the minority who consider this a good thing, but I recognize that it violates expectations and so I understand why my position isn’t widely shared.


My understanding has long been that web developers have decided to turn Javascript into C#, one proposal at a time.


Dynamically typed means I can look up the type of an object at runtime. JavaScript lets us do this with classes/prototypes. Since TypeScript adds types and enums and interfaces, why can’t it let us look up those at runtime too? It would seem to fit with the way JS works.


> dynamically typed language, and that's also nice

what is nice about a dynamically typed language? like, seriously, can you list some reasons for a dynamically typed language to exist?


Types can get complicated. To an extent that code gets extremely overcomplicated by using a typed language. Typed languages encourage to use many layers of DTOs and models.

With dynamic languages you can just code, without being held back by a OOP type system, but it can get very complicated to understand what the typed of you parameters and return values actually are.

Typescript gives you the best of both worlds.


Peter Norvig summed it up in one of his presentations https://norvig.com/design-patterns/design-patterns.pdf

"Dynamic Languages have fewer language limitations Less need for bookkeeping objects and classes Less need to get around class-restricted design. Study of the Design Patterns book: 16 of 23 patterns have qualitatively simpler implementation in Lisp or Dylan than in C++ for at least some uses of each pattern[...]"


ReasonML


ReasonML suffered tremendously when the community split and went off to do ReScript. I don't think either have recovered the original luster.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: