Jump to page: 1 24  
Page
Thread overview
recursive equal, and firstDifference functions
Mar 19, 2013
timotheecour
Mar 19, 2013
simendsjo
Mar 19, 2013
Jonathan M Davis
Mar 19, 2013
monarch_dodra
Mar 19, 2013
John Colvin
Mar 19, 2013
John Colvin
Mar 19, 2013
John Colvin
Mar 19, 2013
bearophile
Mar 19, 2013
monarch_dodra
Mar 19, 2013
John Colvin
Mar 19, 2013
John Colvin
Mar 19, 2013
timotheecour
Mar 19, 2013
Jonathan M Davis
Mar 19, 2013
timotheecour
Mar 19, 2013
Jonathan M Davis
Mar 19, 2013
timotheecour
Mar 19, 2013
Jonathan M Davis
Mar 19, 2013
timotheecour
Mar 19, 2013
Jonathan M Davis
Mar 19, 2013
H. S. Teoh
Mar 19, 2013
Dan
Mar 19, 2013
timotheecour
Mar 19, 2013
Jonathan M Davis
Mar 19, 2013
Dan
Mar 19, 2013
Jonathan M Davis
Mar 19, 2013
Dan
Mar 19, 2013
Jonathan M Davis
Mar 20, 2013
Dan
Mar 20, 2013
Jonathan M Davis
Mar 20, 2013
H. S. Teoh
Mar 20, 2013
Jonathan M Davis
Mar 20, 2013
Dan
Mar 20, 2013
Jonathan M Davis
Mar 20, 2013
Dan
Mar 20, 2013
Jonathan M Davis
Mar 20, 2013
Dan
Mar 20, 2013
Jonathan M Davis
Mar 19, 2013
John Colvin
Mar 19, 2013
Jonathan M Davis
March 19, 2013
we need a std.algorithm.equalRecurse(T1,T2)(T1 a, T2 b) that compares recursively a and b;

its behavior should be:

if opEqual is defined, call it
else, if its a range, call std.algorithm.equal (ie compare nb elements, then each element for equality)
else, if it's a class/struct, make sure types are same and call it recursively on each field.
else if it's a numerical type, call "=="
else (is there an else?)

just as std.algorithm.equal, we should have equalRecurse([1],[1.0]);

Currently, "==" works in a non-intuitive way, breaking principle of least surprise:
----
struct A{	int[]b=[1]; }
void main(){
	auto a1=A();
	auto a2=A();
	assert(a1!=a2);
}
----
we should have:
assert(equalRecurse(a1,a2));

What are your thoughts on those:

A)
Can we just extend std.algorithm.equal (which only works on ranges) to arbitrary types instead of introducing a new function equalRecurse ?

B)
Should "==" / opEqual behave like that (but it might be too late to change)?

C)
in the example above, why assert(a1!=a2); but assert(A.init==A.init); ?

D)
Additionally, we would need a sister function firstDifference(T1, T2)(T1 a, T2 b) that finds the first difference among a and b. How to represent that in a generic way is not clear (should for example, find a particular field being different, etc). How about a string output:
assert(firstDifference([1,2,3],[1,2,3]) == null);
assert(firstDifference([1,2,3],[1,3]) == "a.length");
assert(firstDifference([1,2,3],[1,4,3]) == "a[1]");
A a1; A a2; a1.x=1; a2.x=2;
assert(firstDifference(a1,a2) == "a.x");
assert(firstDifference(a1,void) == "typeid(a)");

Both would be very useful for debugging.

March 19, 2013
On Tuesday, 19 March 2013 at 08:25:45 UTC, timotheecour wrote:
> we need a std.algorithm.equalRecurse(T1,T2)(T1 a, T2 b) that compares recursively a and b;
>
> its behavior should be:
>
> if opEqual is defined, call it
> else, if its a range, call std.algorithm.equal (ie compare nb elements, then each element for equality)
> else, if it's a class/struct, make sure types are same and call it recursively on each field.
> else if it's a numerical type, call "=="
> else (is there an else?)

Not recursively, but how about something like this?
1) One type, one other
   -> false
2) Both types
   -> is(A == B)
3) Both symbols && __traits(isSame)
   -> true
4) A symbol && __traits(compiles, (A.opEquals(B)))
   -> A.opEquals(B)
5) B symbol && __traits(compiles, (B.opEquals(A)))
   -> B.opEquals(A)
6) A symbol && __traits(compiles, (A.equals(B)))
   -> A.equals(B)
7) B symbol && __traits(compiles, (B.equals(A)))
   -> B.equals(A)
8) Both symbols or values && __traits(compiles, A == B)
   -> A == B
