HNHacker News
TopNewBestAskShowJobs

bakkoting

358 karma · joined March 8, 2016

TC39.
submissionscomments
bakkoting··on Goodbye, Node.js Buffer
The proposal for native base64 support for Uint8Arrays is mine. I'm glad to see people are interested in using it. (So am I!)

For a status update, for the last year or two the main blocker has been a conflict between a desire to have streaming support and a desire to keep the API small and simple. That's now resolved [1] by dropping streaming support, assuming I can demonstrate a reasonably efficient streaming implementation on top of the one-shot implementation, which won't be hard unless "reasonably efficient" means "with zero copies", in which case we'll need to keep arguing about it.

I've also been working on documenting [2] the differences between various base64 implementations in other languages and in JS libraries to ensure we have a decent picture of the landscape when designing this.

With luck, I hope to advance the proposal to stage 3 ("ready for implementations") within the next two meetings of TC39 - so either next month or January. Realistically it will probably take a little longer than that, and of course implementations take a while. But it's moving along.

[1] https://github.com/tc39/proposal-arraybuffer-base64/issues/1...

[2] https://gist.github.com/bakkot/16cae276209da91b652c2cb3f612a...

bakkoting··on Base64 Encoding, Explained
There's some additional interesting details, and a surprising amount of variation in those details, once you start really digging into things.

If the length of your input data isn't exactly a multiple of 3 bytes, then encoding it will use either 2 or 3 base64 characters to encode the final 1 or 2 bytes. Since each base64 character is 6 bits, this means you'll be using either 12 or 18 bits to represent 8 or 16 bits. Which means you have an extra 4 or 2 bits which don't encode anything.

In the RFC, encoders are required to set those bits to 0, but decoders only "MAY" choose to reject input which does not have those set to 0. In practice, nothing rejects those by default, and as far as I know only Ruby, Rust, and Go allow you to fail on such inputs - Python has a "validate" option, but it doesn't validate those bits.

The other major difference is in handling of whitespace and other non-base64 characters. A surprising number of implementations, including Python, allow arbitrary characters in the input, and silently ignore them. That's a problem if you get the alphabet wrong - for example, in Python `base64.standard_b64decode(base64.urlsafe_b64encode(b'\xFF\xFE\xFD\xFC'))` will silently give you the wrong output, rather than an error. Ouch!

Another fun fact is that Ruby's base64 encoder will put linebreaks every 60 characters, which is a wild choice because no standard encoding requires lines that short except PEM, but PEM requires _exactly_ 64 characters per line.

I have a writeup of some of the differences among programming languages and some JavaScript libraries here [1], because I'm working on getting a better base64 added to JS [2].

[1] https://gist.github.com/bakkot/16cae276209da91b652c2cb3f612a...

[2] https://github.com/tc39/proposal-arraybuffer-base64

bakkoting··on Bun v0.8
There are now builtin iterator versions of most of these looped functions [1], should be shipping in stable Chrome next month. The "give me an array at the end" function is spelled toArray.

But it's not going to help all that much with this problem. The iterator protocol in JS involves an allocation for every individual value, and while in some cases that can be optimized out, it's pretty tricky to completely optimize.

It's always going to be difficult to beat a c-style for loop for performance.

[1] https://github.com/tc39/proposal-iterator-helpers/

bakkoting··on TypeScript is surprisingly ok for compilers
You can now! The iterator helpers proposal is stage 3 and shipping in Chrome.

  (new Set([0, 1, 2])).values().map(x => x + 1).toArray()
You can also reduce, filter, find, etc.
bakkoting··on Observable API Proposal
It's not `await` which is doing that; it's the Promises themselves. For example:

   Promise.resolve(Promise.resolve("abc")).then(console.log)
This will print `"abc"`, not `Promise.resolve("abc")`.
bakkoting··on Cloud Backed SQLite
I don't know enough about how FTS works to help, I'm afraid. I don't know if it's possible to search across multiple rows.

