HN user

wwwigham

674 karma

[ my public key: https://keybase.io/www; my proof: https://keybase.io/www/sigs/Z47alkN-fwsa7FlMQZTF9A24_7hjUiskAbc5coXsxkw ]

Posts3
Comments137
View on HN

You can define a metatable on your objects of interest (or the root table meta table if you don't mind breaking the language's conventions and thus libraries) with __index and __newindex members. Then you can throw in those by calling the `error` function when they'd otherwise normally return nil, should you desire it.

But runtime checks have a cost, and static types that transpile away are a bit better for overhead so long as you don't mind the build step, so using one of the typed lua variants is probably a bit nicer in the long term. Catching those typos early is their bread and butter.

You can use file:// or git:// versioned dependencies in normal npm `package.json`s today. People just don't, outside some edge cases (local packages, nightly versions) because the centralized registry has upsides for discoverability and maintenance. There's also private registries, where you can setup a .npmrc to pull different namespaces from different registries. But if you want, you can totally be that guy who only publishes their packages to their presumably self-hosted repo - it works.

What's the business model, I wonder?

Serverless functions, right? That's what deno deploy is billed as. Presumably the registry is a platform-adjacent investment to try and bring more serverless market-share to deno. Since it provides a npm-registry compatible facade, presumably you should feel safe publishing deno-y code to it (without calling platform APIs?), and should thus be more likely to use deno, and thus enter the funnel for deno deploy.

Personally, I just use deno's rust v8 wrappers a bunch, since they make embedding a V8 runtime into a rust app very simple, and js is a very nice scripting engine (especially with optional types). A hugely valuable contribution to the open source community. But then again, I don't deploy serverless functions on the regular. To each their own.

TypeScript _itself_ has a branded primitive string type it uses internally.[1] Dedicated syntax for creating unique subsets of a type that denote a particular refinement is a longstanding ask[2] - and very useful, we've experimented with implementations.[3]

I don't think it has any relation to runtime type checking at all. It's refinement types, [4] or newtypes[5] depending on the details and how you shape it.

[1] https://github.com/microsoft/TypeScript/blob/main/src/compil... [2] https://github.com/microsoft/TypeScript/issues/4895 [3] https://github.com/microsoft/TypeScript/pull/33038 [4] https://en.wikipedia.org/wiki/Refinement_type [5] https://wiki.haskell.org/Newtype

Stock Pixel may not ship with it on by default for end users, but anyone can enable developer options and enable Memory Tagging Extensions - either until toggled off, or for a single session if you're trying to test a specific app - if you do want the feature on.

ESM was developed in the open and anyone could participate, including the TypeScript team.

This point stings for me, personally, since _I_ was the TypeScript language dev _in_ this wg trying to make our concerns noted, because we certainly did participate. However the group largely deadlocked on shipping with ecosystem compatibility measures, and what you see in node today is the "minimal core" the group could "agree" on (or be bullied into by group politic - this was getting shipped weather we liked it or not, as the last holdouts). The group was dissolved shortly after shipping it, making said "minimal core" the whole thing (which was the stated goal of some engineers who have since ascended to node maintainer status and are now the primary module system maintainers), with the concerns about existing ecosystem interoperability brought up left almost completely unaddressed. It's been a massive "I told yo so" moment (since a concern with shipping the "minimal core" was that they would never be addressed), but it's not like that helps anyone.

Like this shipped, because _in theory_, it'd be a non-breaking from a library author perspective to get node's CJS to behave reasonably with ESM (...like it does in `bun`, or any one of the bundler-like environments available like `tsx` or `webpack` or `esbuild`), and _in theory_ they're open to a PR for a fix... I wish anyone who tries good luck in getting such a change merged.

I, too, thought http/2+ would encourage unbundled js, but unfortunately in a world where people are used to whole-program minification and dynamic app slicing, I don't think we'll _ever_ move away from bundlers. The build step is here to stay for most serious projects.

ESM may very well be the module system designed for a world that'll actually never exist, and will mostly just be an ill defined compilation target. But hey, maybe the next web module system will do better - those wasm working group people are working hard on their module system - and it's intended as a compilation target from the start, so shortcomings in it can be patched over by tools from the start :)

I think this is funny, since esm being "native" on browsers doesn't really matter until you can convince devs they don't actually need to use a bundler. So long as you're using a bundler, the browser's runtime doesn't really matter - you're using the runtime the bundler presents and emulates on the browser. Native ESM has proven to be quite painful in ecosystems that _don't_ rely on the presence of a bundler to patch over problems, precisely because of the issues of interoping with, I don't know, _literally any existing code_.

I can't think of a concrete benefit to a developer that ESM brings (just pain, but maybe I'm biased by what I'm exposed to). Probably why it's so slow to be adopted.

