Long ago, on Svalbard, when you were a young witch of forty-three, your mother took your unscarred wrists in her hands, and spoke:
Vidrun, born of the sea-wind through the spruce
Vidrun, green-tinged offshoot of my bough, joy and burden of my life
Vidrun, fierce and clever, may our clan’s wisdom be yours:
Never read Hacker News
But Hacker News has read of you, in their snicker-slithing susurrential warrens, and word has spread...
susurrus or susurration is a very literary word for a whisper/whispering. The usual adjective would be susurrous or susurrant, rather than susurrential, but in any case it would mean “full of whispering sounds”.
In case folks miss the link at the top of the article, this is translated from an old 2017 post by Aphyr.
That post was in Haskell, where it's not too surprising that you can do serious computation inside the type system.
This new post translates the ideas to TypeScript, which is more widely known, and which I once heard described as having "accidentally Turing-complete" types:
The part about using the typescript language server to compute the solution, and the protagonist claiming the code is "concise" because only 4 lines of javascript were generated, was absolutely brilliant. Cracked me up at least.
Glancing at the actual code, I admit I'm with Criss in my ability to follow the logic, but it doesn't look like a direct translation from Haskel types to Typescript types either.
I know my mind is decidedly poisoned when I could follow the type definitions perfectly, and they reminded me of certain types I have written myself… ah, TypeScript, what have you done to me…
Here[0] is the open issue about TypeScript being Turing complete. The current most recent comment[1] is showing the type system parsing its own type syntax. Of course there have been many parsers written in the type system since template literal types landed, but I found this one particularly amusing.
This post also mentions "Vidrun," which features heavily in Aphyr's posts. And the interviewer in this post recognizing the situation they were in had me literally laughing out loud!
Were this true it would be terrible, because Turing-completeness necessarily includes non-termination. This would mean that a compiler would crash, or get stuck in an infinite loop.
If you want to see some more legs on TypeScript type-level logic, check out this SQL database as Typescript types: https://github.com/codemix/ts-sql:
import { Query } from "@codemix/ts-sql";
const db = {
things: [
{ id: 1, name: "a", active: true },
{ id: 2, name: "b", active: false },
{ id: 3, name: "c", active: true },
],
} as const;
type ActiveThings = Query<
"SELECT id, name AS nom FROM things WHERE active = true",
typeof db
>;
// ActiveThings is now equal to the following type:
type Expected = [{ id: 1; nom: "a" }, { id: 3; nom: "c" }];
I remember reading the original version and thinking "Ah, this would be so much more grokkable if only I knew Haskell"... Delusions of grandeur are a marvelous thing!
So he wrote all that for the typescript lsp to respond with the answer, but when it compiles down it's nothing? And we're using runes as variables just because?
That is pretty neat, and silly.
I also think it highlights my natural aversion to static type checking in dynamic languages. I know that I could get sucked into writing a bunch of code for the checker, instead of using my energy for making the application work.
The runes are mostly "just because", but there is a reason.
Ideally, I would have written:
type Nil = unique symbol
Which would ensure the Nil type wouldn't match with anything but itself. Unfortunately, the compiler doesn't allow unique symbol other than on const variables initialised with Symbol(). So I needed some meaningless symbols.
I could also have done
type Nil = "nil"
But then it would have looked like the string meant something.
> I know that I could get sucked into writing a bunch of code for the checker, instead of using my energy for making the application work.
“Making the application work” is only half the story. Making a large application stable and maintainable is only really possible with static type safety, IMHO.
brilliant article - the TS therein is type-hints basically, helping the coder program to chess spec. so yes, in that scenario it's not generating JS (functions etc) lol
this is why i love ts when not working for a megacorp. when the ts gets too cray i just nope out. throw an any or as in there and get on with my day. wouldn't pass a code review but i don't care
IMHO it’s worth learning to properly type those cases, otherwise you never really know which parts of your application are actually being type checked.
Personally I also find coming up with the correct types to be gratifying if they’re a little tricky.
Pretty sad that computations on types look like C++98 in Typescript when C++ itself has moved on to have much more concise ways of performing computations on types in C++11 and 14 (see boost hana).
Interestingly TypeScript compiler doesn't use types for compilation (that's why you can have esbuild that compiles Typescript without understanding the type system). That is the end result of type computation is... Nothing, always, just like in OP.
Template metaprogramming is all about code generation. You would expect them to look different.
The way I think about this is that type computations can be used both for type constraint checking and for code generation. In that sense, C++s template metaprogramming system is a superset of TypeScript's, which can only do checks as you pointed out correctly.
I’d say generally the opposite is true. Most commercial software I’ve worked on typically contained only simple typing—discriminated unions are about as complex as it gets—and it’s more of a problem that people get lazy and start using “any” too much than they go overboard.
Where complex types can be a problem is when working with open source libraries, especially when the types are community-developed, separate to the library itself. The library API may not be particularly amenable to easy typing, and the community types can end up being rather confusing, especially to people who developed neither the types nor the original library.
I tell my fellow developers at work: "Any is banned. If you want any, use JavaScript, and we don't use JavaScript here. Perhaps you haven't heard about unknown?"
In my experience, 90% of the time when a developer uses any, they just don't know about unknown. 9% it's because they are lazy. 1% is because you are implementing something from an imported library, and they fell into the other 99%.
This is "stunt programming," similar to shooting an arrow into a target from horseback in a rodeo. It has little to do with the real-life business of real-life cowboys.
In this case it's using the type system to calculate the answer to a problem, which is not useful because the type system can't output anything to the console or do other IO. The "answer" will only be visible in your IDE.
The type system is powerful and extremely capable because it had to support existing javascript patterns, like "this function takes a parameter that might be a string or might be a number or might be an array" and make them type-safe.
It’s a very beautiful language even despite being compatible with Javascript. The code here is delightfully absurd, please don’t think it is representative.
I’ve worked for TypeScript companies for a while now. Most devs I’ve met are fairly pragmatic and wouldn’t try something like this for production code, but I’ve definitely met a couple who have an exceedingly deep grasp of the type system and would appreciate the whimsy of it.
> The sea of confusing types to solve any problem?
Mostly in typings either provided by the library itself or via the 3rd party DefinitelyTyped project. Some typings have been made so complex, that it is hard to follow what kind of concrete type is exactly expected or allowed.
In my experience, even the more wild/exotic patterns in typescript tend to flatten into something rather readable at their usage site. 95% of the time when I use a library that does anything close to what is done in this article I write my code, hover over it, intellisense tells me its type and I say "wow, how was it able to do that? Cool!" And I carry on.
The second is a explanatory story, or "discovery fiction" as the article classifies itself: https://paulbutler.org/2022/what-does-it-mean-to-listen-on-a...
I love these humorous yet pedagogic technical writings, woven with a bit of literary eloquence and down-to-earth narrative. Thank you for this.
https://aphyr.com/posts/353-rewriting-the-technical-intervie...
https://aphyr.com/posts/342-typing-the-technical-interview
(Also linked I the first paragraph of the link you posted, as well as in the intro of the OP.)
The ending is perfect.
https://aphyr.com/posts/341-hexing-the-technical-interview
Deleted Comment
Deleted Comment
That post was in Haskell, where it's not too surprising that you can do serious computation inside the type system.
This new post translates the ideas to TypeScript, which is more widely known, and which I once heard described as having "accidentally Turing-complete" types:
https://github.com/microsoft/TypeScript/issues/14833
The part about using the typescript language server to compute the solution, and the protagonist claiming the code is "concise" because only 4 lines of javascript were generated, was absolutely brilliant. Cracked me up at least.
Glancing at the actual code, I admit I'm with Criss in my ability to follow the logic, but it doesn't look like a direct translation from Haskel types to Typescript types either.
At any rate, very well done.
0: https://github.com/microsoft/TypeScript/issues/14833
1: https://github.com/microsoft/TypeScript/issues/14833#issueco...
Still don’t understand anything though.
That is pretty neat, and silly.
I also think it highlights my natural aversion to static type checking in dynamic languages. I know that I could get sucked into writing a bunch of code for the checker, instead of using my energy for making the application work.
Ideally, I would have written:
Which would ensure the Nil type wouldn't match with anything but itself. Unfortunately, the compiler doesn't allow unique symbol other than on const variables initialised with Symbol(). So I needed some meaningless symbols.I could also have done
But then it would have looked like the string meant something.“Making the application work” is only half the story. Making a large application stable and maintainable is only really possible with static type safety, IMHO.
Personally I also find coming up with the correct types to be gratifying if they’re a little tricky.
But in a smaller shop, oncall are developers, that is, ourselves.
Dead Comment
Template metaprogramming is all about code generation. You would expect them to look different.
I’ve never worked in a Typescript shop, is there any truth to the satire here? The sea of confusing types to solve any problem?
Where complex types can be a problem is when working with open source libraries, especially when the types are community-developed, separate to the library itself. The library API may not be particularly amenable to easy typing, and the community types can end up being rather confusing, especially to people who developed neither the types nor the original library.
In my experience, 90% of the time when a developer uses any, they just don't know about unknown. 9% it's because they are lazy. 1% is because you are implementing something from an imported library, and they fell into the other 99%.
In this case it's using the type system to calculate the answer to a problem, which is not useful because the type system can't output anything to the console or do other IO. The "answer" will only be visible in your IDE.
The type system is powerful and extremely capable because it had to support existing javascript patterns, like "this function takes a parameter that might be a string or might be a number or might be an array" and make them type-safe.
Mostly in typings either provided by the library itself or via the 3rd party DefinitelyTyped project. Some typings have been made so complex, that it is hard to follow what kind of concrete type is exactly expected or allowed.
[1]: https://github.com/DefinitelyTyped/DefinitelyTyped
As long as you're satisfied with the answer being shown on a tooltip when you hover over a variable... sure.
Notice how the whole type structure ending up as 4 lines of inconsequential javascript after going through the typescript compiler at the very end.