Were I in your position I'd just create a copy of the DB with the full transcript in each row and run the search against that. If you only have 4 million words, creating an extra copy of the database shouldn't be prohibitively large.

bakkoting··on Cloud Backed SQLite
I've been using sql.js-httpvfs to get full-text search on a static Github Pages site [1]. (Just make sure you have fts5 enabled on your database.) It works astonishingly well.

[1] https://matrixlogs.bakkot.com/WHATWG/search?q=database

bakkoting··on TypeScript 5.2's new keyword: 'Using'
> I wonder if they will put forth a new proposal for dependency injection next, at this rate.

Yes, there is a new-ish proposal [1] which is motivated in large part by DI.

(Full disclosure: I'm on the committee and don't really like DI or this proposal. But we'll see how it goes.)

[1] https://github.com/tc39/proposal-class-method-parameter-deco...

bakkoting··on TypeScript 5.2's new keyword: 'Using'
You can't use a WeakMap or a WeakSet, but you can use a WeakRef or a FinalizationRegistry.
bakkoting··on TypeScript 5.2's new keyword: 'Using'
Only the value assigned to x.
bakkoting··on TypeScript 5.2's new keyword: 'Using'
That's correct, there is no escape analysis or anything like it. Though there is a way to handle the "I want to return a resource" case: the DisposableStack class. You can call `stack.move()` to "move" ownership of a resource (and rely on the place you're passing it to to do cleanup). E.g.

  using stack = new DisposableStack();
  stack.use(resource());
  if (condition) {
    // the resources will _not_ get disposed automatically by this return
    // it's the caller's responsibility to call `stack.dispose()`
    return stack.move();
  }
bakkoting··on TypeScript 5.2's new keyword: 'Using'
Because it's usually an error, and the benefit of catching the error cases outweighs the cost of losing that flexibility.
bakkoting··on TypeScript 5.2's new keyword: 'Using'
> not in for-in loops

What would that even mean? The binding in a `for-in` can only ever hold a string, which can't be disposable. With a `for` or `for-of` the binding can hold an object, which is something you could dispose of.

bakkoting··on TypeScript 5.2's new keyword: 'Using'
1. Scope exit.

2. Const.

3. On declaration.

4. I don't know what "chained resource" means. If you mean multiple declarations within the same scope, they all get disposed, in LIFO order (even if disposing one of them throws).

5. An error when the `using` statement runs. (Unless the RHS itself is null, rather than an object without a `Symbol.dispose` method; this lets you have conditional declarations.)

bakkoting··on TypeScript 5.2's new keyword: 'Using'
The article is wrong. That's not going to be legal, specifically because it's confusing which object is being disposed.
bakkoting··on Apple Introduces M2 Ultra
They actually just today released an update [1] with fixes for sleep:

"We now have a cpuidle driver, which significantly lowers idle power consumption by enabling deep CPU sleep. You should also get better battery runtime both idle and during sleep, especially on M1 Pro/Max machines.

Thanks to the cpuidle driver, s2idle now works properly, which should fix timekeeping issues causing journald to crash."

[1] https://social.treehouse.systems/@AsahiLinux/110497512340479...

bakkoting··on Zig is hard but worth it
There's a not-very-active proposal to add operator overloading to JS which takes a similar scoped approach:

https://github.com/tc39/proposal-operator-overloading

bakkoting··on Llama.cpp 30B runs with only 6GB of RAM now
I'd guess the GPTQ-for-LLaMa repo is using a larger context size. Poking around it looks like GPTQ-for-llama is specifying 2048 [1] vs the default 512 for llama.cpp [2]. You can just specify a longer size on the CLI for llama.cpp if you are OK with the extra memory.

[1] https://github.com/qwopqwop200/GPTQ-for-LLaMa/blob/934034c8e...

[2] https://github.com/ggerganov/llama.cpp/tree/3525899277d2e2bd...

