EDIT: got downvoted, why? It's a serious question. I've never had any use for any equality besides the structural one. Honestly want to see if anyone can come up with a valid case.
EDIT: got downvoted, why? It's a serious question. I've never had any use for any equality besides the structural one. Honestly want to see if anyone can come up with a valid case.
Is the quotient 1/2 equal to 3/6
Is the string "Bébé" equal to "Bebe"?
Is the float 0.555555559 equal to 0.6?
Is the color RGB(1,1,1) equal to Lab(100,0,0)?
Is the equation y=3x+2 equal to y-2=3x?
Is the function x=>2+x equal to y=>2+y?
> Is the quotient 1/2 equal to 3/6?
I think these are good examples that come up in the real world a lot. The representation structures we use are not sufficient for precise specification. So we have to overspecify (making a superfluous choice in representation, -90, 270, 630?).
My opinion still is that data must be normalized with explicit procedure calls. Doing stuff implicitly is neither beneficient to efficiency nor to comprehensibility. Explicit normalization allows to leverage what the compiler _can_ infer.
The other questions I have to return because it's not even clear to me, "Should they be"?
It can certainly benefit comprehensibility. Needing to explicitly normalize before I can apply standard comparisons leaves more space for things to go wrong. "Having a value of this type means that I have a normalized value" means I have fewer things to keep track of. (I do note that there is significant space between "it can benefit comprehensibility" and "it will always benefit comprehensibility" - that depends on what I need to be readily able to pull out of the implementation from a quick read of surface syntax)
Also, some things may have an easy equality check while normalizing may be expensive or impossible - although I sadly can't think of any examples off the top of my head.
Sorry, had to be pedantic. These equations are not equal under usual mathematical and logical definitions of equality applied to equations. They do possibly express the similar relationships between the variables.
But I agree with your general point. "Equals" has different definitions in different contexts.
Could you elaborate? The second sentence in particular surprises me - don't these necessarily express exactly the same relationship between the variables?
They are the same thing. Thusly
y=3x+2
is equal to 10y-20=30x
is equal to 5y-15x=10
etcIt's been a while since uni, but I don't remember any other mathematical definition of equations being considered equal. I guess the thought is something about what is being defined in terms of what (y= vs x=) but I don't think that's a meaningful distinction between two equations.
This is trickier than it might seem. Is this a description of static geometry, or is this a delta that I'm going to want to interpolate/integrate/etc?
In the latter case, -90 is very different than 270, which is different than 630...
"ABC" == "abc"
Depending on the context, case insensitive equality checking may be required. Also precision on floating point value comparisons.An interesting approach to this in C# (and other languages) is to use ad-hoc polymorphism:
public interface Eq<A>
{
bool AreEqual(A x, A y);
}
public struct EqInt : Eq<int>
{
public bool AreEqual(int x, int y) => x == y;
}
public struct EqDbl : Eq<double>
{
public bool AreEqual(double x, double y) => Math.Abs(x - y) < 0.00001;
}
public struct EqStringExact : Eq<string>
{
public bool AreEqual(string x, string y) => x == y;
}
public struct EqStringIgnoreCase : Eq<String>
{
public bool AreEqual(string x, string y) =>
String.Equals(x, y, StringComparison.CurrentCultureIgnoreCase);
}
public static class GenericCode
{
public static A UseFirstIfEqual<EQ, A>(A fst, A snd)
where EQ : struct, Eq<A> =>
default(EQ).AreEqual(fst, snd)
? fst
: snd;
}
GenericCode.UseFirstIfEqual<EqInt, int>(10, 10); // 10
GenericCode.UseFirstIfEqual<EqInt, int>(10, 20); // 20
GenericCode.UseFirstIfEqual<EqDbl, int>(10.0, 10.0); // 10.0
GenericCode.UseFirstIfEqual<EqDbl, int>(10.0, 20.0); // 20.0
GenericCode.UseFirstIfEqual<EqStringExact, string>("ABC", "abc"); // "abc"
GenericCode.UseFirstIfEqual<EqStringIgnoreCase, string>("ABC", "abc"); // "ABC"
It's slightly more novel than the interface approach, and allows for retrospective equality behaviours to be authored for a sealed type, and importantly, for the selection of the most appropriate equality operation for any particular context.I don't have a compiler to hand, so apologies for any errors!
new Rational(2, 3) == new Rational(4, 6)
You could implement Rationals to be structurally equal by simplifying them immediately on construction, but that may be a lot of unnecessary computation in some cases.Also, the UI may need to display the unsimplified fraction. If you simplified them immediately, you'd have to store a factor somewhere to reconstruct the original (and not in the Rational class because that would ruin structural equality).
In other words, "equal" for I/O purposes and "equal" for mathematical purposes could be different.
Point is that you can have a notion of "equal but not identical". No doubt it's also possible to represent rationals with only structural equality, but I'd have to see two competing implementations to judge which is more intuitive to use.
a / b == c / d is equivalent to a * d = c * b, although whether that's a good approach depends on a lot of things.
(def a (/ 1 2)) (def b (/ 8 16)) (= a b) true
I think that it comes down to a question of interfaces/abstraction. Quite a few languages provide you interfaces for asking both questions, "are these structurally equivalent" and "are these practically equivalent" for whatever choice of "practical".
Mathematics has several.
Probably the most useful for CS is the notion from logic of "extensional equality" - two things are equal if I can't tell them apart. But we quickly want to restrict what approaches we can use, or for most languages that would necessarily be reference equality. Structural equality is one such restriction.
For an abstract data type, there's a strong line of reasoning that would say two values should be equal if they aren't distinguishable through the abstraction.
For example, to define rational numbers using only integers you take a rational to be a pair (n, d) but with a new equality such that (1, 2) and (2, 4) are equated.
You asked for a non-structural equality relation, and I gave you a whole class of them. There are many more. There isn't even one kind of 'structural equality': are two things equal if they have the same memory layout? The same AST representation? What if the layout depends on the allocator? What if they are identical but must be treated differently through aliases?
Considering different abstraction levels like AST or memory layout: This is moving up and down the levels of abstraction. It's conventional wisdom that you shouldn't do this in a single context (like a function, module, or even a programming language). But it's a good point - powerful languages like C present all these levels at once. In other words, there is often an impedance mismatch. Structural equality might be not quite the same to the compiler and to the programmer.
Btw. in my practice (writing really dull code, and some algorithms) I've never seen the need for complex equality functions. In practice I test IDs - typically integers or short strings.