At first, I was really excited, because I really wanted this pipeline to work well. I've wanted an automatic FPGA workload offloader for awhile. Then I read that their "JIT" FPGA bitstream still took 2 _hours_ to build because it still used the built-in slow-as-molasses FPGA manufacturer bitstream assemblers. That's not a timescale that really supports a real, dynamic workload, unfortunately. Still, the work is neat and gluing together these parts in a way the works is really cool - what is essentially a Graal FPGA backend is pretty neat, if unlikely to be on its own paradigm shifting. Still, maybe the existence of something like this can prod Intel into making a fast mode for the bitstream assembler.

Contrary to what seems to be popular opinion here on HN, I think it's fine they'd entertain keeping their code as JS with JSDoc, rather than TS. It's still typesafe, which is what's important from a maintenance and documentation perspective. A linter than goes all in on TS is what tslint was (and it worked on JS), and ultimately it lost to eslint in the linter popularity wars (as has jslint, and jshint, imo). If JS source is the sauce that keeps contributions coming and the downloads flowing, then so be it. There's also some value in dogfooding their own JS lints, rather than TS ones. Sure, there's overlap, but undoubtedly some differences, too (especially any rule that lints jsdoc).

Eh, wanting to dogfood the JS tooling you're writing is a fairly valid reason, imo. Would dogfooding on TS cover many JS usecases? Sure. But inevitably some things are different. I'm somewhat sympathetic.

V8 stopped dropping free perf wins on the JS community every year a while ago, and people are clearly starting to notice and feel like they need to take matters into their own hands. For some, that means an adventurous rewrite of their library with nebulous end results (I hesitate to call the proposal "eslint" still, since it seems to aspire to be an omni-linter). For others, it may mean abandoning JS entirely (see rome). And yet for some others, it has made them realize, just like with the other interpreted languages, the perf probably stopped being important after some unnoticed breakpoint.

To be real, I don't think they'd get any perf gains from a wasm component - not until most of the library is wasm, anyway. The dream of offloading a small hot function to a faster, instruction-optimized wasm implementation is usually killed by marshalling costs for anything other than numbers. Even the perf of simple string functions (like `normalizePath`) often get destroyed in practice by things like unicode validation, since the string models and memory layouts between JS and Rust don't match. Still, despite all that, it's tempting to _try_ because, well, what else are you going to look into for performance gains? Once you've tracked down all your v8 deopts and monomorphized all your hot code, what's left but telling v8 to get out of the way as much as possible, while you go as low level as you can? Algorithms improvements? Those often amount to random flashes of insight - you can't plan those. So when a lot of people say "Hey, I reimplemented 5% of X in Rust and it's 20x faster!" you _could_ smugly assume it's because it's a partial implementation, or you can choose to believe that the authors don't have ulterior motives and the tech stack itself has merit and investigate it yourself. It'll be easier and more fun than thinking hard about usage patterns and algorithms all day, anyway.

When you can write a busy beaver machine in the type system, LOC ceases to be a good indicator or how long something should take to typecheck, imo. If you're frustrated with your build, you should use the trace tools on the TS wiki[1] to track down what types are slow to check, so you can attribute the slowness to the appropriate library authors/yourself and decide for yourself if the speed/correctness tradeoff they've made is right for you.

[1]https://github.com/microsoft/TypeScript-wiki/blob/main/Perfo...

This tbh. If the compiler the Microsoft team maintains is open source (which most of the ones people have heard of are), having involvement in that open source community would probably also be a plus - it helps show the practical part of practical compiler development.

