I can't say that I completely follow all that's being discussed here, but I have seen a few statements pass by that I found confusing and it might be of some use that I share some comments based on what I myself have experienced so far. I'll admit I lost track of who said what, so I'll focus on the themes that came up as I heard them, without quotes. Do correct me where I misunderstood what was actually going on.
First off there seems to be some confusion about the whole associativity situation in computer algebra: - first things first: when using floats you can't rely on (a+b)+c == a+(b+c): the reason is that every time you execute an algebraic operation you need to round the result, and in the two expressions the roundings are not equivalent, so here's a classic counter example: imagine that b has an exponent that is k bits lower than a and that k > 3. Now if the kth least significant bits in b are 101xxx (x meaning any) the rounder(*) will round "away from zero" if a and b have the same sign (up, if they're positive). Now: if c just so happens to knock off the 1xxx part of b, you can see the rounding will change its mind on you depending on how you associate. In particular if it just exactly zeroes it out you'll even get round-to-even behaviour (which is slightly more convoluted to reason about). - (*) this assumes round-to-nearest, there are equivalent discussions for the other rounding modes, but I don't believe we'd employ them. - So: for the compilers I'm familiar with (say an obvious set: gcc, clang, msvc, nvcc, icc, the usual suspects) you need to be -O3 (or enable specific optimizations with their flags) for this to happen. Otherwise a+b+c will be dispatched in source code order ie (a+b)+c otherwise it's a bug. (It's common for compilers to have -O2 to mean "only safe maths optimizations" and -O3 is more "play a little looser with maths") - If you're using integers none of the above applies - Compilers translating a+b+c as a+c+b: (integer only, as discussed) this happens for latency hiding, or register pressure reasons. Different compilers reason about this differently, but in general: either they ran out of register, and they're trying not to spill (which on x86 is not that big a deal if it stays in L1) or there is evidence b is coming straight from memory and its load cannot be hoisted up far enough to guarantee the stall won't happen (this again speaks to register pressure). Note that the reorder only saves you a cycle or two on a reasonably recent processor (10yrs?) so yes there's "a difference" but I'd have trouble believing it'll be a material one (an L3 or main-memory load costs you tens of these on a good day). This applies to +,-,*. Division is different, but that one doesn't reorder as often and it's relatively rare anyways. To the issue that languages don't come with built-in interesting object systems/algebras and units: no they don't, and for good reason except that the reason is the opposite of what was implied before. It's not because it's not useful, or harmful, all the contrary: they expect you, the user, to write your own libraries to deal with this. That's one of the key reasons for having object orientation: making objects belong to classes of behaviour that make your code behave as-close-as-useful to the entities you're modeling. Adding rich algebraic/units systems does happen, I wouldn't say it's particularly rare: here's a reference from my field https://dl.acm.org/doi/10.1111/j.1467-8659.2010.01722.x, associated with a study of how much it helped. Another point of evidence that folks use this is https://www.boost.org/doc/libs/1_65_0/doc/html/boost_units.html, if it made it to boost it should be reasonable to infer that people care about this. Further, unless I'm confused, this discussion is about scheme algebra, not C++ algebra, is that right? If that's the case, surely the truckloads of boxing/unboxing (or whatever you call variable-to-value dereferencing in guile) you're doing to implement the language semantics drown away any of these considerations? Also, do we have evidence that the implementation even can implement (+ a (+ b c)) as if it was (+ (+ a b) c) ? Seems like it could only do it if it had a fairly intimate understanding of the implementation of +, and again, it's a complete mystery to me how this can possibly measurably affect performance. For these reasons, I feel this conversation is trying to make a decision based on aspects that are difficult to show as being very material. However I do feel there is a very material angle that should be discussed and only a couple folks brought up (and seem to have been fairly unceremoniously shot down). If you look at source code implemented with one class system or the other, which one is clearer in its meaning for a user that is _moderately_ familiar with the ontology at hand? I feel this is the more important aspect here, and I'll share what I have observed when facing a similar choice, because the answer in the end was not what I had expected at first. I have worked a fair bit with systems that deal with geometric entities (points, planes, triangles, vectors, rays, lines, curves, that sort of stuff). In our field there are two schools of thought: the mathematicians (like me) want affine algebra class systems (points, vectors and normals are captured by different classes) and the software engineers want just vector algebras (everything is a vector) and "you can keep it in your head what's what". Inevitably, if you do this long enough, you end up working with both systems, and the reality is that in the overwhelming majority of cases only having vectors is fine. And the reason is that in reality affine algebra systems end up being more pedantic and in your way than it's worth to you. They do keep (certain) bugs away, but they cause so much extra typing and allocations that are very difficult to optimize away reliably, that the final balance is not very good. However what's unbelievably confusing, and a very fertile ground for difficult to find bugs, is mixing covariant vectors with contravariant ones (ie vectors and normals). We've had to fix many bugs of this kind, notwithstanding our efforts in careful and diligent naming conventions for variable and function names, to make sure we had code that looked like it was doing what it was actually doing. Folks with years of experience in the field doing this the whole day got caught in mistakes of this kind. And the real issues with these bugs is that they would be subtle because the source didn't help enough make it clear to the readers what was what. For me one lesson learned from this is: there is a cost to what you keep in your head while you're reading a piece of code, no matter how small. Your job as a designer of an ontology is to make sure that this cost is spent in a way that is most useful to the community working on the codebase. You want to maximize the usefulness of code reviews and from this comes the observation above: folks need to be _moderately_ versant in the ontology at hand to be able spot bugs, not deep experts. And there are several reasons for this: - if you have this, you enlarge the group that can usefully comment on a commit during review - in turn this means these people will participate in reviewing important code, which will help them learn how the system is put together, and in places where it actually matters - this helps them 2 ways: they learn what the system does (and where that happens), and picking up good patterns for writing their new code - re-applying these good patterns makes the whole codebase look more regular, which lowers cognitive overhead when you're reading code - and all the above creates serendipity and a very valuable self-sustaining loop (*) - on top of it, this frees up the deep experts to work on the harder problems: when faced with a challenge, finding a place to go and drawing the path to get there is where you want to spend your money. Once the path is traced, you'll see you have a good quality ontology from the fact that walking this new path is a walk in the park for everyone else. People reading a well thought out solution to a hard problem should go "of course!", not "my brain hurts...". (*) code quality goes up, folks write more relevant code because fewer bugs are introduces, bugs are caught early so the fix is not particularly involved (and the relevant code is still fresh in the author's head), folks feel like they're contributing in meaningful ways, more time is spent on new functionality instead of hammering at old material, which makes the people more satisfied and realized ... you get the idea Joel Spolski is often quoted as saying "make wrong code look wrong" [1]. I do not agree with the specifics of how he proposes to achieve it, and in all fairness he wrote that essay a very long time ago, and for a group that had a very specific set of constraints and problems. However the sentiment comes from the same place of what I was saying: making the barrier of entry real low for folks on a bug-spotting mission so they can do their thing is a _fantastic_ idea. Further to this, as I was saying before: not only do you want wrong code to look wrong, you also want code that does the same thing to look the same. And this is entirely because it makes it easier to read for humans, who are the ones that find the difficult bugs. Leaving it to the compiler to find bugs for you is table stakes, it'll only find the easy stuff anyways. That should be your assumed starting point, not your goal: your goal is attending to the community that does what's hard, so that you make it less hard. HTH, L [1] https://www.joelonsoftware.com/2005/05/11/making-wrong-code-look-wrong -- Luca Fascione