This is great, this is some good work. But, like an issue I described before with Linux kernel io-uring[1], this fails to test and demonstrate performance with a healthy server load.
This obviously fails to depict a healthy server load because it's just way too specific.
You're not wrong, it's a synthetic test. I went with this suite to AB test other WSL kernel flag changes, and used it for testing 2.x vs 3.x when I noticed my builds seemed a lot faster with the upgrade.
Probably because C and C++ are mostly used in system and desktop development.
Rust is taking steps or has been taking steps in this direction too.
It's not surprising that a lot of experienced systems and desktop development engineers looking to getting their hands on the newest tool already have experience or at least familiarity with C and C++.
The way C handles (prior to C23) booleans is pretty close to how you'd handle booleans in assembly.
CPUs don't have types, everything is integers or floats. You do your work on registers which have fixed sizes. CPUs have built in instructions for "is this register not zero" which leaks into C. 1 is true in C, but so is 2.
Also, single bits are rarely used for booleans because it requires more CPU power to extract a single bit. Everything is byte aligned at a minimum.
When doing something like network code, if you want to store a bunch of booleans you are typically going to either pack them into a byte, or you'll burn the extra bits and send a single byte for the boolean value. Typically this was flags and masks.
> Also, single bits are rarely used for booleans because it requires more CPU power to extract a single bit.
Not necessarily, on modern CPUs memory access is often the bottleneck. If you have many bits storing these in a bitarray can be quite beneficial for performance.
Only if you have an application which can somehow utilize an array of bits. It's been quite rare in my career to see that in practice. I know of only a few corner cases.
Regardless, due to the way most CPUs work, you almost certainly aren't saving anything by using a bit instead of a byte. When a CPU goes to load a memory address it'll not just get that address, but also the addresses around that address and shove them into cache. With the thought that "If you are loading 1 byte, you are likely loading 128 bytes". The cost savings really only comes into effect when you have more boolean values than bytes that fit in a cache line.
I feel this comment misses some important context; in assembler, you might return a boolean value from a function in a register, but you also might just set a flag (which is much more similar to the "one bit value"). A flag register is very much a set of booleans, not an integer.
> single bits are rarely used for booleans because it requires more CPU power to extract a single bit
I don't believe this to be true; (on x86) `cmp X, 0` is exactly the same amount of operations as `test X, pow2`. One of them is a `sub` and one of them is an `and`. I believe this is how it works on all important CPU architectures of today back to the early microcomputers (although I don't know how the minicomputers handle this, so it's possible that booleans being integers makes more sense on a PDP-11 or a honeywell).
From what I can tell in a cursory web search, that regret is more about issues with C++ being unable to abstract that field vs bit divergence properly, not about the concept of having the specialization be bit-based, which is sound.
I think your description is technically accurate, but maybe this phrases it better:
std::vector<bool> is a perfectly nice growable bit array type, and if the exact same code were in the C++ standard library named std::growable_bit_array nobody would be annoyed about this type, some people would use it, others would ignore it, nobody would write epic rants about it or name it the singe worst thing about C++.
The problem is that C++ popularized generics, and std::vector<T> is a generic growable array type, you ask for a std::vector<Goose> you get a growable array of your custom Goose type, great idea, very popular these days -- yet std::vector<bool> is not a generic growable array of bool, it's this other thing instead that's similar but not quite similar enough to be a drop-in replacement.
In C++ there is no way for the specialization to be bit-based without it being apparent that you are not in fact getting a growable array of the bool type.
No, that's not really an accurate rephrasing. Your comment indicates the problem is that it was exposed as a specialization, KerrAvon is saying the problem was instead that the specialization wasn't effectively abstracted (hidden) behind the API of vector (ie: implication is that ideally we'd have a better API for vector that might require C++ language changes due to the nature of the problems with vector<bool>. not that we shouldn't have the specialization)
> Your comment indicates the problem is that it was exposed as a specialization
Because that is the problem? That's what C++ programmmers are unhappy about.
> ideally we'd have a better API for vector that might require C++ language changes
Ideally after almost thirty years "maybe we could re-design the whole programming language to paper over this bug?" would be such an obviously bad idea that I wouldn't see it in a response.
I recommend not indicating you're rewording another commenter when you actually disagree with them if you don't want that difference in content pointed out. I note that you've trimmed out the parts of my comment that made it clear I was drawing that distinction. Maybe you missed those?
Separately, though, it's not accurate to call this "papering over [a] bug". Wanting to have a thing that behaves like a reference that can identify things that aren't addressable directly (ie: having custom reference representations) shows up in a surprising number of places as desirable because we use references (and things like references) as an abstraction in many programming languages to enable nicer, more usable syntax (ie: having assignments behave in a particular way).
Edit: and changing the language would have been at the time that std::vector was designed (ie: c++98-ish c++ has a real problem with abstracting over not-really-references). With current c++, we can get a reference-like-object much closer to being fully transparent (`auto&&`, ranges iterator model, etc). But std::vector regrets reflect the constraints of the time when it was initially designed/standardized. C++ doesn't have a good story for making breaking changes, so if folks want a good vector for bits, today they'll go elsewhere.
> I recommend not indicating you're rewording another commenter when you actually disagree with them if you don't want that difference in content pointed out.
I can see why you're upset but KerrAvon describes their statement as the result of "a cursory web search" and it isn't false so I wanted to be clear that what they wrote was not wrong per se. I don't think I "disagree" with a person who used Google, read some stuff and came away with an emphasis on the technical reasons this is a problem rather than how it's actually a problem for real programmers.
Maybe I do disagree with you, that's very possible.
With current C++ you could design a specialization that's somwhat less crap. But what's actually important - and what would actually happen - is that you would not standardize such a specialization at all because everybody knows it's a bad idea to do this - it would still be crap if you tried today, just less crap than in 1998.
C++ chooses to have a bad story for breaking changes. That's a deliberate choice and should be understood that way. WG21 are not hapless victims.
I definitely agree that the state of the committee's management of C++'s ability to have breaking changes is not good. Languages should really learn from how rust is handling that pattern, as it seems like it's working fairly well there. Though rust so far has a much shorter lifetime, and hasn't needed to make as large of changes as C++ has needed, the editions model (or something like it) with isolation between translation units seems like it's very workable as a general pattern.
I suspect this is because they are usually introduced like "here are bitfields, but DO NOT USE THEM for anything that might become ABI because their order is implementation defined". I think that causes people to mentally file it under <sketchy language features to avoid> when actually there are many contexts where the ABI risk is not real.
I'm going to project here. Please don't negvouch. I know I am projecting here.
I'd like to note that it seems your life at a computer mostly revolves around Emacs and emacs only.
This doesn't sound sane to me. And you should probably try to diversity when it comes to your engagement with your computer.
The whole computer is supposed to provide you with an ecosystem but Emacs just by itself will not provide you with a whole ecosystem in which you can trive in.
I feel there's a general conflict in what exactly to quantify as 'art'.
At least at professional level, art is developed with a purpose / objective / intended function. For instance, I might visit Africa as someone who has living in Europe all my life but when I go back to Europe, then I decide, well. I should probably put some of what I saw and experienced in Africa in writing or drawings. The above scenerio is quite different from someone who generally draws and writes for leisure.
This is something that happens a lot and happens to apply in all sorts of trades. The situation is often that I engaged or experienced something in real life and would want to express the experience in digital form or some other form.
That said, this conflicts with the general idea of 'art' and other disciplines like 'philosophy' which are generally considered as things that people do for leisure.
For as the primary functionality of your local computer is as a desktop then you do indeed need to be running a server(be it a VPS, dedicated server or whatever).
Otherwise, then I would ask the question? What do you use your local computer for? Playing games? Sounds childish
The bottomline will remain, there's functionality you can get from a $5/month VPS that you can never get out of a desktop. Do I really need to list them? Probably not because they're too obvious. I mean, a server gives you access to the internet.
But, anyways, I forget servers are not useful anymore because we have all move to the AI protocol :-)
I'm not sure whether someone else has commented on this. I am yet to read the comments. Maybe I will read them soon enough.
Anyways and however, speaking in strict mathetical sense, the model of a tree actually breaks the classical mathematical model of operator precedence.
1 + 1 + 1 evaluates to:
(+)
/ \
(+) (1)
/ \
(1) (1)
The above will be correct, mathematically, but will break once you involve multiple mathematical operators in the statement. Because, mathematically, operators have precedence.
The operators are essentially ordered based on their depth into the right of the question / statement but unless I am terribly wrong, this is not the case in mathematics and some operators have higher precedence regardless of their position in the statement.
Then the author fixed the left-recursion and precedence (which also looks good,) but then complains about the fixed version - "the “shape” of expressions feels completely lost in this new formulation." :
Expr =
Factor
| Expr '+' Factor
...
Then the author takes us through Pratt parsing and ends up at:
fn expr_bp(lexer: &mut Lexer, min_bp: u8) -> S {
let mut lhs = match lexer.next() {
Token::Atom(it) => S::Atom(it),
t => panic!("bad token: {:?}", t),
};
loop {
let op = match lexer.peek() {
Token::Eof => break,
Token::Op(op) => op,
t => panic!("bad token: {:?}", t),
};
...
Yikes! I think he criticised the wrong code. I'll take the '{expression} is a {factor} or an {expression plus a factor}' formulation over the 'mut-loop-peek-panic-lexer-next' approach any day!
Turns you have a fairly, if not a very complicated tree for a simple problem. But, fair, enough, you can always get AI to write the code for this and probably, the code will be reused over and over.
But, is it just me or does someone else think that having to rebalance or re-order the tree might be a good breaking point for the camels back?
If you want to have the full operation recorded this is the way to do it. Either this or a non-binary tree in where multiple operators with the same operand and level are all together:
If you transform the mathematical operation to lisp terms you will have the tree explicitly written.
The only way that I can think of to make it simpler is to go the array language route of going right to left and ignore precedence, or some similar way of working.
The code to create the tree should not complex, so yes, AI could be used, but most competent coders should be able to create a basic version and test it in an afternoon.
If you are parsing left to right it can be quite easy to balance things, you should not need rebalancing at all. When you are in an operation node and you have to add an operation of the same level of priority, you always get the existing operation and subtree, put it on the left of the new operation, and continue from there.
With this if you try to do - next to a - or +, or a / to a / or *, you'll be preserving the order of the operations, and the calculation will be correct. Try it with 2*3/4.
(*)
/ \
(2) (3)
(/)
/ \
(*) (4)
/ \
(2) (3)
If you add some more operations to the right (*2/5*12/7) you keep growing the tree. It will not be balanced, but it doesn't need to be.
>The code to create the tree should not complex, so yes, AI could be used, but most competent coders should be able to create a basic version and test it in an afternoon.
I generally tend to find the idea of 'competent' coders misleading. For the most part, I have been developing or writing code in a certain language for while, then it turns out that I am competent coder? Because, yeah, I've been using C or Python for a while and can easily solve a lot of "complex" problems in C but with all due respect, I am obviously not a competent coder. It's like a situation where you frequent a certain part of town that you're very familiar with it and the people living there but then at the same time you don't live there - lol.
Anyways, for this problem I would parse this statement but definitely not into a tree. I'd assign the integrals to objects. I would then parse or go through the statement again executing the operands.
I'm not saying that competent coders will see the problem and think about doing trees and code it that way. I'm saying that if you have that problem and want to solve it with trees (so knowing the problem and a solution) they should be able to program it.
And in a way you are solving it the same way, if I understood you correctly.
Assuming you mean that you'd have classes, and create objects with the operations, it's the same as a tree.
1+2*3^(4+1)+2/3 -> plus( plus(1, mul(2, power(3, plus(4,1)))), div (2/3)) would be the object hierarchy created.
OTOH if you are parsing it and doing the operations that can be done because all the operands are known:
You'd be, once again, doing the tree but instead of having it as an structure you'd be directly parsing the leafs than can be operated and act on them. This way would maybe be faster for simpler expressions (no need to construct the tree), but probably be more expensive than tree construction and resolution for more complex ones.
I'm absolutely stuck on your comment that trees are the wrong approach, or that trees are somehow incompatible with precedence. They encode the precedence more explicitly and unambiguously than the original string itself.
Trees being incompatible with precedence is like parentheses being incompatible with precedence.
>I'm absolutely stuck on your comment that trees are the wrong approach, or that trees are somehow incompatible with precedence. They encode the precedence more explicitly and unambiguously than the original string itself.
Trees being incompatible with precedence is like parentheses being incompatible with precedence.
IMO, trees are not incompatible with precedence but I just prefer a more barebones approach to this fairly simple problem.
What's more barebones than a simple tree structure as used by essentially every compiler and interpreter, at least for one stage as an intermediate representation, out there today? (If they cover precedence, Forth implementations for instance don't care about it so don't need to use an AST even for an intermediate representation.)
Operators not only have precedence, but also associativity. Building a parser that works for arbitrary precedence and associativity is a standard school project that takes only a few lines more code than a parser that deals with operators with a single precedence level.
Here is an example in Haskell for operator parsing that uses nothing no more than the standard library: https://hackage-content.haskell.org/package/parser-combinato... Excluding comments it’s less than 50 lines of code, and it handles arbitrary precedence, prefix and postfix, ternary operators, infix operators with all three kinds of associativity.
Really I wonder how this turns out to be diskless while you're clearly accessing a disk/drive over the network. Shouldn't we refer to this as network boot?
This is great, this is some good work. But, like an issue I described before with Linux kernel io-uring[1], this fails to test and demonstrate performance with a healthy server load.
This obviously fails to depict a healthy server load because it's just way too specific.
1.https://news.ycombinator.com/item?id=44604793
reply