bakkoting··on Llama.cpp 30B runs with only 6GB of RAM now
Some results here: https://github.com/ggerganov/llama.cpp/discussions/406

tl;dr quantizing the 13B model gives up about 30% of the improvement you get from moving from 7B to 13B - so quantized 13B is still much better than unquantized 7B. Similar results for the larger models.

bakkoting··on Modern SPAs without bundlers, CDNs, or Node.js
> $ echo '{}' > package.json && npm i --save-dev typescript vite preact

> added 17 packages, and audited 18 packages in 1s

You're off by two orders of magnitude. I get not wanting a lot of transitive dependencies, but you can get a modern toolchain without having a lot of transitive dependencies.

bakkoting··on Yark: Advanced and easy YouTube archiver now stable
Seconding tubearchivist. One of the killer features IMO is the browser extension [0], which adds a button on every video to send that video to the server to archive.

[0] https://github.com/tubearchivist/browser-extension

bakkoting··on A non-constructive proof of the Four Colour Theorem
Terence Tao is also concerned about that section: https://mathstodon.xyz/@tao/109554420101377184
bakkoting··on A non-constructive proof of the Four Colour Theorem
How about Manifold? https://manifold.markets/ZviMowshowitz/will-the-new-nonconst...
bakkoting··on Lesser-known JavaScript APIs
The same options [1] are built into `String.prototype.localeCompare` (which is just a handy shortcut for calling `Collator` [2]). So for example:

``` ['B 1', 'A 20', 'A 3'].sort((a, b) => a.localeCompare(b, 'en', { numeric: true })) // ['A 3', 'A 20', 'B 1'] ```

[1] https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refe...

[2] https://tc39.es/ecma402/#sup-String.prototype.localeCompare

bakkoting··on Substack is (not) now powered by Ghost
> The files actually do have a link at the top to the license file

That's not actually the license for the file; it's the license for the resources it includes. The license for the file is available elsewhere but is not directly linked. See my comment at https://news.ycombinator.com/item?id=33959622

bakkoting··on Substack is (not) now powered by Ghost
Here's [1] the actual JS file which Substack is loading, pulled directly from the network tab on the page linked in the thread.

Notably, the first line is "For license information please see sodo-search.min.js.LICENSE.txt". But if you go to that file [2], it's not the license _for this file_; it's the licenses _for the OSS code it includes_. I suspect that Substack thought that link pointed to the actual license; I did too before I started writing this comment. Possibly that confusion has lead to some talking past each other.

The actual license is at [3], which is obvious if you know how npm packages work, and probably not obvious otherwise. I don't see a link to that file anywhere.

[1] https://cdn.jsdelivr.net/npm/@tryghost/sodo-search@1.1/umd/s...

[2] https://cdn.jsdelivr.net/npm/@tryghost/sodo-search@1.1/umd/s...

[3] https://cdn.jsdelivr.net/npm/@tryghost/sodo-search@1.1/LICEN...

bakkoting··on Substack is (not) now powered by Ghost
Note that caching resources across sites isn't really a thing anymore. See https://github.com/whatwg/fetch/issues/904
bakkoting··on Why would anyone need JavaScript generator functions?
There's an active proposal which would let you do exactly that, going for stage 3 (ready for implementations) at the end of the month: https://github.com/tc39/proposal-iterator-helpers
bakkoting··on Why would anyone need JavaScript generator functions?
Babel does this, using a library called gensync. I have a writeup which goes into more detail: https://writing.bakkot.com/gensync

gensync instead creates two functions, one sync and one async, which is probably a more familiar API for consumers of your code.

bakkoting··on Universal Paperclips
The first interesting thing happens when you have $5 of available funds. (Specifically, autoclippers become available to purchase.)

When precisely you hit that point depends on how well you are managing the price to balance ensuring that your unsold inventory is getting purchased reasonably quickly against getting a reasonable amount of money per unit sold, but it will probably be less than a minute.

← PreviousPage 4 of 5Next →