Macro-ts: TypeScript compiler with typesafe syntactic macros (2022)
blainehansen.me
blainehansen.me
A ts macro system would be integrated with ts language service and tsc. As a compile time language, the main advantage of tsc is, well, developer experience, which, unless I'm missing something, gets thrown out the door here.
Also, the demo includes very weak use cases, much of it could be done directly with js functionality without the additional macros. It's not clear to me how much effort and computation time does this saves.
Aw :-(
That's the bit that I'd really like to have TS macros for!
My own approach has been to loosely parse input as something like D-expressions, transform that, and pipe the result into the TS compiler. Crude but effective. The trickiest part is extending the tsserver support, but even this is doable.
Ideally, a transpiler that does not want to type check the code, should merely need to know the syntax for type related code and strip them out. This is especially true if targeting a sufficiently recent version of ECMAScript, or if the transpiler will be doing its own transformation of newer features to older ones for backward compatibility.
In practice while many typescript features can be handled like that, even some that are not strictly type features (like method accessibility modifiers), some existing features like enums needs to generate new code, although at least for that one it is an easy pattern to generate.
This is especially true of any feature that cannot be made into a trivial re-write or strip rule. Namespaces and const enums are an example of features now discouraged, as simple rewriting engines cannot handle them. Indeed the whole "isolatedModules" compiler flag is specifically about making such separate simple transpilation possible, and its documentation details other scenarios that can be a problem.
The `syntax` subdirectory is (or should be!) pretty clean. The `compiler` subdirectory is, uh, functional. Please do get in touch if you have any questions.
https://github.com/leanprover/lean4/blob/master/tests/playgr...
I believe most of the language is defined this way, although it is pre-compiled.
For more details see the lean4 metaprogramming book: https://github.com/arthurpaulino/lean4-metaprogramming-book
Going off at a tangent, is there a reason WebAssembly can't interact with the DOM? I would have thought that being able to do so would be kinda useful, particularly in a web page.
So for now you can think of it as a range of different things in the form of web development. You can view it as a sort of non-proprietary flash, where you can technically distribute your flash-like application to any browser. You can also view it like another form of virtual DOM (of sorts) the way Microsoft tries to do with Blazor, or you can view it as JavaScript “modules” where you do compute heavy stuff with C++ and implement it in your otherwise JavaScript frontend. But that’s just the web part, WebAssembly wants to be more than that, but you should go to their documentation for a better understanding of that.
It would be kind of useful to have WebAssembly interact with the DOM and I’m not completely convinced that the security would be a major issue, but it’s a massive undertaking, and it’s likely going to be sort of in vain. Because JavaScript is moving forward at a much higher pace than WebAssembly. One of the primary reasons you often see listed is that the DOM is slow, but that’s not really true. Because the adding or removing a DOM node is literally a few pointer swaps, which isn't much more than setting a property on an OOP language object. What is slow is "layout". When Javascript changes something and hands control back to the browser it invokes its CSS recalc, layout, repaint and finally recomposition algorithms to redraw the screen. The layout algorithm is quite complex and it's synchronous, which makes it stupidly easy to make thing slow. Imagine handling that, again, when we’re not even really intend on doing so in most frontend JavaScript. What I mean by this is that one or the reasons we use React (and by we I mean the global we) is that it’s much easier to not fuck-up with the virtual DOM, and while things like Blazor is valid alternatives, they aren’t remotely comparable even when backed by Microsoft.
So it’s really a question of “do we want to do all that work, when all that work is already being done better by someone else?”, and for now the answer is “no”. Which is disappointing to many, but probably not the many who were going to contribute anyway.
You already mention Blazor, which would hugely profit from direct DOM access. Various React-like frameworks now exist for various languages that target WebAssembly, all of them would receive performance benefits from not having to go through a JavaScript layer all the time.
That's the community side. On the standards side, efforts to enable direct Web IDL bindings are still underway, they just take time. Some things have been postponed because the issues are more complex than originally anticipated. But this will eventually happen.
Maybe, but wasn’t this the reason DOM replacement was removed from the roadmap?
Are there any actual cases where a macro system in TypeScript would be useful?
This of course, goes against the "prime directive" of typescript and would never see the light of day in the core compiler, which is probably a good thing. But for a hobby project it'd be interesting.
In large scale TS applications of my experience, there's always several places where you're writing out long terms exactly corresponding to types in some mechanical way, but there's no real infrastructure to help you there so it's just a manual process.
I’ve been at this point several times, and for most of the time, the solution was to either use the satisfies operator or remove explicit type declarations in favour of what TS infers on its own, then refactoring the code. This resulted in a leaner, more elegant, and most importantly correctly typed code base. YMMV of course, and sometimes this just isn’t possible, but I believe macros to be way too dangerous for that fringe use case or hobby project - inexperienced developers would jump to introduce them everywhere immediately, guaranteed.
A lot of use cases involving decorators today (which are really rough for tree shaking).
Writing type-safe SQL queries.
Using JSONSchema.
`userId!`
to
`if (!userId) throw new Error('Non-Null Assertion Failed "userId!"')`
Macros are bad and you're bad if you use them. This has been essentially a de facto mantra since the early 90s (when I was a wee lad) and it's quite common to see people writing very long articles on why you shouldn't use them[1]. They are magic, so should be avoided.
There's a reason languages like C# or Go don't have them, and you should take heed of the very smart people that designed these languages. Even in Rust, they are controversial[2] (the most obvious reason is that your IDE is worthless when macros are involved). Metaprogramming (i.e. macros) adds extreme complexity (in some cases, pre-runtime-Turing-completeness) with many footguns, including the classic, and very "fun," double evaluation.
Here be dragons.
[1] https://www.linkedin.com/pulse/why-considered-bad-practice-u...
[2] https://www.reddit.com/r/rust/comments/taxfe3/what_are_the_p...
Go has a quasi macro system though (go generate)
https://gradha.github.io/articles/2015/01/the-day-go-reinven...
I like the macro systems in Crystal and Haxe (reifed macros)
https://haxe.org/manual/macro-reification-expression.html
https://crystal-lang.org/reference/1.8/syntax_and_semantics/...
I think C would have benefited from such system at the AST level instead of text substitution.
Javascript is dynamic enough there is absolutely no need for Macros.
TypeScript macroses would be very useful for the same things codegenerators are useful: for example generating api client from swagger schema. Ofc you can do some trickery with metaprogramming, for example build and eval strings on fly and the result could be a dynamically generated function, but the point is: we want the process and the result to be typesafe and efficient, which is what is missing with hacky dynamic metaprogramming approaches.