Basic operators more-or-less done

It’s been a while since I’ve posted an update, so here goes.

The following binary operators are now implemented for signed and unsigned integers: +, -, *, / (signed only at this point in time), all the comparison operators (==, !=, <, <=, >, >=). Also, for booleans, &&, || and !. For and and or, the right side is evaluated only if the left side is insufficient to determine the expression’s result.

While far from extensive, these should be sufficient for getting the basics up and running. At this point, I’m moving on to other, much needed, work on the language. Next on my list is pointers, and I’ll probably do structs after that.

If you want to actually start playing with the language, and find that a particular operator is missing, please feel free to drop me a note and I’ll add it. Individual operators are, as a rule, not hard, but getting them all takes time that I’d rather not spend on it right now.

As usual, share and enjoy.
Shachar

From here (https://stackoverflow.com/questions/56585109/) I wonder why group & with */%? This (bitwise precedence) was considered a mistake by C’s authors. As you probably know it happened because && and || came along later. But I would advise keeping the bitwise operators in their own group. It’s just easier to reason about that way.

The way guidelines are often written presume nobody knows precedence or is willing to check a reference if they’re not sure, so guidelines suggest always using () because lazy programmers won’t know otherwise, but that gives a false impression to people who do know that something unusual is happening in the code. A () should be a sign that the regular order is changed in an expression.

For reference: https://specs.practical-pl.org/en/latest/operators.html

It would be nice if C++ could make some of these changes. I don’t think it’s impossible but would require thinking of syntax as a cosmetic, trasformational layer on top of an abstract language. It would be nice if we thought of formatting (like always using spaces and parentheses like Swiss-cheese) more like fonts. Syntax could be that way too. We could choose a preferred style font. There’s a lot of low hanging fruit in programming. No one’s even optimizing C++'s templates at a high level so the compilers run one or two orders of magnitude more slowly than they could (and chew up insane amounts of memory). It would be nice to see some real experimentation with the C++ formula.

Hi Mick. Welcome to the forum.

Practical’s bitwise ops do not have the same relative precedence as C++'s ops. The problem, as I understand it, is that the bitwise ops in C/C++ have lower precedence than the comparison ops. As such, expressions such as flags & mask == 0 bind counter-intuitively as flags & (mask == 0). This problem is fixed by Practical.

There is always room for improvement, but I don’t think I understand what you’re suggesting. Can you give a concrete example where an expression binds counter-intuitively under Practical’s suggested precedence?

I don’t see it. I think there is an inherent value to binding & at the same level as *, and | at the same level as +.

With that said, feel free to propose an alternative precedence table, and we’ll discuss.

That’s where I disagree with you. At this point in time, changing something as fundemental as this is practically, if not theoretically, impossible. Otherwise, I might have attempted to change C++ instead of going through the insanly insane insanity that is creating a new programming language from scratch.

I’m not sure how much improvement can be added to the way C++ does template matching. Where Practical aims is to encourage the programmer to only use templates for actual templates. Compile time manipulation of the program should be done with other, dedicated, facilities.

Finding a practical (huh!) way to do this was the hook that got me started working on Practical, but it is, sadly, still a bit in the future.

I was just curious if you have a rationale for hoisting & above +- and not putting it among its fellows (|^) because that doesn’t seem very practical, obviously it’s counterintutive since it’s a bitwise operator and not a multiplicative operator. You don’t offer that explanation. I thought it might be interesting to hear.

Especially if C++ adopts “modules” (encapsulation) then code could (theoretically) opt into basic changes like rectifying operator precedence mistakes. It would be a harder transition than usual but ultimately keeping mistakes indefinitely accumulates more pain than pulling off the bandaid so-to-speak.

How I found your project was to do with pointer-to-member’s weird precedence. In that case it’s very strange that . and .* have different precedence because even something like using ! on an object causes it to bind to a bool which is clearly unnatural. . (and ->) only appears to the right of identifiers or postfix operators, so in that case it seems like the language would long term benefit from removing that “gotcha”, for example. I don’t think it’s right to say certain kinds of past mistakes are locked in forever and the only solution is to throw away the language or fork it.

I think you have the making of an interesting FAQ of anomalies in C++ and maybe other languages. But I’d caution that programmers have historically had very, very bad instincts, so attempting to formulate a knew language as you are is going to pick up on a lot of bad decisions unless you have 20 or 40 years of experience and are particularly gifted. If your project were closer to C++ I would offer you my opinion on more matters. There aren’t many looking to make alternatives to it, and many would like a more hardened compiler that maps more naively to assembly, and right now with LLVM designing compilers is an approachable project. There might be fierce competition in this area in the next decade, and I do think it would benefit from separating syntax from logic and style.

Unfortunately not many people are interested improving the compiler’s performance. Anyone who works in algorithms knows how you can optimize for certain high-level criteria. So far no one is working in this area. If people are uninterested in doing hand optimizations then they should look into machine-learning since 99% of templates follow basic well-known patterns. They just have to be identified as a pattern and then 99% of the CPU and memory resources applied to them would be eliminated. For example, a unique_ptr is just a pointer, and so are a huge array of templates, but no compiler actually treats them as raw pointers for bookkeeping purposes. They pretend they’re black boxes so build times are through the roof when they don’t have to be. That’s a simple example. (I’m just talking about things that boggle the mind about C++, how primitive it in fact is, and that it’s not really a mature language like people think.)

I don’t think there is any intuitive answer to “what happens when bit and arithmetic operators mix”. Since Boolean logic treats & as * and | as +, it made sense to me to put them in the same precedence.

I don’t like such statements. Since it’s my own time I’m risking here, I don’t think a statement saying “you have to have such experience to participate” makes sense.

And I say that despite the fact that, at least as far as experience goes, I actually do meet your criteria. I’ve started programming 38 years ago.

I think I failed to make my point. My point was that Practical will have other facilities for those cases where C++ templates are slow.

Can you give an example of how knowing they are “just pointers” would help compilation speeds?

Well like it or not my point is you can take a stab in the dark at a programming language, but if the goal is to actually satisfy the wide world of C++ system programmers beginners have a lot of ideas that historically haven’t panned out, so whatever something someone comes up with is probably going to be biased to early influences instead of optimal. Historically trends in programming haven’t been good influences. So we have a long track record of mistakes. (I.e. wildly popular trends/orthodoxies that are today looked back at with embarrassment and nobody stands by. Programmers have bad instincts and an almost cargo cult like mentality. I don’t know why that is. It’s a personality type I think.)

Sure, like with smart-pointers every type of pointer is a template instance. GCC for example seems to evaluate the entire template when it’s instantiated (per type) and load its entire definition into memory. For an XML schema where every type of element is its own type for me that quickly outgrows my 8GB of RAM and GCC kills itself instead of switching to virtual memory. Instead it could be identifying this template as a pointer wrapper that’s identical for all instances except for the type it matches. Then it wouldn’t have this problem. And it wouldn’t have to instantiate every method as a unique code tree. They’re all the same. C++ template compilers still more or less use this brute force approach that’s very memory and cache intensive. I can’t find any papers on anyone optimizing for common template patterns, but it’s pretty much not the kind of thing you can find search terms for, since searching for “optimization” in the context of C++ keywords will only find results for enabling optimization in your compiler. It surprises me big software companies aren’t interested in bettering C++ WRT templates, especially since the “std” library leans on templates so heavily. I think it’s time the standard should require this kind of high-level optimization.

EDITED: For the record I don’t see how & are * related, no offense. I don’t have a dog in this fight but like operators should be in their own groups, just like . and .* and -> and ->* should be side-by-side. I wouldn’t overthink it. It’s how mistakes are made.

Identifying this particular case makes no sense. We want the standard library to be just another case of language written code.

I can think of two things that could be done in this case:

  1. Identify that the templates are single use and flush them from the cache.
  2. Identify that all the templates are identical in implementation to each other, and merge their cache.

1 is tricky. There will always be a worst cases, and it seems you’ve hit one.
2 is a tradeoff between the time it takes to merge the implementations and the amount of resources this merge saves. I might do something like that, but it’s way in the future.

I’ll ask again: what’s the precedence order you imagine?

I guess */%+-&|^?

On templates the performance is unacceptable, it would be one thing if the standard library used templates very lightly (big projects avoid templates because they degrade build times severely) but it does the opposite. The template just has to be analyzed where it’s defined, its degrees of freedom must be determined, then when it’s expanded/instantiated the minimal impact path through its degrees of freedom must be taken on site. No caching should be done. The approach taken now is to treat templates just like non-template code, so that there are layers where a template version is converted into a regular class in the compilers internals. That clearly doesn’t scale, plus it adds overhead.

I think Google or whoever should be optimizing templates just like they optimize their search/ranking algorithms. No one is approaching them the same way you would optimize a video game. It’s easy to see that they have few moving parts. You can reason that some templates are trivially simple, in which case their representation in the compiler should be trivially simple too, or else we can blame compiler authors for sitting on their hands, wasting everyone’s time (literally so as code takes hundreds or thousands of time longer to build.)