Anyways, many public-facing compilers (C++, C#, VB, F#, the .NET runtime as a whole, TypeScript) are in the devdiv org - if you look up Julia Liuson in the org chart and explore down from there, you can probably get a good idea of who you most want to grab coffee with (or whatever the remote work equivalent of that is now).

Outside of devdiv, there's also some esoteric c++ compilers, PowerShell, and SQL compilers housed in the windows and azure orgs, plus iirc there are some people who work on v8 fulltime around the edge team now, too.

Point is that you've got much more direct access to those teams and opportunities than most people already, being only an internal transfer away - hopefully you can make use of that.

To this day I haven't found a way of doing this in TS

You use an ambient declaration to declare the missing classish part of the type.

    declare function myLibrary(arg: Type): myLibrary;
    declare class myLibrary {
      member: Type;
      constructor(arg: Type);
    }

You see this pattern pretty often in DefinitelyTyped.

The `conformance` suite is the "set of tests people added when a feature was first released", usually without the decade of following regression tests in the `compiler` suite. In TS itself, the `conformance` and `compiler` suite are considered the part of the same unit most of the time. (The compiler suite, however, is not helpfully subdivided by feature, since it oft deals with cross-cutting concerns!)

Deno in 2021 4 years ago

Deno has _very_ custom module resolution rules. The first thing you notice is the required `.ts` extensions and browser-esque URLs. Oh, and also the import maps proposal (which is its own can of worms). Then you dig a little deeper and learn that deno also supposedly supports all `node` resolution rules sometimes via compat flags. Then you learn that it pulls types from magic `/// <reference types` comments, which sounds like what TS normally does, until you realize deno uses it to _override_ the types for JS files, and not pull in new files, which isn't something TS does at all. Oh, and `@deno-types` comments for overriding individual imports, besides.

One of the first lines in the deno docs is:

Deno has no "magical" module resolution.

Which, as an implementor of module system rules, seems incredibly far from the truth of the matter (where deno has the most complex resolution rules of any runtime currently in use). I think maybe there were simple goals at the start, but that came crashing against reality and xkcd 927 executed in full force.

In TypeScript you can assign an instance of one class to an instance of another as long as they have compatible fields.

Fun fact: If you add a private field to a class it'll behave nominally in TS. This is because private fields kinda require nominal relations to function. So, in a way, it does support "switching" to nominal type checking for classes - the opt in is simply per-class.

Typescript takes less than a minute to build, and basic PR validations (the simple regression, conformance, and unit test suites) add around 10 minutes of test running to that (to be fair, I can run those locally in just under two minutes, we just use slow CI boxes, and local incremental build and test can bring that loop down even more). The extended test suites that run on a rolling basis on `master` and on-demand on PRs can be much longer and take up to two hours to run (the longest extra suite being the DefinitelyTyped suite, where the CI system runs all of DefinitelyTyped's tests on both nightly and your PR/master and reports any changes). Technically, there is also a github crawler running periodically that rebuilds anything public and open source it finds with the latest TS and reports new crashes, and that's _constantly_ running, so I can't really say that has a fixed run time, per sey. Turns out the closer you get to building the world with your (build tool) project, the longer it takes, but the more realistic your coverage becomes.

Too bad nil/null _aren't_ typically bottom types or "base cases", but rather are real, runtime values that are sentinel values of some kind, rather than valid references to a value of some type. A distinct sentinel value that, for some usually historical reason, your compiler often thinks is OK to pretend is some interface, right up until it blows up at runtime. I get the confusion, though, since the same compilers that make that mistake often attribute some bottom-type-like characteristics to nil/null to facilitate using it (eg, considering it a valid possible value of all other expressable interfaces). Empty values and other sentinels are meaningfully different from a type-theoretic bottom type. Real bottom types don't come up much in mainstream languages - TypeScript has `never`, Scala has `Nothing`, and both typesystems have distinct representations of various `null`s and other empty values. Such uninhabitable bottom types see most of their use in describing generic constraints in situations where variance comes into play.

I think the real "WTF" here is that the go compiler quietly makes a simple `nil` written in your code into one of potentially many nil-like sentinels at runtime (contrary to what one may think), coerced depending on usage, and, moreover, that a simple `==` comparison or reflective call alone is insufficient to detect all of them. The semantics of how such a sentinel is coerced (and reflected) is surely in the realm of language esoterica and not simple beginner knowledge.

Funny that the author mentions TypeScript, then complains that maintaining a system like this that "doesn't exist elsewhere" would be hard. TypeScript, in fact, does an analysis almost exactly like what is described in the article. Possibly even more expansive, since it also allows for user-defined assertions and guards, in addition to simple syntactic narrowing.

The article mentions the importance of comments and documentation inline in code. I tend to agree - well-written code is great and all, but a good comment can bring in context external to the code and make _why_ code is what it is more clear to future readers. And reviewers. Comments explaining _what_ code does largely aren't needed - that's evident from usage. But the _why_? Some people would say code which can't be explained by one liner comments is "too clever". Well, I'm inclined to disagree - it's hard to fit a full historical justification for an awkward handling of an edge case into a single line. I once wrote a 17 line long comment above an 8 line diff; that much context felt justified to explain the odd code. A reviewer, hilariously, had this to say:

    This right here is "here be dragons" commenting level Double Dragon.
When I come back to code I've written long in the future, I think I'll be happier to have written the detailed "commenting level Double Dragon" long comments, over the more ambiguous yet still traditional `// HERE BE DRAGONS`. Mostly because that comment will give me the context I need to know if any of that _why_ has changed and thus in what way it's likely safe to change the commented code.

I feel like detecting these environments is directly at odds with user privacy and anti-tracking; but I guess google has never been anti-tracking, so that's not too surprising. Still, I'm incredibly disappointed that they'd essentially require clients be fingerprintable to auth. I feel like this is just codifying an arms race between strengthening requirements and JS environment checks, and hostile embedders ability to emulate a real runtime, and taking legitimate embedders with less incentive to participate in the race down as collateral damage.