This all just sounds like problems we see when making new features, of any sort, for customers. A feature is never objectively done, there are many opinions on its goodness or badness, once it’s released its mistakes can last with it, etc.
If this is a wicked problem, then so is much of other real-world engineering.
To be clear, is AI actually at play here, aside from the fact that the repo is for Gemini? It just looks like two simple rules that interact poorly, that we could've seen in 2015.
Well, it's even more ironic as AI in general is touted as smart. I'd fully expect such bots to notice they're in a loop and one to throw the towel. Still a long way to AGI. And to AI for that matter.
The results at https://www.mattmahoney.net/dc/text.html explicitly add the size of the compressor itself to the result. Note the "enwik9+prog" column. That's what it's ranked on.
The reason to do this is that it's trivial to create a compressor that 'compresses' a file to 0 bytes. Just have an executable with a dictionary of enwik9 that writes that out given any input. So we always measure what is effectively the Kolmogorov complexity. The data+program as a whole that produces the result we want.
So those results add in the compressor size. The programs there generally have no dictionary built in or in the case of LLM based compressors, no pre-trained data. They effectively build the model as they process data. Not compressing much at all at the start and slowly compressing better and better as they go. This is why these programs do better and better with larger data sets. They start with 0 knowledge. After a GB or so they have very good knowledge of the corpus of human language.
This program here however is pre-trained and shipped with a model. It's 150MB in size! This means it has 150MB of extra starting knowledge over those models in that list. The top models in that list are the better compressors, they'll quickly out learn and overtake this compressor but they just don't have that headstart.
Of course measuring fairly this should be listed with that 150MB program size added to the results when doing a comparison.
The complexity of a simple turing machine is itty bitty, and you can bootstrap that into an x86 emulator in a matter of kilobytes, so when we're messing with 100MB files it's not a big factor.
> Responses to my publication submissions often claimed such problems did not exist
I see this often even in communities of software engineers, where people who are unaware of certain limitations at scale will announce that the research is unnecessary
Solids and liquids mostly don’t compress so as a general rule most can handle those pressures without experiencing any real mechanical stress, as they instantly provide a perfectly matching internal pressure that balances out the forces to zero.
It’s mostly things that contain gases that can get crushed by high pressure. Almost any type of closed cell foam for example, will either collapse to a small size or crack and crumble apart depending on how rigid it is.
Living things tend to get harmed by pressure changes because they have compressible gasses and/or biological compartments that contain things that experience phase changes between gas and liquid at different pressures.
Even so, wouldn't you expect that you could crush an open empty beer bottle by putting a heavy enough weight on it? A human can't do it, but I would expect an elephant can.
There is quite a lot of pressure put outside from the beer of a full bottle, but that little bit of air is probably enough to cause it to implode at some point.
I'll be honest; I have no idea how to estimate that. I'm sure there are folks on here who can (and might). It's probably not as deep as you'd think.
What does this sort of language complexity mean for future changes in Rust? In C++, its existing complexity makes new changes so much more difficult. Is Rust reaching a similar place?
Can you be more specific? What language complexity are you referring to? Plenty of things in this post are hypothetical, not actual features being surfaced by the language.
Rust only supports & and &mut references. This was a deliberate choice early in the development of the language.
&own, &pin, and &uninit are proposals for additional pointer types. They don't actually exist in the type system right now, but other parts of the compiler do have to care about them. Another blog post that floated around here about a month ago called these "inconceivable types"[0]; adding them to the type system would allow formally extending these behaviors across function boundaries.
Like, right now, implementers of Drop can't actually move anything out of the value that's about to be destroyed. The Drop trait gets a &mut, but what we really want is to say "destroy this value over there". Rust's type system cannot understand that you own the value but not the place it lives in. What you need is an "owned reference" - i.e. &own, where the borrow checker knows that you can safely move out of it because it's going to get destroyed anyway.
Rust also can't support constructors, for the same reason. What we really have are factory functions: you call them, they return a value, you put it somewhere. This is good enough that Rust users just treat factory functions as if they were constructors, but we can't do "placement new" type construction with them, or partial initialization. At least not in a way the type system can actually check.
&pin is a first-class version of Pin<T>. Rust was originally designed under the assumption that any type can be memcpy'd at any time; but it turns out not being able to move types is actually super useful. Fortunately, it also turned out you could use smart pointers to pin types, which was 'good enough' for what it was being used for - async code.
Actually, the blog post that coined "inconceivable types" was specifically talking about writing async functions without async. It turns out Future impls encode a lot of details Rust's type system can't handle - notably, self-borrows. If a value is borrowed across an await, what's the type of the variable that got borrowed from? It's really a negative type: borrowing T to get &T also turns T into !'a T that you can't access until 'a ends. Each borrowed reference is paired to a debt that needs to be paid back, and to do that you need lifetime variables and syntax to explicitly say "pay back this debt by ending this borrow's lifetime".
How much of this complexity is actually needed is another question. There's a problem that each and every one of these reference types (or, anti-types) is intended to solve. Obviously if we added all of them, we'd overcomplicate the type system. But at the same time, the current Rust type system is already known to be oversimplified, to the point where we had to hack in pinning for async. And it's already kind of ridiculous to say "Well, async is all special compiler magic" because it prevents even reasonable-sounding tweaks to the system[1].
[1] For example, async does not currently have a way to represent "ambient context" - i.e. things we want the function to be able to access but NOT hold onto across yields. That would require a new Future trait with a different poll method signature, which the current Rust compiler doesn't know how to fill or desugar to. So you have to use the core Future trait and signature which doesn't support this kind of context borrow.
I think only a small percentage of users care that much about running LLMs locally to pay for extra hardware for it, put up with slower and lower-quality responses, etc. . It’ll never be as good as non-local offerings, and is more hassle.
If this is a wicked problem, then so is much of other real-world engineering.