[HN Gopher] TypeScript please give us reflection/runtime types
___________________________________________________________________
TypeScript please give us reflection/runtime types
Author : decs
Score : 303 points
Date : 2023-07-07 19:36 UTC (1 days ago)
(HTM) web link (github.com)
(TXT) w3m dump (github.com)
| dontlaugh wrote:
| Maybe controversial, but I think code generation is fine. It work
| well enough in Go or C#.
| vbezhenar wrote:
| The one downside of this proposal is faster typescript processors
| like esbuild. Right now they don't care about types, they just
| strip them from the code. It allows them to be very fast and
| provide fast feedback loop.
|
| With this proposal those tools must become extremely more
| complicated and probably they will just be as slow as tsc.
| [deleted]
| shadowgovt wrote:
| Normally I'd say "hard pass." I'm pretty comfortable with static
| types being a model where the compiler throws the type
| information away at compile time, so I can type with abandon
| without worrying that I'm going to impact runtime performance in
| my generated code.
|
| ... TypeScript may be one exception to my rule of thumb. It's
| already compiling down to JavaScript, so you're already paying a
| performance tax in having your code run in an interpreted
| language. The question should be "how much more of a performance
| tax are you paying annotating things with type runtime metadata?"
| mrkeen wrote:
| Why not just let the compiler build the deserialiser out of the
| runtime information during compile time, and then throw out all
| the types?
| thanksgiving wrote:
| I'm not an expert or anything but I sincerely feel like this is
| barking up the wrong tree.
|
| To anyone who works on typescript/JavaScript outside the context
| of a web browser, you DO NOT matter as far as I am concerned. If
| I was in control of JavaScript/typescript, I'd give zero
| attention to your demands. You are not my top priority. Go away.
|
| I feel like we are losing focus here. Should JavaScript be an all
| purpose language? Yes. Should we focus on making a web browser
| context, also yes.
|
| The reason I led with this is that I think it is up to the
| JavaScript folks and the web browser vendors to include support
| for this before typescript can change what it emits. Ideally, I'd
| say wait a couple of years after browser vendors include support
| to implement this in typescript.
| Eric_WVGG wrote:
| I don't understand this comment at all. I mostly work on
| client-side front-end and am absolutely desperate for better
| type generation. Zod is making me want to jump out a window
| right now.
| thanksgiving wrote:
| My comment is simple. Don't add anything to JavaScript if it
| does not help in the context of a web browser. Don't add
| anything to typescript emitted output if it isn't in
| JavaScript.
| colinmcd wrote:
| Something like typebox (that's designed to map one-to-one
| onto JSON Schema) may be better if codegen is a priority.
| klysm wrote:
| So this is asking for a completely different language?
| RussBrown00 wrote:
| unpopular opinion, but this just fosters my opinion that TS
| people just don't understand JS at all. I wish TS would just die
| (another currently unpopular opinion). If you want runtime type
| safety, go use a different language and stop trying to ruin JS,
| it's already the messed up crazy cousin and it doesn't need to be
| in-bread anymore, it's perfect the way it is!
| imbnwa wrote:
| Only writer I know who discusses pure JS patterns that embrace
| those its dynamism is Raganwald Braithwaite[0]. For example,
| most people I know would call this hacky code[1], when this is
| just design patterns in idiomatic, non-Java/C#-constrained
| JavaScript. The truth, most devs desire intellisense and/or
| theorycrafting that comes with static types and compilers.
|
| [0]https://raganwald.com/
|
| [1]https://raganwald.com/2014/04/10/mixins-forwarding-
| delegatio...
| DanRosenwasser wrote:
| Hey all, TypeScript PM here.
|
| I understand the desire here. Runtime type checking is often
| necessary for data validation, and we can see lots of libraries
| developed to help fill the gap here. But I think the fact that
| there are so many libraries with different design decisions is
| pretty indicative that this is not a solved problem with an
| obvious solution. We knew this going into the early design of
| TypeScript, and it's a principle that's held up very well.
|
| What have been happy to find is that we've grown TypeScript to be
| powerful enough to communicate precisely what runtime type-
| checking libraries are actually doing, so that we can derive the
| types directly. The dual of this is that people have the tools
| they need to build up runtime type validation logic out of types
| by using our APIs. That feels like a reasonable level of
| flexibility.
| 0xb0565e487 wrote:
| I'm relatively new to programming and had a question about
| TypeScript's functionality. Is there any specific reason why
| TypeScript doesn't allow for the creation of custom and
| intricate data types? For example, I'm unable to define a
| number type within a specific range, or a string that adheres
| to a certain pattern (like a postal code).
|
| I'm imagining a language where I could define a custom data
| type with a regular function. For instance, I could have a
| method that the compiler would use to verify the validity of
| what I input, as shown below:
|
| function PercentType(value: number) { if (value > 100 || value
| < 0) throw new Error(); return true;
|
| }
|
| Is the lack of such a feature in TypeScript (or any language) a
| deliberate design decision to avoid unnecessary complexity, or
| due to technical constraints such as performance
| considerations?
| lolinder wrote:
| TypeScript actually does have some support for the kinds of
| types you're suggesting. For example, a US postal code can be
| defined like so: type PostalCode =
| `${Digit}${Digit}${Digit}${Digit}${Digit}`; type
| Digit = '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' |
| '9';
|
| You could trivially define a `parsePostalCode` function that
| accepts a string and yields a PostalCode (or throws an error
| if it's the wrong format).
|
| Ranges like percent are much trickier--TypeScript would need
| to compute the return type of `Percent + Percent` (0 <= T <=
| 200), `Percent / Percent` (indeterminate because of division
| by zero or near-zero values), and so on for all the main
| operators. In the best case scenario this computation is very
| expensive and complicates the compiler, but in the worst case
| there's no clear correct answer for all use cases (should we
| just return `number` for percent division or should we return
| `[0, Infinity]`?).
|
| In most mainstream programming languages the solution to this
| problem is to define a class that enforces the invariants
| that you care about--Percent would be a class that defines
| only the operators you need (do you really need to divide
| Percent by Percent?) and that throws an exception if it's
| constructed with an invalid value.
| n2d4 wrote:
| _> Is the lack of such a feature in TypeScript (or any
| language) a deliberate design decision to avoid unnecessary
| complexity, or due to technical constraints such as
| performance considerations?_
|
| It makes a lot of things impossible. For example, if you
| defined two different types of ranges, OneToFifty and
| OneToHundred similarly to your PercentType above, the
| following code would be problematic: let x:
| OneToFifty = <...>; let y: OneToHundred = <...>;
| y = x;
|
| Any human programmer would say the third line makes sense
| because every OneToFifty number is also OneToHundred. But for
| a compiler, that's impossible to determine because JavaScript
| code is Turing-complete, and so it can't generally say that
| one is certainly a subset of the other.
|
| In other words, any two custom-defined types like that would
| be unassignable from and to each other, making the language
| much less usable. Now add generics, co-/contravariance, type
| deduction, etc., and suddenly it becomes clear how much work
| adding a new type to the type system is; much more than just
| a boolean function.
|
| That said, TypeScript has a lot of primitives, for example,
| template string types for five-digit zip codes:
| type Digit = '0' | '1' | '2' | '3' | <...> | '9';
| type FiveDigitZipCode =
| `${Digit}${Digit}${Digit}${Digit}${Digit}`;
|
| (Actually, some of these are Turing-complete too, which means
| type-checking will sometimes fail, but those cases are rare
| enough for the TS team to deem the tradeoff worth.)
| 0xb0565e487 wrote:
| Thanks :)
| jerf wrote:
| It's the fundamental programming language design conundrum:
| Every programming language feature looks easy in isolation,
| but once you start composing it with everything else, they
| get hard. And hardly anything composes as complexly as
| programming languages.
|
| There's sort of a meme where you should never ask why
| someone doesn't "just" do something, and of all the people
| you shouldn't ask that of, programming language designers
| are way, way up there. Every feature interacts not just
| with itself, not just with every other feature in the
| language, but also in every other possible combination of
| those features at arbitrary levels of complexity, and you
| can be assured that someone, somewhere out there is using
| that exact combination, either deliberately for some
| purpose, or without even realizing it.
| bern4444 wrote:
| Some of this is possible in the type system like a range:
| From stackoverflow:
| https://stackoverflow.com/questions/39494689/is-it-
| possible-... type Enumerate<N extends number,
| Acc extends number[] = []> = Acc['length'] extends N
| ? Acc[number] : Enumerate<N, [...Acc, Acc['length']]>
| type NumberRange<F extends number, T extends number> =
| Exclude<Enumerate<T>, Enumerate<F>> type
| ZeroToOneHundred = NumberRange<0, 100>
|
| One limitation is that this has to be bounded on both ends so
| constructing a type for something like GreaterThanZero is not
| possible.
|
| Similarly for zip codes you could create a union of all
| possible zip codes like this: type USZipCodes
| = '90210' | ...
|
| Often with the idea you have in mind the solution is to
| implement an object where the constructor does a run time
| check of the requirements and if the checks pass instantiate
| the instance and otherwise throw a run time error.
|
| In functional programming this is often handled with the
| Option which can be thought of as an array with exactly 0 or
| 1 elements always. 0 elements when a constraint is not met
| and 1 element when all constraints are met.
|
| This [0] is a library I wrote for JS/TS that provides an
| implementation of Options. Many others exist and other
| languages like Rust and Scala support the Option data
| structure natively.
|
| [0] https://github.com/sbernheim4/excoptional
| 0xb0565e487 wrote:
| Interesting, thanks!
| hn_throwaway_99 wrote:
| > or a string that adheres to a certain pattern (like a
| postal code).
|
| This is somewhat supported with template literal types,
| https://www.typescriptlang.org/docs/handbook/2/template-
| lite.... However, there are good technical/performance
| reasons they don't support, for example, plain regexes -
| there is a HUGE discussion on this topic here,
| https://github.com/microsoft/TypeScript/issues/6579.
| wk_end wrote:
| This is a feature some (experimental) programming languages
| have - look into dependent types. The long-and-short of it is
| that it adds a lot of power, but comes at an ergonomic cost -
| the more your types say about your code, the more the type
| checker needs to be able to understand and reason about your
| code, and you start to run up against some fundamental limits
| of computation unless you start making trade-offs: giving up
| Turing-completeness, writing proofs for the type checker,
| stuff like that.
|
| Another interesting point of reference are "refinement
| types", which allow you to specify things like ranges to
| "refine" a type; the various constraints are then run through
| a kind of automated reasoning system called an SMT solver to
| ensure they're all compatible with each other.
| 0xb0565e487 wrote:
| Awesome, thanks for the pointers!
| golergka wrote:
| You can do exactly this with io-ts and fp-ts: https://gist.gi
| thub.com/golergka/64b06f711e4cb07c67367bdace7...
| fouc wrote:
| thoughts on the other poster asking for type info to be kept
| around in the Class objects after compilation?
| scotty79 wrote:
| Maybe official preprocessor plugins for TypeScript compiler
| could help?
|
| I understand that everybody who needs it can already put their
| own preprocessor that generates runtime objects from type
| information before the code is passed to tsc for compilation.
|
| But the effort is inconsistent and distributed.
|
| If TypeScript officially supported pluggable preprocessor and
| plugin ecosystem for it some good solutions might get
| discovered.
| andix wrote:
| What's next? An official TypeScript UI framework, will it be
| Vue, React, Next.js or Svelte? An official TypeScript date
| library?
| scotty79 wrote:
| What's the point of any of those?
|
| Generating code based on type annotations is frequently
| requested feature directly related to the core feature of
| TypeScript which is type system.
| pjmlp wrote:
| This is the curse of guest languages, after the initial
| adoption pain everyone wants idiomatic libraries and
| pretends the underlying platform doesn't exist.
|
| Until they hit a roadblock caused by a leaky abstraction,
| that proves them otherwise.
| andix wrote:
| Type script does a very good job not to hide the
| underlying platform. In it's essence it is just a
| development time linter and does not interfere with the
| JavaScript runtime at all (except enums).
|
| And I think that's actually the reason why it won the
| competition against Googles Dart. They even used
| Microsofts TypeScript for Angular instead of their own
| language Dart.
| pjmlp wrote:
| Indeed, and that is why the Typescript team is against
| any feature that steps away from that relationship.
| clansimus wrote:
| I'll take an official WYSIWYG editor while we're at it
| mirekrusin wrote:
| Microsoft hosted MacroScript as TypeScript plugin or toplevel
| wrapper would solve this problem.
|
| You just need to spark it, community will help maintaining it.
|
| It would solve all codegen needs from generating clients to
| runtime type assertions and many interesting problems in
| between.
| gizmolo wrote:
| Future ECMAScript iterations will include types and then
| hopefully TypeScript can take advantage of that to implement
| reflection and other cool things
| MrResearcher wrote:
| Typescript already kind of has it, it's just they got it
| backwards.
|
| One needs to define their types as a const, like this one:
|
| ``` [{ type: Number, name: "field1" }, { type: String, name:
| "field2" }] as const ```
|
| and then use Typescript magic to convert it into the fully
| fledged Typescript type.
|
| Yes, it's annoying, but it's more flexible.
| epolanski wrote:
| This is as useful as vue 2's prop types: very little.
| gloryjulio wrote:
| U just need a library to enable this to reduce the boilerplate.
| I don't see how that's impossible
| asherah wrote:
| this wouldn't work for interfaces, or more complicated types
| noobdev9000 wrote:
| Modern TS code is hard for me to read and reason, and I use Rust,
| Haskell, C++. Way too complex for my brain
| epolanski wrote:
| Well, as always, this really depends on our the code we read
| and our background.
|
| TypeScript's type system is indeed way more verbose than
| Haskell's.
| chmod775 wrote:
| Since it's not explicitly listed there, I feel I should shout out
| David Blass and his incredibly cool ArkType project[1]. It's
| typescript wizardry.
|
| He sometimes (used to?) streams himself working on twitch and
| it's a really comfy place to hang out.[2]
|
| [1] https://github.com/arktypeio/arktype
|
| [2] https://www.twitch.tv/arktypeio
| yewenjie wrote:
| How complete is arktype actually? The README says a lot of the
| syntax is yet to be developed, and don't even get me started on
| the horror of the docs site.
| chmod775 wrote:
| Until a short while ago it was still very much work in
| progress.
|
| Right now a lot of work seems to be going into getting a 1.0
| release out of the door (the current one is still marked
| alpha).
| BiteCode_dev wrote:
| While type hints in Python are not as nice as typescript
| annotations, I understand their plea, because once thing Python
| does better is runtime introspection.
|
| Which means we can have dataclass, pydantic, typer and fastapi
| all generating their stuff from regular type hints. No need for
| special syntax or functions. And that's super nice.
| orthecreedence wrote:
| Reflection on a language that compiles to javascript? I don't get
| it. Why are people so allergic to macros?? Every stinking
| language that has compiled to JS has had a chance to do macros
| but they don't. Coming from many years spent in the lisp world,
| metaprogramming is this incredible feature that, in my
| experience, completely negates the need for runtime reflection
| while adding many other possibilities as well. I get it...the
| syntax is way harder when your language isn't basically a pre-
| parsed AST. But rust does it.
|
| Seems like a huge waste.
| kazinator wrote:
| TypeScript imposes more detailed typing onto JS objects. It
| makes sense to ask the question: can the type imposed by TS
| onto a JS object be reified as a run-time object?
|
| It could be, but maybe the use cases for that can be solved in
| another way that don't require the type system at run-time.
|
| The good news is that static type is, well, static. All the
| objects of the same type can share a pointer to the same type
| metadata. The overhead is thus not necessarily huge: one extra
| property to initialize when an object is created.
|
| The generated TS code has to carry the run-time support
| routines for the type stuff though.
| tinideiznaimnou wrote:
| Preach, brother!
|
| If only ECMAScript had a native macro system, TypeScript could
| be just a library - and I would have zero problems with its
| existence.
|
| You're probably familiar with the following anecdote:
|
| >Eich originally joined intending to put Scheme "in the
| browser",[4] but his Netscape superiors insisted that the
| language's syntax resemble that of Java.
|
| JavaScript became the language that we all love to hate due to
| political, not technical reasons. Yet people look at me funny
| when I call out the TS/React monoculture as the blatant
| corporate power grab that it is.
| kazinator wrote:
| If ECMAScript had macros such that TypeScript could be a
| library, people would still ask the question: can we attach
| the type object manipulated by the library to the objects?
|
| If ECMAScript had macros, page load times would skyrocket.
| ECMAScript and/or browsers would have to define a way to
| distinguish JS-with-macros files and embedded scripts from
| plain old JS that doesn't require expansion.
|
| Any JS code bases with complicated macros that take time and
| memory to expand would have to be expanded by the developer
| and shipped expanded. So, back to the same model as
| TypeScript.
| tinideiznaimnou wrote:
| You raise fair points.
|
| >So, back to the same model as TypeScript.
|
| I beg to differ. There's a world of difference between:
|
| 1. a standardized _feature_ with an official spec, which is
| subject to debate among the community, and of which it 's
| viable to have multiple competing implementations; vs
|
| 2. a proprietary _product_ that presents itself to be a
| superset of the core language (but isn 't), is peddled by a
| single multinational, has a single implementation designed
| by an unaccountable elite in the employ of said
| multinational, which just so happens to be the original
| Dumbing Down The Computer Company
|
| >If ECMAScript had macros, page load times would skyrocket.
|
| >ECMAScript and/or browsers would have to define a way to
| distinguish JS-with-macros files and embedded scripts from
| plain old JS that doesn't require expansion.
|
| If you introduce them today by way of a polyfill, maybe.
| I'm not necessarily advocating for introducing native
| macros to ECMA-262 at the present juncture. But I can't
| help speculating what would've happened if they had caught
| on.
|
| If they were introduced at the point in history when people
| were first realizing transpilers were a thing; or designed
| into the language from the start; or a pre-existing
| metaprogramming-aware language was used in browsers,
| instead of Eich designing in 10 days an entire new language
| under the constraint that it also has to be an ad for Java
| - who knows where we would be as a civilization? What do
| you think?
| vbezhenar wrote:
| What're you talking about? JS has had macros since the dawn of
| this world. Just eval(anything). For example eval(tsc("function
| f(x: number): number { return x + 1}")). Easy!
| eyelidlessness wrote:
| This comes up so often, and every time I wonder how much the
| people who want it have a shared understanding of what it is they
| want. I realize this is reiterating a point that TS team members
| have already made, but I'm coming from a different perspective:
| actually having built higher level tooling on top of one of the
| solutions mentioned in the post, and having built a fairly mature
| prototype of another solution from the ground up to address
| problems none of the existing offerings solve.
|
| My use case isn't particularly outlandish, I can even imagine it
| being non-niche if I ever get around to publishing it. But it's
| also pretty far afield from any of the existing solutions:
| platform-agnostic documentation as a core/foundational principle.
|
| My original work implemented JSON Schema documentation on top of
| io-ts, and added some runtime and type level functionality to
| support that. But my vision for a hypothetical successor is that
|
| - the documentation standard itself (whether JSON Schema,
| OpenAPI, or any other underlying format) should be the underlying
| primitive
|
| - the underlying format should be composable to produce APIs
| which are just as user friendly (ie users shouldn't have to muck
| around with the doc format unless they have a really specific use
| case, and even then they should still be able to compose the
| parts they're not concerned with)
|
| - no additional build time codegen tooling: define a schema once,
| its type is inferrable from the definition without a compiler
| extension or IDE config, its documentation is a plain function
| call executable in any standard runtime to resolve what's
| composed
|
| This is all very achievable in TypeScript as-is. But it's way out
| of scope from what I imagine any first class thing would or
| should be. And sure, if such first class thing existed, that
| wouldn't make my thing any less achievable. But here's what I
| think it would do:
|
| 1. Disincentivize usage by users who prefer the first class thing
| with type syntax as the primary authoring mechanism.
|
| 2. Incentivize me to cave on tooling to accommodate that.
|
| 3. Piss people off because "we wanted less tooling not more".
|
| 4. Recurse into this same debate.
|
| I'd personally prefer the status quo. Even if that means
| rehashing the same debate every few months, at least it's at a
| recursion depth that's manageable and doesn't have countless
| other sets of unintended consequences.
| paulddraper wrote:
| Better title: TypeScript please give us reflection/runtime types
|
| AFAIK the best current solution is emitlDecoratorMetadata. [1]
|
| [1]
| https://www.typescriptlang.org/tsconfig#emitDecoratorMetadat...
| dang wrote:
| Ok, we'll add that bit to the title above. Thanks!
| nsonha wrote:
| It would be nice but the problem is not that big of a deal if you
| use any of the community libraries they mentioned.
| rcme wrote:
| There are good typed deserialization libraries. Personally, I
| prefer runtypes but there are options.
| decs wrote:
| Agree, zod, arktype, and typia are impressively easy to use!
| Though, I feel that this environment created a different
| problem of spreading developers into multiple solutions. So for
| lib devs like me, we end up having to choose between coupling
| with a single validation lib or managing support for multiple
| (like tRPC does). To address that, I ended up doing a reusable
| lightweight lib that wraps that logic for supporting multiple:
| https://typeschema.com
| code_biologist wrote:
| Thanks for the lib! I'm always charmed to come across such a
| concrete example of the fundamental theorem of software
| engineering: "We can solve any problem by introducing an
| extra level of indirection."
| andix wrote:
| 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!
| shortrounddev2 wrote:
| 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
| andix wrote:
| 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.
| shortrounddev2 wrote:
| 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
| kristiandupont wrote:
| 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.
| shortrounddev2 wrote:
| 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
| twelve40 wrote:
| > 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?
| andix wrote:
| 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.
| Barrin92 wrote:
| 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[...]"_
| fzeindl wrote:
| You can use Kotlin and compile to JavaScript. It's type system
| is better and the standard library greater _scnr_
| andix wrote:
| Exactly. Pick whatever language you like. There are many more
| options.
| booi wrote:
| This is the way. Best of both worlds although using js
| libraries is kind of a pain
| lf-non wrote:
| 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.
| adjav wrote:
| My understanding has long been that web developers have decided
| to turn Javascript into C#, one proposal at a time.
| asherah wrote:
| typescript enums exist and are compiled to javascript (other
| than const enums)
| lolinder wrote:
| That TypeScript fails to live up perfectly to its design
| goals doesn't mean it should adopt additional anti-features.
| andix wrote:
| And that's one of the features of typescript that really
| sucks. String union types work so much better.
| moystard wrote:
| Why does typescript enum really suck?
| Waterluvian wrote:
| 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.
| WorldMaker wrote:
| 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).
| andix wrote:
| Thanks for putting into words why they just "feel" so
| wrong.
| graypegg wrote:
| 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.
| eyelidlessness wrote:
| 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.
| andix wrote:
| 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).
| joshmarinacci wrote:
| 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.
| Arainach wrote:
| 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.
| andix wrote:
| 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.
| vbezhenar wrote:
| 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.
| andix wrote:
| 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,
| ...
| mrloba wrote:
| 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#
| whalesalad wrote:
| 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.
| armchairhacker wrote:
| 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.
| baxuz wrote:
| 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.
| __jem wrote:
| It's meant to represent a hypothetical TS compiler builtin
| that does codegen for you.
| baxuz wrote:
| Ah, yeah I get it.
| scotty79 wrote:
| What if this macro could be expanded into valid typescript by
| preprocessor that runs before typescript compiler?
| armchairhacker wrote:
| 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)
| matt_kantor wrote:
| 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
| random_mutex wrote:
| ReasonML
| dimitrios1 wrote:
| ReasonML suffered tremendously when the community split and
| went off to do ReScript. I don't think either have recovered
| the original luster.
| [deleted]
| paxys wrote:
| > 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".
| codeflo wrote:
| Yes, but it's special in that it compiles by erasure. (Mostly
| -- the cases where it doesn't are considered historical
| mistakes.)
| vanviegen wrote:
| 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?
| codeflo wrote:
| 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.
| johnfn wrote:
| 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.
| a_wild_dandan wrote:
| 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.
| PaulHoule wrote:
| 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".
| einrealist wrote:
| Java's generic type information (aka parameterized types)
| is available at runtime through reflection.
| PaulHoule wrote:
| 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.
| einrealist wrote:
| 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.
| Quekid5 wrote:
| > 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
| lolinder wrote:
| 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...
| wvenable wrote:
| 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.
| lolinder wrote:
| 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.
| wvenable wrote:
| 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.
| lolinder wrote:
| 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.
| wvenable wrote:
| 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.
| RyanCavanaugh wrote:
| 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).
| wvenable wrote:
| Hard to argue with that! I wonder why this isn't
| considered a solution to the author of this site.
| lolinder wrote:
| 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.
| RyanCavanaugh wrote:
| 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
| Dylan16807 wrote:
| 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.
| vbezhenar wrote:
| If some design goals are stupid they must be changed. It's
| not god word. It's human word. And this human was wrong.
| lolinder wrote:
| 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.
| andix wrote:
| 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.
| chucklenorris wrote:
| 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.
| epolanski wrote:
| 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.
| benatkin wrote:
| Please give me an integer type.
|
| Just kidding, but I'm not pushing for JavaScript anymore. If
| rust/WASM works as well, I say go with that.
| hajile wrote:
| JS added BigInts quite a while ago.
| [deleted]
| vbezhenar wrote:
| Yet JSON.parse("9007199254740993") returns 9007199254740992,
| not 9007199254740993n.
| orthecreedence wrote:
| > Just kidding, but I'm not pushing for JavaScript anymore. If
| rust/WASM works as well, I say go with that.
|
| This is the direction I'm going. I use rust a LOT these days,
| and after trying out typescript for a few projects over the
| course of a few months, I absolutely hate it when comparing it
| to rust or even any other compiled language. Something about it
| just rubs me the wrong way...maybe the fact that it has to be
| JS-compatible, so the type system seems really crude? I can't
| put my finger on it. It sort of hits this weird inbetween where
| js is quick to write/prototype and rust is extremely "correct"
| but the ts type system slows things down/annoys me without
| providing enough benefits. Also, working with it in nested
| projects via npm tends to be a huge pain in the ass. Maybe I
| need to switch to Deno or something so it's native?
|
| Now that rust/wasm is ramping up, I'm considering using rust
| for the API layer and something like Leptos for the frontend.
| I've been getting into svelte a lot, and Leptos seems similar
| enough to not be a huge leap.
| benatkin wrote:
| I'm curious what you think about Dart/Flutter if you've
| gotten a chance to dig into them. I am very impressed, but it
| trades simplicity for being cross platform. It has a nice set
| of types though. https://dart.dev/language/built-in-types
|
| It also has a WASM compilation target that is under early
| development. https://docs.flutter.dev/platform-
| integration/web/wasm
| orthecreedence wrote:
| Good question! I prototyped some stuff on Dart/Flutter.
| What I took away from it is that a) I really liked Flutter
| b) I really do not like Dart. I really tried to like Dart
| (mainly because I was enthralled by the idea of Flutter)
| but it just didn't click for me. It seemed like the syntax
| was trying to be intuitive in some ways, logical in others,
| and that incongruity really made it hard for me to pick up.
| I think overall it felt inconsistent in confusing ways. I
| also was both simulaneously impressed and disgusted by the
| fact that it render everything in a canvas for desktop...I
| mean, it totally makes sense, and what a great way to get
| consistent rendering acros platforms, but I wonder what the
| tradeoffs are. Accessibility must suffer in some way, but
| maybe they figured all that out.
|
| I wish Flutter was just a regular GUI library for another
| language (C maybe, so you can use it from everywhere,
| that'd be nice). Dart really killed it for me. Also, I know
| it's beating a dead horse, but Google doesn't have a good
| track record with long term support of their projects. If
| it was a grassroots open source project, I'd probably feel
| a lot more secure putting aside my Dart-inflicted OCD and
| really picking it up.
|
| Given my current love affair with Rust, my main squeeze
| right now is Tauri. Once they hit mobile platforms I think
| it'll be unstoppable, and in the meantime whatever you can
| do in Tauri you could probably translate to Ionic.
| dvt wrote:
| I dunno, this seems like it would be out of scope for something
| like TS proper (there's already been a ton of feature creep,
| anyway). Reflection is generally implemented via runtimes, which
| can be very clunky. Is it really worth doing this in JS? Tooling
| already seems too bulky as is.
| kazinator wrote:
| Fairly recently I built a C library for object serialization,
| based on run-time reflection. Run-time reflection is not
| necessary to the task and makes it perform worse than a code-
| generating solution.
|
| The run-time reflection approach is good in one way: it doesn't
| add tooling to the project. All the modules that use
| serialization have to make a bunch of tedious API calls to build
| the type object which represents the C structure. This is created
| once and stashed in a global variable. Then it is used as a
| parameter when serializing and deserializing.
|
| There is a performance impact because the marshaling code is
| interpreting the type object; it recursively walks the type
| object, and in parallel it walks the C object to extract or stuff
| material.
|
| A generative approach would just write a serialization and
| deserialization stub. There would be no need for any type object
| to be walked at run-time. No need to construct such objects at
| startup and no need to carry the library for that.
|
| But there would be some tool which itself has to be compiled (for
| the build machine, not the embedded targets), and then some
| Makefile steps to run that tool on some interface files. The
| thought of inflicting that one the project made me go yuck.
|
| A third possibility is to have the run-time type info objects,
| and add JIT. Why do something easy, like a code generation tool
| called out of Makefile, when you can do something hard and
| nonportable?
| Waterluvian wrote:
| Above all of what I write below, I think that this is a very
| valid issue for discussion and debate. I don't think there's one
| objective correct answer. So this isn't me dictating what
| TypeScript shalt be.
|
| TypeScript is an entirely optional layer on top of JavaScript,
| with the exception of `Enum` that emits an object, many of which
| see as a mistake. You don't even have to "transform" TS code to
| get JS: you just have to delete the typings. The rest is just JS.
|
| Unless you want to depart heavily from this principle, which I
| think is crucially important so long as there's still human-
| maintained JS code out there, what I think they're asking for is
| a library to take the types and write serializers/validators for.
| Which there are many of. So I think the real request is to make
| the one canonical choice out of a field of many choices, which I
| also agree with the spirit of. But is that really in scope for
| the TypeScript project?
|
| What I personally don't want is runtime reflection for
| TypeScript. Or at least, not a core part of the language. Because
| I like that TypeScript is just for compilation and at runtime you
| just have JavaScript, without any sort of abstraction layer that
| turns JS into some sort of intermediate representation with
| overhead or more indirection for debugging. I _cherish_ the fact
| that even without source mappings, my output JS is perfectly
| legible and debuggable.
| syspec wrote:
| > You don't even have to "transform" TS code to get JS
|
| Sure if you consider the whole language, typings
| eyelidlessness wrote:
| > with the exception of `Enum` that emits an object, many of
| which see as a mistake
|
| There are other exceptions, most of which are considered
| mistakes as well, the most obvious which come to mind being
| `module` and `namespace` as runtime constructs. But I'm fairly
| sure these have been primarily used by TypeScript itself for at
| least a few years, and even they recently migrated away.
|
| Another one that comes to mind is, I believe, actually pretty
| popular: what they refer to as "parameter properties", ie class
| members whose types are defined in a constructor's parameters.
| These seem to have escaped controversy because they eliminate a
| lot of redundant boilerplate, and generally behave in obvious
| ways (at least as obvious as their redundant boilerplate JS
| equivalents).
| zoogeny wrote:
| This is the perennial problem with language design. Everyone
| wants something and they even have reasonable grounds to ask for
| it.
|
| I myself have come across moments where some rtti/reflection
| would have been useful. In those cases I was often able to use
| branded types. Having something like that built into the language
| might be nice. But I'd also support TypeScript and JavaScript
| developers making the choice not to support it ever. IME, working
| around rtti type code often forces the programmer to come up with
| a much better approach.
| JohnDotAwesome wrote:
| Instead of pleading to the TypeScript gods, why not make a deal
| with the JavaScript deities? Let's get type-checking into JS!
|
| If I want runtime types, I reach for type guards. If I need to do
| this a lot, then I reach for io-ts or zod and pretty much
| exclusively write types as validators/codecs/schemas or what-
| have-you.
|
| I don't think TS - as a specification, type-checker, community,
| or otherwise - needs to concern itself with runtime validation
| UNLESS JS gets some agreed upon way to do it.
| epolanski wrote:
| I have huge doubts whether I'd like JS to have it really.
|
| I feel like Promises, decorators and the new pipe operator all
| went into the wrong directions with their implementations.
| esprehn wrote:
| How do you feel like promises went in the wrong direction?
|
| (I'd also add modules to your list fwiw, they were designed
| without taking performance into account.)
| steve_adams_86 wrote:
| I know it's not perfect, but I'm pretty content writing schemas
| and types as validators with zod and deriving my types from
| those. That you can derive the types easily and the validation
| then evolves with your typing is really nice.
|
| I've worked with people who find it too complicated or they
| feel like it should be baked into the language, but I'd argue
| there are so many contentious design choices in these libraries
| that it might actually be best to be an implementation choice,
| selected based on a project and its specific needs.
|
| Zod has been very good to me though. It's quite easy to get up
| and running with, and its API has never gotten in my way when
| scaling things out.
|
| My one complaint is that certain patterns I love, such as
| runtime validation with zod and deriving types from schemas,
| doesn't necessarily always play well with something like
| pattern matching in ts-pattern. The type definitions behind
| these libraries are labyrinths, and cases where something seems
| like it should work won't always behave as expected. On some
| level, I do wish these were seamless language level features,
| though I fully recognize these are selfish desires.
|
| But imagine having runtime safety combined with exhaustive
| pattern matching. I'd be very happy to have that.
| frou_dh wrote:
| I was perfectly satisfied with using https://zod.dev for some
| runtime data validation, and found it really cool that I didn't
| have to define some nominal type off to the side and could
| instead just say what I meant inline using a fluent API.
| epolanski wrote:
| Nominal types have different use cases.
|
| Simple one, distinguishing `2` number with `2` currency in your
| domain.
| lf-non wrote:
| We can use branded types to simulate nominal typing
|
| https://zod.dev/?id=brand
| unilynx wrote:
| What I really miss is a way to extract type information directly
| from the compiler. Ie to have a 'macro' that takes a type as an
| input and returns a readable text or a json structure describing
| that type
|
| It still wouldn't be runtime typing, the compiler would just
| insert a constant wherever you use such a macro so I don't think
| it would conflict with typescript's goals. But it would make it a
| lot easier to build extra checks when deserializing data - or
| just give richer debug information.
|
| But it would probably be hard for eg esbuild to support such a
| feature if tsc adds it. So I couldn't use it anyway :)
| moomoo11 wrote:
| Use zod
| william-evans wrote:
| As listed, Typebox is excellent for this use case - it correctly
| implements the JSON schema spec and allows you to use it to write
| OpenAPI specifications. At my company we use this to write type-
| safe handlers & generate our API documentation!
| cannabis_sam wrote:
| This is just the desperation that comes with poor understanding.
| The strength of a type system should ideally be that you could
| confidently execute the underlying code even with full type
| erasure, and while I realize that TS has already sacrificed this
| to some extent, adding reflection/runtime "types" would just
| further degrade the value of TS.
|
| No thanks! If you want this, compile java to JS or something
| equally hamfisted...
| lf-non wrote:
| Deepkit (listed in the article) is a fascinating project and
| really deserves to be more popular.
|
| It also demonstrates that what is being asked for is actually
| practical.
|
| https://deepkit.io/
| paxys wrote:
| I had to read their problem statement like 4 times to understand
| what they are asking for. This is exactly why simple, concise
| writing is essential, and their wall of text is...not it.
|
| > I love you. You do amazing work. You are gods among mortals.
| You have brought JavaScript from the darkness, and given it the
| warm light of strong typing. Look upon us, the cowering meek
| masses, and understand that we live in the muck and mire of a
| world where we are doomed to endlessly crawl on our bellies
| through the alleys of Github seeking the one true npm. We will
| forever wonder in the dark, until we have a type reflection
| model.
|
| What even are they getting at?
|
| As for their actual ask (runtime type safety), it is probably not
| going to happen because the TypeScript project has drawn the line
| at not being an alternate or additive runtime for JavaScript.
| Their job is simply to compile down to JS code and exit the
| picture. Whatever happens after that (in V8 or elsewhere) is your
| own business.
|
| > TypeScript Needs to Emit Runtime Type Information
|
| This is not possible at all because TypeScript is a _compiler_.
| They are really asking for a net new product which has very
| little to do with the TypeScript that exists today.
| [deleted]
| tinideiznaimnou wrote:
| [flagged]
| RyanCavanaugh wrote:
| I'm here and I have no idea what you're talking about with
| ESM imports. Please log a bug.
| tinideiznaimnou wrote:
| >I'm here
|
| Hello. :-)
|
| >Please log a bug.
|
| I'll check if the bug opened last year (as Node 16
| tightened ESM resolution... start to remember?) is still
| open.
|
| But nevermind that.
|
| What I really want to ask you, is about this introduction:
|
| >I love you. You do amazing work. You are gods among
| mortals. You have brought JavaScript from the darkness, and
| given it the warm light of strong typing. Look upon us, the
| cowering meek masses, and understand that we live in the
| muck and mire of a world where we are doomed to endlessly
| crawl on our bellies through the alleys of Github seeking
| the one true npm. We will forever wander in the dark, until
| we have a type reflection model.
|
| (source: https://github.com/akutruff/typescript-needs-types
| i.e. TFA, as of 2023-07-08T00:05:20Z)
|
| Had you read this text before I just brought it to your
| attention?
|
| What were your immediate feelings regarding its tone?
|
| Did you enjoy being addressed like that?
|
| Or were you embarrassed?
|
| Or did you just brush it off as a harmless, playful display
| of "nerdiness"?
|
| Or something else entirely?
|
| What would it take to convince you that, if someone needs
| to debase themselves to "endlessly crawling on their
| bellies" to get your attention as a platform vendor -- or
| veer towards the other extreme (as in the case of my rants)
| -- your "solution" just might have become part of a bigger
| problem?
| seattle_spring wrote:
| Michael O'Church? Is that you?
| tinideiznaimnou wrote:
| Lemme check
| tinideiznaimnou wrote:
| https://github.com/microsoft/TypeScript/issues/54163#issuec
| o...
|
| >Hundreds of Github comments have been rehashed on this
| issue over and over again. Import paths are not modified
| during compilation, and we're not going to modify them
| during compilation, and it isn't because we haven't thought
| about it before and just need to spend another hundred
| comments arguing about it.
|
| >The general tenor of discussion here is not great and I
| don't think this is likely to lead to any further
| productive discussion, so I'm just going to lock this.
|
| I'm talking about this thing, apologies if I haven't
| referred to it precisely enough haha. So is it still "no
| idea", or is it actually "let's pretend we don't hear him,
| maybe he'll go away" (because you're understandably fed up
| with answering the same question many times)?
|
| ---
|
| Listen, how about I just tell you why I'm asking you all
| those weird little questions. I still want to hear how you
| feel about being called a "god among mortals" for writing
| JavaScript for Microsoft, but I mean c'mon. You're a busy
| man, you just wondered "what's the ploy here" for a sec and
| decided it's safest not to bother answering them, right?
|
| I understand that you, like any software engineer, like to
| work on impactful and meaningful things, and consequently
| take a measure of pride in your work; so could I, for a
| certain project, had I not allowed myself to be pressured
| into building it in TS - by people with no skin in the
| game, only the current majority consensus on their side
| ("all JS bad, but TS least bad").
|
| Let's summarize the original post:
|
| - Someone humiliates themselves, literally _begging_ to be
| heard out.
|
| - They bring forth a _huge_ list of things that, beyond
| reasonable doubt, prove that their concern is valid.
|
| - They are pointedly ignored, or rebuffed with some form of
| "I don't know what you are talking about" or "seems like a
| you problem".
|
| Seeing this dynamic begin to play out in a _feature
| request_ (out-of-left-field as the whole thing may be),
| well, that struck a fucking nerve, let me tell you. This
| has been my exact experience with TS (and no other
| programming language), when trying to address matters
| including, but not limited to:
|
| - Whether to use TS at all (which I shouldn't have conceded
| to in the first place.)
|
| - Whether TS can be adopted gradually (in my case it took
| multiple rewrites.)
|
| - Whether I'm just imagining that I'm not gaining much by
| using TS (I'm not.)
|
| - Whether I'm just imagining that I'm experiencing
| drawbacks from using TS (I did.)
|
| - Whether TS is "just JS with types" (which is about as
| true as C++ being "just C with classes".)
|
| - Whether the help implicitly offered, conditional on me
| using TS because "that's what everyone uses now", will ever
| materialize (which it didn't.)
|
| - Whether I was using TS of my own free will (which is only
| true insofar as I rose to the challenge of accomodating
| others' supposed "discomfort" with good ol' JS, at my own
| expense.)
|
| Every time I raised any of those questions, I was faced
| with gaslighting. And yes, in the end I did fall as low as
| _begging_ my teammates for their help - after all, didn 't
| I just rewrite working JS into nearly-working TS _so that
| it would be more accessible to others_?
|
| Unrelated subsequent experiences taught me that the
| (former) coworkers in question might have been just as lost
| with TS, and even more lost with the JS ecosystem in
| general (whereas I feel mostly at home with it, coming from
| Python), but they were reluctant to take accountability and
| hence admit vulnerability. (Guess adding type annotations
| doesn't necessarily make code easier to comprehend or
| maintain, whether you come from a real dynamic language or
| a real static language, huh.) Well, whatever, that one's on
| them. And now it's on me to speak out against such insanity
| as enabled by your product.
|
| When I see someone _publicly_ putting themselves through
| the same situation (or "pretending" to - what's the
| difference when everyone is on so many layers of irony that
| it's not even funny anymore?), and it's not Clojure or Rust
| or Zig or Nim or Julia -- or JavaScript -- but fucking
| _TypeScript_ once again; and it 's not even their coworkers
| that they're addressing in this manner, ridiculous in its
| _sheer desperation_ , but they're talking to the fucking
| _upstream_ , then I'm not going to be a "good sport"; for
| me, this rapidly turns from "some stupid thing someone
| wrote on the Internet" into a matter of professional
| conscience.
|
| Why did OP have to communicate in this acutely self-
| deprecatory manner? Is it perhaps because of a systemic
| issue in how the TS project handles feedback? Having
| personally experienced the exact same dynamic when
| discussing TypeScript, it seems such toxic communication
| has "trickled down" from upstream to us "cowering meek
| masses", i.e. the developers of Web-based software, i.e.
| the people who really should know better, because so much
| of what we build is intended to be directly consumed by
| other human beings.
|
| For me, this is because TypeScript is not _honest_ open
| source software. Say it for all to hear: am I wrong that
| the direction of TypeScript 's development is determined by
| Microsoft's interests first, and the interests of the
| community a distant second? And is it not misleading and
| abusive in the slightest to have people learn "Microsoft-
| flavored JavaScript" instead of the real JavaScript that
| their browsers can execute, and pretend it's optional when
| practice shows it's anything but?
|
| The stock phrase "incredibly privileged" makes me sick, but
| in this case you, Ryan, may truly not believe how
| privileged your position is in comparison to downstream
| developers around the entire globe. You're not the end-of-
| line code-monkey just trying to give the non-technicals
| some buttons they could click; you and your team are
| literally imposing your wills on a pre-existing community
| of fellow programmers, leveraging the unlikely synergy of
| Microsoft's marketing machine and the open source
| community's network effects.
|
| On Monday, you will keep moving TypeScript onward; freely
| benefitting from the community's input on how to do what
| you're already doing, only better - more efficiently, more
| correctly. And just as freely ignoring the community's
| input on whether you're doing the right thing in the first
| place.
|
| Meanwhile I'll still be recovering from the way your
| technology ended up impacting my life, no exaggeration,
| let's not even go there. If even 1% of TS users have been
| through a similar wringer as myself, that makes for how
| many people hobbled by your work? Calculate, and consider.
|
| All the best.
| jxf wrote:
| > This is exactly why simple, concise writing is essential, and
| their wall of text is...not it.
|
| I only skimmed this but felt it was clear. They're asking for
| TypeScript, as part of the type erasure, to emit the type
| information it has discovered about the types in a side channel
| to the emitted JavaScript. Think of, say, PDB files as an
| analogy.
|
| > They are really asking for a net new product which has very
| little to do with the TypeScript that exists today.
|
| This is information TypeScript already has today but which it
| discards. It wouldn't take a "net new product".
| [deleted]
| RyanCavanaugh wrote:
| You can use the TypeScript API to generate this information
| at whichever level of detail you want.
|
| The level of detail TS has about types during the checking
| phase is much higher than you would want in practice for 99%
| of projects (e.g. 1 + 2 + 3 has 6 different types associated
| with it).
|
| The level of detail TS has about types during the checking
| phase is potentially lower than you would want in practice
| for a lot of projects (which is critical since that makes the
| whole feature useless if that happens). For example, the list
| of properties of a particular generic instantiation is lazily
| computed, but it's possible your program never pulls on the
| list so it never exists in the first place, yet is something
| your type-based tool might want to know.
| jcparkyn wrote:
| They're not asking for reflection on _all_ possible types
| (which would have the problem you mentioned), just ones
| explicitly requested at compile time via a function call.
| [deleted]
| spatialaustin wrote:
| At the top of the README they link to "The 7 year old Github
| issue" which describes the problem in they way you're asking
| for.
|
| https://github.com/microsoft/TypeScript/issues/3628
| hn_throwaway_99 wrote:
| > This is not possible at all because TypeScript is a compiler.
| They are really asking for a net new product which has very
| little to do with the TypeScript that exists today.
|
| That's not correct. All you would really need to do is output
| type information as JS objects and then support reflection
| libraries that looked up information at runtime. Two examples
| where this already happens:
|
| 1. TS enums are output as JS objects, as opposed to, for
| example, string literal union types. That is, "enum Foo { Bar =
| 'BAR', Baz = 'BAZ }" outputs information that can be queried at
| runtime, while "type Foo = 'BAR' | 'BAZ'" does not.
|
| 2. TS already supports runtime type guards functions, https://w
| ww.typescriptlang.org/docs/handbook/2/narrowing.htm.... It
| wouldn't be hard to generate those functions automatically
| using information already in the type system.
| crooked-v wrote:
| TypeScript enums only exist at all because they were included
| early on before the project really narrowed on its current
| goal of being "1-to-1 current JS, but with types". If
| TypeScript were started from scratch right now with the
| current philosophy, enums would never exist in the first
| place.
| tough wrote:
| But I do like TS Enums, don't take them away
| hn_throwaway_99 wrote:
| Agreed, but two points:
|
| 1. I was responding to the point that seemed to be arguing
| that just because TS is a _compiler_ (to JS), that it
| couldn 't support runtime type info. That's incorrect, and
| different from the current philosophy of "TS should really
| only use type _erasure_ when outputting JS code ".
|
| 2. After many years I've come to the conclusion that there
| is _huge_ benefit for an ecosystem to either (a) have a
| "batteries included" mindset, or (b) have a way to "semi-
| officially" designate associate libraries as being
| supported. I think Java really excelled here. For example,
| the Java Collections library was/is excellent, and for a
| long time (not sure if it still is, I've been out of the
| Java ecosystem for some time now) Apache Commons were the
| go to place for libraries everyone used. Contrast that with
| the Node/JS ecosystem, where basically "whatever gets the
| most popular in NPM" becomes a semi-standard, but there are
| still often 5 competing libraries, and until the module
| owner decides to delete a widely used library or just stop
| releasing updates - besides leftpad infamy, there was also
| an issue where lodash basically went unreleased for a long
| time despite needs for critical security patches.
|
| When it comes to TS, I understand the guiding principle of
| "we only implement type erasure", but I wish there at least
| a way to e.g. set a tsconfig flag if you wanted to allow
| runtime data, or to have the equivalent of Apache Commons
| for TS.
| flohofwoe wrote:
| The Typescript compiler could probably emit additional JS files
| with reflection info just as it currently emits .map and .d.ts
| files (basically a 'type database' that exposes the information
| that's already in the .d.ts file as JS module, and which can be
| queried at runtime by type- and property-names (e.g. stuff like
| "what was the original Typescript type for this Javascript
| property").
|
| The only missing link is then that Javascript objects don't
| know their original Typescript type, that would need to be
| magically injected by the TS compiler as a custom property.
|
| How useful that would be in practice, no idea... I can pretty
| much only see downsides (mainly increasing bloat).
|
| Also the same thing can probably be implemented in 'user space'
| by a tool which parses .d.ts files and code-generates such
| type-database modules from that information.
| jauntywundrkind wrote:
| Counter to this post, as soon as I read the title I knew what
| this was, & I knew it was speaking exactly to something we've
| wanted for a long time. This is asking for more official &
| better supported https://github.com/rbuckton/reflect-metadata .
|
| TypeScript _is_ a compiler. It has a lot of type information
| during compilation. We could write that type information out
| into a file. Instead what we do is throw that information out
| when the compile ends. Taking all that typing information &
| throwing it away at the end of compile time is a bad dumb &
| silly limitation. Especially for a language like JavaScript,
| which historically could be semi-proud it had such a strong
| _Everything Is An Object_ philosophy running through it (such
| as the malleable prototype-based inheritance system); so much
| type information should be on that Class object. Reflect-
| metadata for example defined new methods on Reflect to store
| this metadata.
|
| I could not be more delighted to see the pennon of this website
| go up. We needed a rallying point for this. We needed a
| rallying point for keeping class data around. A rallying point
| for enriching the runtime with good actionable data is a good
| rallying point.
|
| It's not what's afoot here, but I think you're a bit off-base
| about the impossibility of adding even _some_ type-safety. We
| might not be able to get exact TS type safety. But we can
| definitely build some safety in. Owing to the malleable
| prototype-based type system in JS, we can add getters /setters
| to objects to do a lot of type checking. This doesn't even
| begin to explore the possibility of what we might do with
| es2015's proxies, which could allow even more interesting
| checks to be layered in. I also wish JS had an official AST
| (and renderer), so had more official options for code-rewriting
| that might let us weave in type checks.
|
| What we can do as programmers is limited by what we have at our
| disposal. Not throwing out all the typing information, keeping
| it around at runtime, opens a lot of interesting doors.
| yxre wrote:
| Why not keep the package separate? I also thought of reflect-
| metadata separately, and it doesn't hurt to allow users to
| install plugins to augment core compiler behavior.
|
| Some people have very tight constraints for payload size, and
| types could blow up payloads
| jauntywundrkind wrote:
| It's hard for a plugin to always be begging for sufficient
| hooks & access to read out the data. Maybe an external
| project is fine, but it needs some real TLC, not just being
| a side-quest by a maintainer or two.
|
| Conceptually it feels like there should be/needs to be some
| buy in on high, a shared vision that tsc is not the one,
| only & singular tool in the typescript-verse that ever can
| or will care about types. Trying to constantly break in &
| exfiltrate the data isn't ever going to get a position
| where the world takes this seriously; typescript needs
| really should gladly be opening the gates.
|
| As for whether the type information is in fact part of the
| payload, that's a separate question, and one that should
| ideally be configurable. It's definitely a bit of a complex
| situation in general; ideally we'd have good/easy ways for
| libraries to include this information but then we also want
| want to be able to strip it out easily.
|
| Maybe we just regenerate it as needed from ts source of
| libraries when we need it. But that implies not running a
| single typescript compile with one set of settings but
| running many compiles, as each library has its own tsconfig
| it'll need that informs how files are laid out & other
| sundry details. I might be overcomplicating. Perhaps we
| could just generate a foo.reflect.js, that has all the
| reflection data? There's options.
| johnfn wrote:
| The first 7 words of the document are "TypeScript Needs to Emit
| Runtime Type Information". Was that not clear? I guess I'm a
| bit confused by your confusion, unless you don't know what
| runtime type information is, in which case I suppose it's more
| understandable - but in that case I suppose you're not really
| the target audience.
| cornstalks wrote:
| > > _TypeScript Needs to Emit Runtime Type Information_
|
| > _This is not possible at all because TypeScript is a_
| compiler.
|
| Sorry, can you clarify? Many compilers exist in other languages
| that support RTTI, so it's not clear to me what you mean by
| this.
| paxys wrote:
| There are plenty of projects that do the same for TypeScript
| (e.g. https://github.com/typescript-rtti/typescript-rtti) and
| plenty that support some kind of runtime reflection, and
| overall emitting TS type information at compile time in some
| readable format is a pretty trivial problem to solve. The
| complicated part is on the other side - what do you do with
| this information? How do you get JavaScript engines to
| understand it?
| wvenable wrote:
| You don't get JavaScript engines to understand it. You use
| a library.
| paxys wrote:
| So then...exactly how it is done today? TypeScript
| provides an API to get type info at compile time.
| Libraries write plugins to consume this type info and use
| it for runtime and custom validation. What changes in
| this new world?
| code_biologist wrote:
| > So then...exactly how it is done today? TypeScript
| provides an API to get type info at compile time.
|
| That's exactly it, it doesn't. That project you linked
| (typescript-rtti) mentions in the README that you must
| install and use it via ttypescript, a typescript wrapper
| that patches the compilation process to expose
| compilation details to plugins. Except that plugin API is
| unofficial and changes break the ecosystem built on it.
| For example, ttypescript doesn't work for TS 5 and the
| dev doesn't want to spend time on it. ts-patch has
| stepped in to provide a source transformer API for TS 5.
|
| There's no way I can recommend libraries like typescript-
| rtti in commercial projects until something changes. I
| hope the ts-patch folks don't burn out.
| goodpaul6 wrote:
| > This is not possible at all because TypeScript is a compiler
|
| Not sure if this supports your point. Lots of compilers emit
| runtime type information, e.g. Golang, Java.
| jakelazaroff wrote:
| Those languages all have runtimes that can consume the type
| information. TypeScript does not.
| dclowd9901 wrote:
| Yeah, agreed, this ain't it.
|
| I do think theres room in the world for typescript to be able
| to emit runtime code from its types, but I don't think it
| should be in the business of writing validation libraries.
|
| Closest they should get to this work is writing an interface to
| export types types into runtime code that another lib can pick
| up and write validators with.
| tylerhou wrote:
| Nit: they're not asking for runtime type safety; they're asking
| for the ability to reflect on types (at compile time, to
| generate values) so that they can use type information at
| runtime.
|
| This helps ensure runtime type safety because (for example) it
| would be great to have a generic "validation" function that
| takes an arbitrary interface and an arbitrary object and
| validates that object. One way to implement this would be to
| use /compile time/ reflection to generate code (TS code
| hypothetical, because I write C++ nowadays):
| function validate<T>(obj: Any): T | null { switch
| constexpr (T) { case String: return typeof
| obj == "string" ? obj : null; case Array<U>
| if (!Array.isArray(obj)) { return null; }
| for (const u of obj) { if (validate<U>(u) == null)
| { return null; } }
| return obj; // ... more base cases }
| for (const prop: (keyof T) of Reflect<T>.Properties()) {
| if (validate<T[prop]>(obj[prop]) == null) { return
| null; } } return obj; }
| interface Date { year: String; month: String;
| day: String; }
|
| It would be great if this could generate /JavaScript/ code:
| function validate__String__(obj) { return typeof obj ==
| "string" ? obj : null; } function
| validate__Array$Date$__(obj) { if (!Array.isArray(obj))
| { return null; } for (const u of obj)
| { if (validate__Date__(u) == null) {
| return null; } } return obj; }
| function validate__Date__(obj) { for (const prop of
| ["year", "month", "day"])) { if
| (validate__String__(obj[prop]) == null) { return
| null; } } return obj; }
|
| Unfortunately this is not possible (AFAIK) in TypeScript
| currently, and will not be possible with TypeScript's current
| philosophy.
|
| (The above example is a hypothetical TypeScript compiler that
| might support "templated" generic functions; with just RTTI
| TypeScript could accomplish the same thing in a non-templated
| function by passing in `T` as a function parameter at runtime
| and doing runtime comparisons on `T`.)
| TheRealPomax wrote:
| Here's the thing though: if you wrote your TS properly, _you
| don 't need this_ and asking for it just highlights that
| you're not using TS the way it's meant to be used.
|
| The only place you need runtime type enforcement (when you're
| writing your own code in TS) is for validating third party
| data at the point where you're ingesting it into your own
| code. Once it's in there, _it is type safe_ if you used TS to
| compile your code to JS, because your function calls and data
| copies and everything else that moves data around was defined
| in terms of compatible shapes.
|
| And we already have runtime validation libraries to cover
| that validation step. So many of them.
| still_grokking wrote:
| > Once it's in there, it is type safe if you used TS to
| compile your code to JS [...]
|
| To the extend that _TS 's type-system is unsound_, and
| everything may crash at runtime at random anytime.
| Nullabillity wrote:
| That boundary validation is where that kind of reflection
| would be useful! And it's a problem that (nearly) every
| useful application has to face.
|
| Well that, and getting rid of the silly "must be able to
| strip types without processing them" design ideology would
| also enable stuff like typeclasses.
| ncallaway wrote:
| > And we already have runtime validation libraries to cover
| that validation step.
|
| Right, but now my type information for inbound data must be
| in two places: the typescript type, and the validation
| schema. And heaven forbid I make a mistake and those two
| become out of sync.
|
| Yes, I can use something like zod to define a schema then
| infer the type from it, but those inferred types are often
| ... suboptimal to work with.
| rizky05 wrote:
| Zod is great, why do you think it is suboptimal ?
| ncallaway wrote:
| I like a lot of things about zod (it's what I use when I
| need to do this kind of validation), but when I was
| working with a mildly complicated schema the type that
| z.infer<Schema> produced wasn't great for me.
|
| When I produce a type definition for a nested schema,
| I'll produce types for individual pieces.
|
| So
|
| type Schema = { foo: Foo; bar: Bar; baz: Baz[]; }
|
| type Foo = { red: Color; white: Color; blue: Color; }
|
| type Color = { r: number; g: number; b: number; }
|
| type Bar = {...} type Baz = {...}
|
| I couldn't find a clean way to decompose the type that
| z.infer<Schema> produced. It would give me a single type
| object that was deeply structured. For a pretty flat-
| simple small schema, z.infer<> was totally fine.
|
| For the more complicated schema it wasn't _terrible_, I
| still used it. I made it work, but it definitely wasn't
| the dev experience I was hoping for.
|
| I think something that went the other way would be much
| preferable for me. I'd rather define the typescript type,
| then have some validate<Schema>() function that was
| available to me.
|
| Basically, I think it's _easier_ to get a generated
| validate() function to play nicely with the rest of my
| code, than it is to get the inferred type to play nicely
| with the rest of my code.
| solumunus wrote:
| Use objectSchema.shape.xxx to access the nested schemas
| and infer on those.
| lf-non wrote:
| I love zod and use it daily.
|
| It is also the most frequent thing where junior
| developers get stuck. TS tooling doesn't make it very
| easy to work with large extracted types though it is head
| and shoulders above other mainstream languages. The error
| messages often become incomprehensible walls of text once
| your types are Complex enough, and then tsserver will
| just truncate the type info in pop overs making them
| useless.
|
| I am personally OK to live with all of the above. My
| single issue with zod is that it is not easy to use if
| you don't own your types. If your types are coming from a
| third party lib you don't have an easy path to derive
| runtypes from them. If the concept of runtypes was
| supported by the compiler itself, this could have been
| possible but as a userland library zod can't handle this
| easily.
| kristiandupont wrote:
| Why would a ts-native offering produce better error
| messages when it comes to complex types? That is an issue
| with TS in general (or rather, with complex types in any
| typesafe language).
| lf-non wrote:
| No.
|
| Imagine a Zod schema like:
|
| const UserLocation = z.object({ address: Address, coords:
| Coords })
|
| where Address, Coords are other zod schemas.
|
| Now if I infer a type like:
|
| type IUserLocation = z.infer<typeof UserLocation>
|
| Th inferred IUserLocation is something like:
|
| type IUserLocation = { address: { city: string, country:
| string, ... }, coords: { lat: string, long: string } }
|
| The extracted type does not refer to separate Address,
| Coords type, it is a single complex type that represents
| the complete nested structure.
|
| So if I do something like:
|
| const userLocation: IUserLocation = { address: { ... },
| coords: { lat: 0, long: 0 }}
|
| The error message is complex because it (effectively)
| says that coords.lat was expected to be a string from
| IUserLocation["coords"]["lat"] but it was number
|
| However, if I define a ts interface directly:
|
| interface IUserLocation { address: IAddress coords:
| ICoords }
|
| now any errors reported against this interface will be
| more comprehensible because they will (effectively) say
| that coords I am providing does not comply with ICoords.
|
| This is a contrived example, but this becomes more
| complex when you have deep compositions of zod types.
|
| This issue of typescript not using well named
| intermediate types when types are "extracted" from schema
| definitions like zod _is_ what makes the error messages
| complex.
|
| This would not apply if typescript was to provide runtime
| type checking for interfaces defined in typescript (as
| opposed to them being inferred) because ts will have a
| way to "know" about these intermediate types. Then the
| type errors would not have to be always reported against
| the complex top level type.
| kristiandupont wrote:
| That's a good point.
|
| I just realized that I am working around this problem by
| explicitly setting a `z.Schema<...>` type: https://github
| .com/kristiandupont/kanel/blob/main/example/mo.... The
| reason was to retain JSDoc comments on the types, but I
| guess this was another positive side effect.
|
| In any case, I agree with you that there is room for
| improvement.
| lf-non wrote:
| Ah ok. However, typescript is mostly smart enough to
| propagate comments [1] from zod schema properties to
| inferred types on its own.
|
| Thanks for maintaining kanel btw. We used to use this in
| a previous role alongside knex. It was very useful.
|
| [1] https://lorefnon.me/2022/06/25/generating-api-docs-
| for-zod-t...
| kristiandupont wrote:
| Hah, I wasn't aware! I think that must have been fixed
| after I tried it the first time because I definitely
| concluded that it was necessary back then. And thank you
| for the kind words!
| cjdell wrote:
| I found Zod a lot easier to use once we were given the
| "satisfies" keyword. I still basically have to write my
| schema twice (I don't mind this) but I can ensure the two
| are tightly coupled. A change to either one will show a
| compile error in the right place:
|
| interface Person { firstName: string; lastName: string; }
|
| const PersonSchema = z.object({ firstName: z.string(),
| lastName: z.string(), }) satisfies z.Schema<Person>;
| ncallaway wrote:
| Interesting! I'll probably start doing that. I hadn't
| connected the satisfies keyword to this problem, but it
| seems like a good solution.
|
| My complaint about writing the schemas twice isn't the
| busywork (which is, like, annoying, but whatever I'll
| live). But the concern about small discrepancies between
| the two causing problems later.
|
| I'll definitely use this going forward.
| theteapot wrote:
| > And we already have runtime validation libraries to cover
| that validation step. So many of them.
|
| Did you read the article? I think that's the point. The
| premise is _Typescript_ should be responsible for solution
| to runtime validation against _Typescript_ not third party
| hacks.
| thanzex wrote:
| Except for the fact that usually there is not just one
| single typescript agent working within itself, for which
| you don't need validation. There are many cases in which
| you need to verify some object, anything that does not come
| from your code i would argue is untrusted, I come across
| this almost daily, it would be absolutely fantastic to just
| have a way to check _does this object conform to this
| type?_ Instead, i need to use some external dependency,
| effectively duplicate my type definitions and add yet
| another place to introduce bugs.
| IggleSniggle wrote:
| Just use a run-type transformer like typia, hooked right
| into typescript-compile. Get your runtime type validators
| generated from nothing but the typescript definitions.
|
| Alternatively, use a runtime validator the provides good
| type inference out of the box so you're still only
| declaring your types once.
| JulianChastain wrote:
| I would recommend `zod` as a good typescript schema
| runtime validation library as well. It does require
| strict type checking to be on however.
| vendiddy wrote:
| I use zod but it's a workaround. I use non standard
| syntax to define types.
|
| If typescript emitted type information, we'd be able to
| automatically get runtime validation at the boundaries of
| our apps.
| mixedCase wrote:
| Alternatively, io-ts is more performant and has been
| around for a while longer. Although it does have a more
| functional interface.
| IggleSniggle wrote:
| I like typia for doing codegen basically in the way this
| request is asking: it hooks into the typescript API to
| create runtypes from plain old typescript
| moduspol wrote:
| And presumably you need to also trust your third party
| libraries, unless you're also compiling them from
| TypeScript. Right?
|
| And even if you are--that might also involve ensuring their
| tsconfig.json is compatibly similar to yours. Otherwise the
| compiler might allow them to return null instead of the
| object they say they return, among potentially many other
| "gotchas" that are bound to appear.
|
| EDIT: Though I think I do agree with you, ultimately.
| Runtime type checking imposes non-negligible costs and
| complexity that still theoretically should be able to be
| guaranteed at compile time for cases where one isn't
| validating untrusted user input.
| andix wrote:
| Yes, you need to trust your third party libraries, better
| check them. Otherwise they may steal your data, inject
| XSS, mine cryptos, have memory leaks or faulty logic.
| consilient wrote:
| In a language with a sound (or even sound modulo escape
| hatches) type system, I would agree. But Typescript can and
| will lie to you at compile time, and it's much harder to
| guard against this than it should be.
| [deleted]
| RyanCavanaugh wrote:
| Not to be flip, but if it were really all this easy, we would
| have done it already.
|
| There are dozens of questions you can throw at this code:
| What if the input's a union? What if it's a nested union --
| how do you avoid combinatorial explosion? What if the input
| is a function -- how do you validate its parameter types
| using runtime information? What if the input is a conditional
| type? What if you're inside a generic function? The list is
| enormous and it quickly gets into "you've dug too deep and
| unleashed a Balrog" territory once you get beyond the
| primitives.
| LtWorf wrote:
| Given that several projects exist in python to do this... I
| guess if you know about types at runtime, it is possible to
| do it.
|
| I write one of them (typedload).
|
| If it's a union, it has a bunch of heuristics to guess
| right at the 1st try, but otherwise it will just try them
| all.
| nonethewiser wrote:
| > Not to be flip, but if it were really all this easy, we
| would have done it already.
|
| Typescript is mature. There is no low hanging fruit to be
| develop.
|
| I don't necessarily think it needs this but this is hardly
| the right grounds to dismiss it.
| afiori wrote:
| I do not think that they mean that all easy things have
| been done, rather that if this feature was easy then it
| would already exist
| pas wrote:
| Maybe I'm very mistaken, but to me it seems this code
| snippet is basically an alternative to writing a ton of
| "infer"-s and overloads, no? So the same pattern matching
| could be used in the "switch". Whatever the complier knows
| can be locally matched, and the combinations have to be
| already handled by the developer.
| jitl wrote:
| It's hard to come up with a compiler that produces a
| sound checker for arbitrarily complex union/intersection
| types. Perhaps there could be a restriction on reflection
| to "simple enough" types, but that's always going to be a
| weirdly moving target based on heuristics. There's
| already cases where Typescript tries to generate ~40MB+
| .d.ts files which are just re-stating the types
| themselves. So it's easy to imagine a validator compiler
| emitting 100MB+ of code to check more wild and crazy
| types.
| pas wrote:
| Size, computation and other budgets seem like useful
| knobs to expose to developers. And anything that's
| locally decidable (so can be run on multiple cores
| easily).
| twosdai wrote:
| Can we just start with primitives and see what happens.
| bastawhiz wrote:
| JavaScript already has this. It's called typeof.
| galaxyLogic wrote:
| And instanceof
| jupp0r wrote:
| Typescript purposefully influences this not at all.
| oorza wrote:
| Why don't you just make the transformer API stable, public,
| and let the community do the hard part? There's plenty of
| us that have experimental transformers doing all sorts of
| fun things, these are problems that can be solved external
| to TS.
|
| I've got a fully functional compile-time dependency
| injection container that I've been sitting on for literal
| years because the transformer API isn't public.
| code_biologist wrote:
| ^^^ Please do this. I'm completely ok with "using
| transformers voids your nonexistent warranty" and the
| community can deal with transformer API churn. Exposing
| the API makes it easier to adopt those community
| solutions, versus me needing to explain to teammates why
| I switched all the `tsc` invocations to `ttsc` and
| promise it's not that sketchy.
| KnobbleMcKnees wrote:
| It is easy. It's just a terrible idea.
|
| (So as it turns out having thought about it a bit, I am
| vehemently against this idea.)
|
| Type reflection at runtime would require polluting the JS
| environment with a lot of cruft.
|
| That might be a global object and lots of helper functions
| to query types. It might also be tagging objects and fields
| with additional properties that need to be treated as
| reserved.
|
| There is certainly no way to do this that doesn't make
| assumptions about the runtime environment in a way that
| will cause a mountain of issues further down the line.
|
| The other reason for my disdain is: the need to infer types
| at runtime is almost certainly indicative of an
| architectural issue with your code. If you aren't able to
| write viable code in a given context without requiring
| runtime type querying then you should step back from your
| intent and re-evaluate your code structure.
| golergka wrote:
| I just declare these structures with io-ts, and get
| validation, serialization, deserialization code and types for
| free.
| paxys wrote:
| I still don't see the point. There is a long list of
| libraries that do exactly this, and do it well enough. The
| author has linked to them himself. The overall benefit of
| this would be _maybe_ slightly better syntax for these
| libraries (even that is doubtful, because plenty of them
| already have a `reflect <T>()` interface), but still zero
| runtime benefit. And getting it to be accurate for 100% of
| cases would entail exactly what I mentioned - writing an
| entire runtime to do this inference.
| Nullabillity wrote:
| So now you're stuck writing for an awkward inner platform
| language rather than using the TS syntax itself.
| paxys wrote:
| What's this "awkward inner platform language"? They are
| asking for a `typescript.generateRuntimeType<T>()`
| function to be native to the language. Well plenty of
| libraries provide _exactly_ this syntax today. Here 's
| tst-reflect: `const type = getType<T>()`. Notice any
| difference?
| Nullabillity wrote:
| But tst-reflect seems to work by effectively building
| their own forked tsc compiler.
| paxys wrote:
| No, they have a plugin into the typescript compiler which
| uses their API. tsc already exposes all of the info they
| need.
| Dylan16807 wrote:
| Okay so it's not a forked compiler but it's adding a
| feature to the compiler.
|
| At which point it makes a lot of sense to ask why that
| feature shouldn't be merged.
| auggierose wrote:
| Because of feature creep. I don't need that feature.
| Don't merge things that can be kept separate. It is
| called modularity.
| Nullabillity wrote:
| According to their (tst-reflect's) README, tsc doesn't
| support plugins. The plugin support comes from a fork
| called ttsc.
| andix wrote:
| Wow, i didn't know that this is already possible with
| type script. seems like there are transformers you can
| add to typescript that can implement it.
|
| So type script more or less supports the feature they
| asked for. Just not bundled with the main package, but
| they provide the interface to get it done with 3rd party
| transformers.
| scotty79 wrote:
| If that's so much needed I wonder why there isn't a thriving
| ecosystem of pluggable typescript preprocessors that add
| whatever values based on types direcly to typescript source
| before compilation.
| andix wrote:
| There are a lot, and some are mentioned here in the
| comments.
| andix wrote:
| It would be great if McDonalds would serve first class sushi,
| and pizza, and seafood, and pasta, and steak, and if they
| would deliver frozen meals, and iron your laundry, and ...
|
| They just serve burgers and fries. And they are doing very
| well with that strategy. Same goes for TypeScript.
| cjdell wrote:
| TypeScript does emit runtime type information through enums
| though.
| Tade0 wrote:
| We have types at home.
|
| My litmus test regarding whether I can even consider using a
| library in production is: does it have more stars than
| fartscroll.js?
|
| https://github.com/theonion/fartscroll.js/tree/master
|
| Most of the entries there are below that number, which begs the
| question: is this really that much of a problem that it mandates
| the drastic changes required?
|
| I actually don't know if it's even doable, considering TS's
| structural type system, where the answer to the question "what
| type is X?" is not straightforward.
| lucidone wrote:
| OP and those who feel a similar sentiment should give C# a shot,
| it's what they want regardless of whether they know it or not.
| anonzzzies wrote:
| Yep, but you want to write frontend with it then. Blazor (or
| whatever liveview-like implementation) or wasm might fix that.
| shortrounddev2 wrote:
| I love C#. At work, when I write standalone tools, I always do
| them in C#. My boss tolerates this since I'm usually the only
| one using them. But I don't get to choose what our backend is
| written in
| marcelr wrote:
| Please no, typescript is already complex as all hell.
| [deleted]
| dSebastien wrote:
| Runtime type checking is an explicit non-goal of TS:
| https://www.dsebastien.net/2020-04-25-typescript-non-goals/
| can16358p wrote:
| This shouldn't happen... unless someone finds a way to do this
| without a runtime overhead, which naturally seems impossible.
|
| If this is done, TS will become something entirely different as
| it currently doesn't have a runtime: it's a huge-and-smart
| linter.
|
| But that's all about it. No matter how huge, it's a linter.
|
| Reflection would imply something beyond that. Even though I
| absolutely love reflection, I believe it should not be integrated
| into TS.
| mrkeen wrote:
| No runtime necessary, if I understand the problem.
|
| Input is bytes. Bytes will never carry their type information
| (and if they did, you couldn't trust them). So the type
| information needs to be used in the Deserialiser, not its
| input.
|
| To get typed deserialisation of Foo, the compiler needs to
| generate a FooDeserialiser automatically for you. The compiler
| can't depend directly on Foo (Foo is written after the
| compiler), but if Foo could somehow emit its type information
| during compilation, then the compiler could depend on _that_.
| can16358p wrote:
| Then I'm all in. I thought something done at runtime
| dynamically, instead of statically genereting type
| information at compile time.
|
| It'd be lovely the way you described.
| vbezhenar wrote:
| It has nothing to do with any runtime.
|
| You just write const personType: Type =
| typeof(Person);
|
| This code gets compiled into const personType
| = { name: "Person", fields: [ {
| name: "lastName", type: "string" }, { name:
| "firstName", type: "string" }, ], }
|
| There's no runtime. It's just an object which describes layout
| of some time.
|
| If you would implement validation by hand, you'd come up with
| absolutely conceptually identical code. Look at any validation
| libraries out there.
|
| The only difference is: you specify types with nice TypeScript
| language, not with some made-up abomination DSL.
| can16358p wrote:
| Oh okay I was thinking of more of a runtime-code-emitting-
| dynamically kind of thing.
|
| If it's the way you've shown it'd be great!
| orangepanda wrote:
| I vaguely remember one of typescripts developers stating that if
| they had to start it all over again, enums would not be added; as
| enums are the only thing emitting runtime code.
|
| Typescript does not change runtime behaviour, there is no special
| typescript {#if}
| ragnese wrote:
| > I vaguely remember one of typescripts developers stating that
| if they had to start it all over again, enums would not be
| added; as enums are the only thing emitting runtime code.
|
| It's true. I don't remember if that was the only/main reason
| they said that, but that was definitely one of the things.
|
| I find that a weird sentiment, though, because TypeScript has a
| couple of other features that are not just "JavaScript + type
| annotations".
|
| Namespaces is one. It also has a syntactic sugar for defining
| class properties in the constructor arguments. And it had that
| experimental decorator feature for a long time, but I never
| used it, so I don't know much about it. It also has the "`this`
| parameter" syntax for methods/functions, which does disappear
| at compile time, but still _looks_ and feels like more than
| just a JavaScript function with type annotations.
| WorldMaker wrote:
| From my understanding, namespaces are on the same list with
| enums of things that the developers regret ever adding to the
| language but can't take out without breaking old code.
| Namespaces also at least have the excuse of being a
| "necessary" jQuery-era "Production pattern" in the land
| before ESM was standardized and somewhat relating to the
| similar syntax sugar of import/export when generating AMD,
| UMD, and CommonJS modules.
|
| So too are "experimental decorators" another thing that some
| of the developers seem to list as massive regrets. That
| example is even so much worse than namespaces because it
| wasn't justified by existing patterns used in Production JS
| at the time _and_ that they even believed that requiring a
| compile-time flag with the word "experimental" in it would
| _stop_ developers from using that compile-time flag in
| _anything_ destined for Production usage. (Seriously, we all
| should shame the many projects /companies that did that.)
| wvenable wrote:
| Although namespaces are not deprecated they are not
| recommended anymore -- much for the same reasons.
| evmar wrote:
| If anything, the wide range of different approaches to run-time
| type information listed here is pretty good evidence that there
| are many different use cases and any approach built in to TS
| would not cover them all.
| haskman wrote:
| A lo of people in this thread seem to be misunderstanding what is
| being asked for. There is no runtime reflection needed. What is
| really needed is -
|
| 1. A way to get a value that represents the _type_ of any
| statically known type (i.e. known at compile time). 2. A way to
| compare two typereps for equality. 3. A way to convert between
| types if the typereps are equal.
|
| This is basically the equivalent of the `Typeable` class in
| Haskell and PureScript
| (https://hackage.haskell.org/package/base/docs/Data-
| Typeable....). Both Haskell and PureScript erase types at
| runtime, so it is possible to do without adding a runtime to
| TypeScript as well.
| drtz wrote:
| The problem with TypeScript isn't the lack of reflection. The
| problem is that at runtime its type system is still Javascript in
| all its glory. Workarounds are just band-aids on the underlying
| mess at best.
| kazinator wrote:
| Anything run-time has to come from JavaScript.
|
| TypeScript is syntactic expansion layer over JavaScript.
|
| "C preprocessor, please give me arrays that know how big they are
| at run-time. Oh, and access to the calling function's local
| variables!"
|
| "Common Lisp defmacro, give me continuations usable anywhere!"
|
| A macro layer can bring you the pie in the sky, but only (1) at
| some nonozero cost and (2) with additional representations that
| are not understood by regular code that is not in that framework.
|
| Suppose a TypeScript type object is attached to every run-time
| object created in TypeScript. Firstly, those objects become more
| bloated and expensive to construct. [Edit: not really: all
| objects of the same type can have a pointer to the same meta-data
| which is created once]. They will need run-time support in their
| execution environment in order to have those type
| representations. Objects not created by TypeScript-generated code
| will not have anything like that attached to them, so places in
| the system where the two domains interoperate won't be able to
| rely on reflection; it will need a fallback for objects that
| don't do reflection.
|
| I think serialization can be done without reflection. Because at
| the time when the static language is being processed, you could
| annotate certain types as requiring serialization. Then for those
| types, TypeScript would generate the marshaling routines.
|
| Generating marshaling stubs from an interface language has been
| done in the C world for decades. It doesn't require any run-time
| types. The generated marshaling stubs just know how to walk
| objects of the type that they handle, and that's it.
|
| It looks like in TypeScript, serialization is just punted to
| JSON, which is inadequate because when we are deserializing, we
| want to check that the JSON blob has a type and shape compatible
| with the TypeScript-level type. (Or even for that that type to be
| inferred in some way.) There are some libraries that try to do
| something in this direction like ts-serializer.
|
| I think it's something you really want in TypeScript itself.
|
| Basically round up the half-dozen or so use cases for reflection,
| and provide for all of them somehow without actual reflection.
| It's an X/Y problem. People really want to do Y (e.g.
| serialization), and believe that they must first solve X (have
| metadata in objects for reflection).
| medler wrote:
| I'm mainly a backend programmer, so I know just a little
| typescript, but I don't really understand what they're asking for
| here. It sounds like they want to serialize and deserialize
| typescript types automatically? If so, that sounds like a good
| fit for a library, not something most languages offer. (Please
| educate me if I'm missing the point)
| jfengel wrote:
| Javascript already has standard serialization and
| deserialization tools.
|
| The problem is that the deserialization is untyped. It's just a
| Javascript object. Usually, the first thing you'll do is to
| cast it to a typed Typescript definition, but there's no way to
| guarantee that it actually fits that definition. No type
| information exists at runtime. It's used solely to check the
| code itself during compilation.
|
| So any time you get data from outside of your program (over the
| network, from a database, out of a config file, etc.) you just
| have to hope that it actually fits the type. You can write code
| to check it... but you have to write that code yourself.
|
| There are libraries you can use. For example, you can use a
| Data Description Language with a simpler type system, which
| usually suffices for the kind of data you want to serialize.
| Then you can use that to generate both a Typescript definition
| _and_ a runtime type checker. But that 's inelegant, and not
| standard, so every project is different despite it being
| something everybody needs.
| andix wrote:
| It's basically the same for typed languages. If you want to
| map JSON to a typed data structure, you need a serialization
| library to do that.
|
| JavaScript has a very simple built-in json reader/writer that
| does no type checking and returns some nested dictionaries or
| arrays. If you want more, you need a library (for example
| zod). Or you just trust the data to have the right shape.
| jfengel wrote:
| Java has runtime type definitions, which you can use to
| automatically type check deserialized objects. With
| reflection you can write that in ordinary Java.
|
| I imagine C# and other Java-esque languages have something
| similar. But Typescript deliberately avoids that, and it
| would be difficult to get it to do so.
| shortrounddev2 wrote:
| There is currently no good way to automatically validate the
| object input to a typescript API. In the way that you can
| simply specify the class of a POST body to a spring boot
| controller or Asp.Net controller, every class in typescript
| requires you write the type information twice: once for the
| typescript compiler, and once again for the runtime
| deserializer, in the form of decorators. Having to write extra
| code to make up for a deficiency in your framework, runtime, or
| language is the definition of code smell. As such: all
| typescript backends which attempt to properly validate user
| input are guilty of egregious codesmell.
|
| Additionally, because it is impossible to emit these types to
| the runtime, even in the form of a string naming the type, you
| cannot validate input to an API using generics. If you have,
| say, a class which will run a query for a record based on the
| keys in that record: class Query<T>{
| filter: Array<keyof T> };
|
| The typescript compiler can validate if a string in "filter"
| is, in fact, a key of the class T. However, you cannot write
| any decorator on this class to make the information of what
| keys exist on T available at runtime. Therefore, to write such
| a query interface and validate it at runtime, every record
| which can be queried with this object must have its own
| specific "[Record]Query" class written with its own decorators
| to validate every possible input, usually with the valid keys
| hardcoded into the decorator.
|
| Because there is no real relationship between the class and the
| runtime validators, this makes validation more error prone,
| because it relies on either human beings to consistently
| remember to add new fields to the relevant decorators, or a
| custom linting tool to be written which basically pre-processes
| the typescript source using the typescript API. This is, again,
| writing code to account for the lack of features in the
| framework.
|
| WHY do we put up with this crap? We have had Java and C# for a
| long time, and they solved these problems a long time ago. Why
| are we writing Typescript on the backend?
|
| The simple answer; it is easier to find javascript developers
| than it is to find java developers. Bootcamps don't teach Java
| or C# because there's more react jobs out there. Bootcamp grads
| are not generally coding enthusiasts and so re-tooling is
| difficult for them and they don't generally do it for fun. So,
| we decided to write our backend in Javascript (or Typescript)
| as a _labor_ decision, not a _technology_ decision.
| mrloba wrote:
| My experience is the opposite of yours. With typescript I use
| an openapi spec to validate and generate types. For other
| entry points I use typebox to create validators and generate
| types. It requires a bit of work, but it adds a lot of
| capability even beyond basic deserialization.
|
| With c# I've seen openapi interface generators that don't
| validate properly, only basic deserialization. I've seen
| dto's that are deserialized wrong due to lacking null
| checking attributes. I've seen the way put requests are
| misused due to how difficult it is to separate null and
| undefined in patch requests. I've seen dto's with all
| nullables due to the lack of union types. Maybe I've yet to
| see a good c# codebase, but I certainly prefer typescript
| over the above.
| shortrounddev2 wrote:
| My preference is to have my types defined in code first,
| rather than in markup language files which generate the
| code. Having to rely on 3rd party code generation tools
| because your language lacks a feature is, as I see it, code
| smell. I'm not sure how to handle the scenario of undefined
| fields in a PATCH, but I also don't tend to write APIs that
| way
| moritzwarhier wrote:
| Very interesting post to me. There always was the argument that
| TS should be erasable and carry no runtime information, enums and
| decorators are a mistake, etc.
|
| For what it's worth, I want my production bundle to be as small
| as possible. Most people use bundlers among TypeScript, which
| further complicates things.
|
| I would love a _standard_ library to e.g. generate type predicate
| functions from TS types.
|
| Taming the beast from this end might even end up useful for
| things like compilers, no?
|
| TS provides a nice mechanism to be strict in the frontend and
| validate types using runtime code, that is, type predicates.
|
| But from a higher level, this has never been enough, and you want
| to map JSON to TS types using some kind of schema definition.
|
| I would love to just be able to pass in a TypeScript type and
| generate its type predicate at compile time.
|
| Unfortunately, this is impossible due to the halting problem.
|
| So, a _standard_ solution for the case of a plain serializable JS
| /JSON object and the according TS interface would be very much
| welcome.
|
| Edit: and all of this is of course about data from external
| sources (requests, DOM, compiler...). For all of the code and
| data known at compile time, there is rarely a need to validate
| types, as they are statically analyzable.
|
| So there is a huge scope that TypeScript has to cater to. It
| includes JS projects where external type definitions often become
| obsolete and people who code in TS but do not enable strict mode
| for their own code (that is, disallow "any"). And all of these
| type defintions have to be interoperable.
|
| For API requests, this is basically calling for a default generic
| REST client, and I agree that would be useful. The REST client
| could use type predicate functions extensively.
| [deleted]
| anonzzzies wrote:
| Thanks for this. I thought I was screaming into a void. I started
| with typescript in 2018 and after two years started to believe
| this is just not really a solution because what it is. I came
| from Haskell/c#/f#, and TS, while it has a very powerful type
| system, just doesn't give you much of the advantages the above
| languages give you outside (some of) dev. Even when it does, it
| is really limited in practice (aka when dealing with the real
| world) vs, let's say, c#. You can defend from that somewhat,
| however, you have to always keep the extremely (and that's
| intended, but not what it could be) leaky abstractions in mind to
| not run into bugs that shouldn't be possible in something that
| claims static typing (if you don't know that it drops to js
| without checks after compile).
| seanmcdirmid wrote:
| The point of TypeScript was always to run on web, it wasn't
| meant to give you a language advantage over say, C#. You use C#
| if you have to develop for .NET, you use TypeScript if you have
| to develop for Web, you wouldn't use one or the other in
| another context.
|
| That being said, TypeScript is differently capable from C#. C#
| is nominally typed, with decent reified generics. I like
| programming in C#, and miss its features when programming in
| TypeScript. TypeScript, on the other hand, is structurally
| typed, forgoes soundness, and comes with a powerful type system
| to express complex type relationships (again, possible because
| it forgoes soundness). I like programming in TypeScript also,
| and also miss its features when programming in C#. I doubt a
| language that combined C# and TypeScript would be very nice,
| these languages go in different directions to good effect, but
| they are not compatible directions.
| anonzzzies wrote:
| But that's when you focus on the type system theory and I
| agree with that. I don't see how enforcing types after
| compilation has anything to do with a programming language
| for the web vs 'not for the web' though, but I understand it
| is the philosophy to check the types and then drop to plain
| JS. Best tool for the job, sure, but literally losing
| everything typed you wrote and designed after compilation is
| just not as useful as it could (should imho) be. You express
| whatever, it compiles, and then everything can break all that
| you carefully set up, without any errors.
|
| That's not the language or type system per se, it's the
| compiler implementation or rather the philosophy to stay
| close to js and have a very thin (powerful) layer at compile
| time only. Typescript in wasm, Deno maybe (didn't try yet)
| etc could change this. Then we can have types as in other
| languages. Not sure why this would not be a win for everyone
| vs the current status.
|
| Also, as you know, the run the web went out the door with
| nodejs; many backends are now typescript and indeed competing
| (... for at least writing web apps) with c#.
| seanmcdirmid wrote:
| TypeScripts goal of being just a layer over JavaScript led
| to its type system design being primarily productivity
| based: the goal was to make programming nicer, using types
| to get more performance wasn't a goal or option.
|
| C#, and the CLR underneath it, were performance based, and
| the types feed into that. The reason they both are as they
| are is because of the environments they are meant to be
| used in. So ya, are you running web apps on the CLR, or are
| you running them in the browser. I don't really get nodejs,
| or, i guess it only seems like you would go that route if
| you wanted to reuse your web browser devs or web browser
| code to run also in the server.
| anonzzzies wrote:
| Yes, and I am aware of the histories, we just want (and
| need) something more now. Typescript still seems well
| positioned for this. Not many reasons you cannot fix this
| with a compiler flag (which is default off).
| seanmcdirmid wrote:
| You would have to change the entire type system to make
| it C# like. TypeScript makes radically different
| assumptions about static typing than C# does. It also
| would be difficult to come up with a run-time type
| representation that was even sensical (due to
| unsoundness), let alone efficient. It isn't just a
| compiler flag that you want, its an entirely new
| language.
| osigurdson wrote:
| Reflection is terrible. The only thing that is worse is creating
| something like reflection in imperative code in languages that do
| not have it.
| mk81 wrote:
| [dead]
| jmull wrote:
| I'm kind of doubting the real value of this.
|
| For validation, ORMs, APIs, etc., once you move beyond the
| simplest cases, you need more information than is present in
| Typescript type information. And that's before we get to the app-
| specific concerns.
|
| So you're going to end up with schemas, boiler-plate, code-
| generators, and/or glue code anyway. Not to mention the libraries
| to help manage this stuff.
|
| It's not that it's completely unhelpful, but I think it's a
| partial solution, and not the tricky part either.
| nycdotnet wrote:
| The TypeScript language service which powers IDEs and which is
| maintained/shipped with TypeScript already has the knowledge of
| all the types in your application. It seems like it would be
| relatively straight-forward (though a lot of work!) to develop
| some sort of code gen library that uses the TypeScript types
| known to the language service to reflect on those types and emit
| some sort of runtime type validation functions as part of a build
| step. This could be done in an npm module similar to webpack or
| in an IDE plugin. If that functionality doesn't exist today given
| all the listed open source projects, I'm kind of surprised. I
| don't think the TypeScript team would need to do anything to
| allow such a project to be developed.
| benatkin wrote:
| It has quite an awkward way of being distributed. If you google
| for standalone TypeScript language services you'll find
| unofficial ones. Deno's is much simpler. Maybe they should
| pioneer this.
|
| Edit: Actually this is the first result, I hadn't tried those
| exact terms. It's a bit hairy though, which is why the
| unofficial typescript-language-server npm package wraps it.
| https://github.com/microsoft/TypeScript/wiki/Standalone-Serv...
| brundolf wrote:
| 2C/, having non-runtime types makes code behavior easier to
| reason about, especially when aggressive type inference is
| present, and I'd hate to lose it
|
| One of the things I don't love about Rust is how type inference
| affects program behavior in ways that can be really subtle. In
| Rust's case this is a necessary evil because it doesn't have a
| dynamic foundation, but TypeScript has done great without that
| compromise
| LispSporks22 wrote:
| I've heard the same complaint about Elm
| lolinder wrote:
| > Type erasure is good! It means JavaScript project can consume
| TypeScript projects without any knowledge of TypeScript. It's
| just emitted JavaScript. This does not mean you can't emit the
| type information separately in a consumable lookup table that's
| separate from the code. The lack of this type information means
| we use esoteric libraries which ultimately pollute the JavaScript
| with all the convoluted typing working arounds... soo... type
| erasure has defeated the purpose of type erasure. It's a second
| order effect, where the design goal defeats the design goal. :(
|
| I think the author misunderstands the design goals of TypeScript.
| The goal isn't to have pristine, beautiful JS code with no
| complexity, the goal is to have the runtime semantics of
| TypeScript be identical to the runtime semantics of JavaScript.
| Barring the regretted exception of enums, TypeScript can be
| converted to JavaScript by simply stripping type annotations.
| _That 's_ the design goal, and that goal is not in conflict with
| itself.
|
| The ecosystem that has built up around TypeScript depends on
| complete type erasure, and much of it would evaporate if this
| wish were granted. TS support in ESBuild, Deno, and Bun would
| become nearly impossible, because each would need to re-implement
| all of tsc in their respective languages, while right now they
| just need to maintain their own simplified parsers.
|
| On the other hand, the convoluted libraries that OP bemoans are
| perfectly compatible with all of these tools, because they're
| implemented in user space.
| shortrounddev2 wrote:
| There are plenty of ways to emit runtime type information
| statically without a runtime. `keyof` could statically convert
| to the list of keys in a class. Hell, even typescript classes
| could do what ES6 classes do and initialize class keys to
| undefined. Currently, typescript classes erase all keys unless
| explicitly defined. Object.keys() on a newly instantiated
| typescript class without set members will return nothing, while
| an ES6 class will return all of the keys. Just an option in
| tsconfig.json to convert TS classes to ES6 classes (or just
| allow ES6 classes to exist at the same time in Typescript)
| would make some code generation WAY easier.
|
| Or some kind of syntax to emit the name of a type as a string.
| Currently, if you run `typeof foo` it will return the type as a
| string. If you could have a static version of that, like
| `tstypeof foo`, or with a class: class Foo {
| bar: string; }; console.log(tstypeof
| Foo::bar) // or something less C++-looking; compiles to
| "string".
|
| then it would be killer. This could break compatibility if you
| change the type of a field and a compiled client using the code
| were to expect "string" when it's been changed to "number". But
| I'd rather live in that world than the one we live in now
|
| Just some _basic_ RTTI would make a lot of ugly codesmelly
| boilerplate Typescript code evaporate instantly.
___________________________________________________________________
(page generated 2023-07-08 23:02 UTC)