Propose new operators to reserve / include for edition 2018

As an aside: the dereferencing operator can’t be postfix, because then the parser couldn’t tell it apart from infix *.

Agreed with zero look-ahead, but I think with one token look-ahead it would be fine, so long as expr expr remains not an expression (well, any more than it already is). Definitely hypothetical, though, as I'm not going to propose changing it.

Perhaps you could declare your custom operators (Unicode or not) with a crate attribute? #![...]

I don't know if what is declared in an attribute can be exported though...

2 Likes

It's not that easy: is a*-b suffix+infix or infix+prefix operators?

4 Likes

Would that it were that simple.

  • Here is how rustc lexes multi-glyph operators in the source. (The preceding lines handle single-glyph operators.)

  • Here is how rustc addresses the many Unicode confusables (AKA synonyms) for the basic ASCII glyphs. Similar treatment will be required for the many cases where there are alternate Unicode characters for the same mathematical operator.

I don't understand why Unicode arrow characters need to be usable as operators. I don't even want to think about disambiguating the multitude of synonymous (i.e., confusable) Unicode arrow characters.

1 Like

Pretty standard hand-written Lexer.

I'm not convinced we would want to do this. I think a starting point would be to only accept the specific Mathematical Symbols Code Block Entries (no duplicates/aliases) and only add aliasing/duplicate allowance if it was truly deemed necessary/ergonomic/useful (I'm not convinced it would be, but, we should definitely think about it IMHO).

I'm not sure we wanted to either, but, I feel like it could be in the realm of possibility. Not ALL though. There might be domains where some arrow characters as operators could be really useful, but, I don't have specific use cases in mind yet. I'd like to keep an open mind about it though. I prefer not to arbitrarily rule things out in the early design phase on something. I'd like to hear from perhaps domains that aren't so Math Oriented about what their opinion on the matter is. In the end though, you are probably correct and that would be my expectation as well.

EDIT: BTW, when I see long lengths of boiler-plate code like this I immediately imagine just putting stuff in a spreadsheet or CVS file and writing some simple code to generate the boilerplate (fairly simple) - that is assuming you didn't want to use Serde or other Macros to be compatible with the hand-written nature of the current code (assuming it is hand-written and not generated already).

Maybe this is a bad idea but here it goes… What about a ~ binary operator that compares floats, so you would write res = a ~ b;, here the comparison should not be exact (i.e res = a==b;), but taking an “off by x” margin where x may default to the machine epsilon… And while we are at that, let’s allow the user to set the value for x via something like let x_lifetime = std:ops:loose_comparison::set_x(x: float), so the value for x would be the valid in the lifetime of x_lifetime… Pls forgive my grammar as english is not my native lang…

Comparing floating point with a tolerance of machine epsilon is wrong. It is never what you want, and is in fact the same as checking for exact equality.

Machine epsilon is the smallest representable float. This is also the smallest possible difference between any two floats, and any two floats will be at least an order of magnitude more different if they’re greater than one due to denormalization.

The correct way to compare floats for equality (if you absolutely have to) is with a percentage tolerance. If it’s versus a known target value, use the acceptable variance on that value. If it’s between two calculated floats, reconsider if that’s what you actually want, then maybe just use a percentage of the larger.

That said, I don’t think ~ has meaning today, and either it or a compound with it is great for fuzzy equality on types where that makes sense, or even just as a generic overload for DSL use, and I doubt ~ meaning box/ToOwned::to_owned is coming back.

2 Likes

Yeah my bad, really bad choice on the tolerance default value, but i think the use case is common enough to justify adding a new operator. What i meant is make comparison for floats work like you described by default, via the proposed operator or modifying the behavior of == for floating point types, so we can all forget about defining the compare_float(a,b) :).

If the == operator gets modified then a lot of code could break, but also, how could we set a default acceptable variance and make sure that no existing code breaks?

I’m not really sure about the choice of the ~ operator, maybe some other notation would be better, the idea is to communicate to the reader that code using this is not comparing bit to bit.

How about the lexeme ~=, which most people would deduce means “approximately equal”. Presumably that’s what you intend for the semantics of this operator.

What is your intent with regard to NaNs? Are they comparable under this scheme, or to they return a comparison Result of None?

Another “advantage” of using ~= is that at least some programming ligatures (Fira Code / DejaVu Sans Code) render it as ≃ (Asymptotically Equal To, but most people would interpret it the same as ≈ Almost Equal To).

This would be an equivalence relation, so NaN ~= NaN would be true or false, and I think sticking to partial equivalence per IEEE and returning false would be the correct answer (and the simplest, where ~= is defined as | lhs - rhs | < ( max[lhs, rhs] × tolerance )).

(That said, hashing out the implementation details of ~= is off-topic here; this thread is just to suggest reserving the operator.)

Please please please no magic global configuration values, especially for operators.

And that implies that it's a ternary operation (to take the tolerance), at which point I think a method is fine.

4 Likes

@scottmcm I meant sort of an “overridable” (is that even a word?) global.

Edit: What about something like std::ops:loose_comparison::<f32>::set_tolerance(tolerance: f32)?, so the tolerance can vary with the type. I’m with you on the no globals idea, but then how would the set_tolerance feature be implemented?

I understand, but that's what I'm objecting to. It leads to different libraries wanting different values, which leads to drop types to set and unset the global, which leads to it being thread-local, which leads to problems in async-await, which ...

2 Likes

Hmm... a == b +/- 0.05 or a == b * (1 +/- 0.05). Though, if you're actually constructing a range, then comparison isn't really the right operator; you'd need an element-of operator, like x in 1..5 or something.

This discussion is going in a weird direction.

Any kind of reasonable general floating point comparison operator is impossible. You need to know the properties of what you’re comparing, and answers to questions like “could there be cancellation involved?”. Sometimes you need relative tolerances, sometimes you need absolute tolerances, and sometimes there’s no substitute for ULPs.

Floating point math is really hard, and nothing you can do will ever make it easy.

3 Likes

This might be too much, but how about a logical xor operator?

Though rarely a problem, its often semantically difficult to actually represent logical exclusive or in many languages.

I propose that the operator would look like this: >< ie a >< b

see here the issues with current xor

Rust has logical XOR:

a != b
3 Likes

read the link man, come on…

Yes, XOR is just inequality.