9) assert(0)
March 19, 2013
On Tuesday, March 19, 2013 09:25:43 timotheecour wrote:
> we need a std.algorithm.equalRecurse(T1,T2)(T1 a, T2 b) that
> compares recursively a and b;
> 
> its behavior should be:
> 
> if opEqual is defined, call it
> else, if its a range, call std.algorithm.equal (ie compare nb
> elements, then each element for equality)
> else, if it's a class/struct, make sure types are same and call
> it recursively on each field.
> else if it's a numerical type, call "=="
> else (is there an else?)
> 
> just as std.algorithm.equal, we should have
> equalRecurse([1],[1.0]);

If you want recursive equal, then do equal!equal. Granted, that's only one level of recursion, but how many levels deep are you really going to have your ranges? And you have to get to == eventually anyway in order to compare the deepest elements. Going beyond a range of ranges is likely to be quite rare, and when it does happen, you can simply nest equal as many times as you need.

- Jonathan M Davis
March 19, 2013
On Tuesday, 19 March 2013 at 10:08:43 UTC, Jonathan M Davis wrote:
> On Tuesday, March 19, 2013 09:25:43 timotheecour wrote:
>> we need a std.algorithm.equalRecurse(T1,T2)(T1 a, T2 b) that
>> compares recursively a and b;
>> 
>> its behavior should be:
>> 
>> if opEqual is defined, call it
>> else, if its a range, call std.algorithm.equal (ie compare nb
>> elements, then each element for equality)
>> else, if it's a class/struct, make sure types are same and call
>> it recursively on each field.
>> else if it's a numerical type, call "=="
>> else (is there an else?)
>> 
>> just as std.algorithm.equal, we should have
>> equalRecurse([1],[1.0]);
>
> If you want recursive equal, then do equal!equal. Granted, that's only one
> level of recursion, but how many levels deep are you really going to have your
> ranges? And you have to get to == eventually anyway in order to compare the
> deepest elements. Going beyond a range of ranges is likely to be quite rare,
> and when it does happen, you can simply nest equal as many times as you need.
>
> - Jonathan M Davis

"equal!equal(RoR1, RoR2)"

That looks cute, but I think it says something about how powerful and expressive D can be, while being compile-time optimized. It's those little things that still amaze me about D.
March 19, 2013
Jonathan M Davis:

> Going beyond a range of ranges is likely to be quite rare,

I agree.


> and when it does happen, you can simply nest equal as many times as you need.

A similar function seems useful for Phobos because it's automatic, you don't need to tell it how many levels there are.

Bye,
bearophile
March 19, 2013
On Tuesday, 19 March 2013 at 11:46:14 UTC, monarch_dodra wrote:
> On Tuesday, 19 March 2013 at 10:08:43 UTC, Jonathan M Davis wrote:
>> On Tuesday, March 19, 2013 09:25:43 timotheecour wrote:
>>> we need a std.algorithm.equalRecurse(T1,T2)(T1 a, T2 b) that
>>> compares recursively a and b;
>>> 
>>> its behavior should be:
>>> 
>>> if opEqual is defined, call it
>>> else, if its a range, call std.algorithm.equal (ie compare nb
>>> elements, then each element for equality)
>>> else, if it's a class/struct, make sure types are same and call
>>> it recursively on each field.
>>> else if it's a numerical type, call "=="
>>> else (is there an else?)
>>> 
>>> just as std.algorithm.equal, we should have
>>> equalRecurse([1],[1.0]);
>>
>> If you want recursive equal, then do equal!equal. Granted, that's only one
>> level of recursion, but how many levels deep are you really going to have your
>> ranges? And you have to get to == eventually anyway in order to compare the
>> deepest elements. Going beyond a range of ranges is likely to be quite rare,
>> and when it does happen, you can simply nest equal as many times as you need.
>>
>> - Jonathan M Davis
>
> "equal!equal(RoR1, RoR2)"
>
> That looks cute, but I think it says something about how powerful and expressive D can be, while being compile-time optimized. It's those little things that still amaze me about D.

and then:

template NumDimensions (T) {
	static if(is(ElementType!T == void))
		const NumDimensions = 0;
	else
		const NumDimensions = 1 + NumDimensions!(ElementType!T);
}

bool rec_equal(R0, R1)(R0 r0, R1 r1)
	if(NumDimensions!R0 == NumDimensions!R1)
{
	mixin("return " ~ replicate("equal!", NumDimensions!(R0)-1) ~ "equal(r0, r1);");
}

obviously it requires some more checks, but it works nicely (except if you feed it two integer literals, in which case the compiler throws an out of memory error!).
March 19, 2013
On Tuesday, 19 March 2013 at 12:16:54 UTC, John Colvin wrote:
> On Tuesday, 19 March 2013 at 11:46:14 UTC, monarch_dodra wrote:
>> On Tuesday, 19 March 2013 at 10:08:43 UTC, Jonathan M Davis wrote:
>>> On Tuesday, March 19, 2013 09:25:43 timotheecour wrote:
>>>> we need a std.algorithm.equalRecurse(T1,T2)(T1 a, T2 b) that
>>>> compares recursively a and b;
>>>> 
>>>> its behavior should be:
>>>> 
>>>> if opEqual is defined, call it
>>>> else, if its a range, call std.algorithm.equal (ie compare nb
>>>> elements, then each element for equality)
>>>> else, if it's a class/struct, make sure types are same and call
>>>> it recursively on each field.
>>>> else if it's a numerical type, call "=="
>>>> else (is there an else?)
>>>> 
>>>> just as std.algorithm.equal, we should have
>>>> equalRecurse([1],[1.0]);
>>>
>>> If you want recursive equal, then do equal!equal. Granted, that's only one
>>> level of recursion, but how many levels deep are you really going to have your
>>> ranges? And you have to get to == eventually anyway in order to compare the
>>> deepest elements. Going beyond a range of ranges is likely to be quite rare,
>>> and when it does happen, you can simply nest equal as many times as you need.
>>>
>>> - Jonathan M Davis
>>
>> "equal!equal(RoR1, RoR2)"
>>
>> That looks cute, but I think it says something about how powerful and expressive D can be, while being compile-time optimized. It's those little things that still amaze me about D.
>
> and then:
>
> template NumDimensions (T) {
> 	static if(is(ElementType!T == void))
> 		const NumDimensions = 0;
> 	else
> 		const NumDimensions = 1 + NumDimensions!(ElementType!T);
> }
>
> bool rec_equal(R0, R1)(R0 r0, R1 r1)
> 	if(NumDimensions!R0 == NumDimensions!R1)
> {
> 	mixin("return " ~ replicate("equal!", NumDimensions!(R0)-1) ~ "equal(r0, r1);");
> }
>
> obviously it requires some more checks, but it works nicely (except if you feed it two integer literals, in which case the compiler throws an out of memory error!).

correction:

bool rec_equal(R0, R1)(R0 r0, R1 r1)
	if(NumDimensions!R0 == NumDimensions!R1)
{
	static if(NumDimensions!R0 == 0)
		return r0 == r1;
	else
		mixin("return " ~ replicate("equal!", NumDimensions!(R0)-1) ~ "equal(r0, r1);");
}
March 19, 2013
On Tuesday, 19 March 2013 at 12:16:54 UTC, John Colvin wrote:
> (except if you feed it two integer literals, in which case the compiler throws an out of memory error!).

Which is a legitimate error, seeing as -1 as a size_t is size_t.max which meant replicate was requesting size_t.max * int.sizeof bytes. See my correction to the code in my other reply.
March 19, 2013
On Tuesday, 19 March 2013 at 12:11:50 UTC, bearophile wrote:
> Jonathan M Davis:
>
>> Going beyond a range of ranges is likely to be quite rare,
>
> I agree.
>
>
>> and when it does happen, you can simply nest equal as many times as you need.
>
> A similar function seems useful for Phobos because it's automatic, you don't need to tell it how many levels there are.
>
> Bye,
> bearophile

But the compiler can't tell how many levels deep you want to go. There are legitimate cases of "Range of Stuff" where Stuff just so happens to be a range, but you don't want range comparison.

For example:
class MyClassRange;

MyClassRange a, b;

SuperEqual(a, b); //?

I think using equal!equal is not that much more verbose, but safer/explicit.
March 19, 2013
On Tuesday, 19 March 2013 at 12:34:04 UTC, monarch_dodra wrote:
> On Tuesday, 19 March 2013 at 12:11:50 UTC, bearophile wrote:
>> Jonathan M Davis:
>>
>>> Going beyond a range of ranges is likely to be quite rare,
>>
>> I agree.
>>
>>
>>> and when it does happen, you can simply nest equal as many times as you need.
>>
>> A similar function seems useful for Phobos because it's automatic, you don't need to tell it how many levels there are.
>>
>> Bye,
>> bearophile
>
> But the compiler can't tell how many levels deep you want to go. There are legitimate cases of "Range of Stuff" where Stuff just so happens to be a range, but you don't want range comparison.
>
> For example:
> class MyClassRange;
>
> MyClassRange a, b;
>
> SuperEqual(a, b); //?
>
> I think using equal!equal is not that much more verbose, but safer/explicit.

I think if your calling a recursive version of equal, then you assume it will go as deep as possible. If you want control as to how deep, then you can either use equal manually, or there could be a maximum depth parameter to the recursive equal.
« First   ‹ Prev
1 2 3 4