Re: [code-review] Please review String::FlexMatch

Fergal Daly <[email protected]> Sun, 7 Sep 2003 23:15:42 +0100
Newsgroups gmane.comp.lang.perl.code-review-ladder
Message-ID <[email protected]>
Long mail, I've moved the summary to the front.

Currently is_deeply's definition of deep equality is

- based purely on data, not behaviour
- intuitive: like the definitions of other kinds of equality it is symmetric, 
reflexive and transitive
- as deep as possible, something shouldn't claim to be deep but change it's 
mind sometimes.
- non-destructive
- strict
- leaves no room for confusion or doubt about what it does

The definition you want

- includes behaviour, sometimes and for some reason "" is more important than 
0+ overloading
- is neither symmetric nor transitive and so is unlike other types of equality
- is not always as deep as it could be
- can possibly alter the data
- allows bugs to create false positives *** big no no ***
- is awkward to describe precisely
- caters for a rather specialised need (in my opinion)
- is more complicated to implement

On Saturday 06 September 2003 12:44, Tony Bowden wrote:
> I still maintain that your concept of 'deep' here is wrong.

Our concepts are different. Your definition includes a special case for 
objects which overload "", mine doesn't and so I'd argue my concept is more 
"pure". My concept of deep equality is that the 2 things are 
"indistiguishable", your's is indistinuguishable up to a point. If I accept 
your concept of deep then I need to accept another one which takes numeric 
overloading into account and possibly other ones which handle both and give 
priroity to one or the other etc.

I guess my stance is because I've created a separate module to do deep 
comparisons which handles these and other special cases in a generalised and 
extensible way. That's why I'd rather see the more widespread one be 
absolutely strict and special case free. I'd also like it to be side-effect 
free which is not guaranteed if it's calling stringify methods.

> > The docs don't say anything about string equality, they say that is_deeply 
> > will walk the structures and that should include the structures of an
> > overloaded object.
> 
> Why? 

Because it can and because it says it will. In an earlier email you quoted 
"descend a structure and apply is()", where did you read this? Here are the 
docs I have

           Similar to is(), except that if $this and $that are hash or array
           references, it does a deep comparison walking each data structure
           to see if they are equivalent.  If the two structures are differ-
           ent, it will display the place where they start differing.

It should also say "this test ignores blessings on references". If we make it 
work with overloading it will have to read "this test ignores blessings on 
references, except when an object inside $this overloads "" or eq and the 
corresponding part of $that is a scalar, in which case instead of descending 
into the object it will use eq to compare it to the scalar". Clear? No.

I have not deliberately obfuscated this (much ;-). Try and make it more clear 
but remember that is_deeply() with overloading _must not_ be symmetric in 
$this and $that.

Why must it be asymmetric? Because

is_deeply("string", $overloaded_object)

should definitely fail even when your

is_deeply($overloaded_object, "string")

would pass.

> 	is $fred, "fred";
> 	is_deeply [$fred], ["fred"];
> 
> Do you really think that test 1 should pass and test 2 should fail?

Even if you said

is $fred, "fred" 
is_deeply $fred, "fred" 

I'd say yes, I think they should pass and fail respectively. is() is 
documented to use eq, is_deeply() is documented to examine the structure of 
it's arguments, it doesn't say that it examines the structures of it's 
arguments _sometimes_. It doesn't say anythig about using eq either. It does 
say it's "similar to is()" but I always took that to mean it gives nice diags 
and it takes the tested value followed by the expected value, followed by a 
name. Maybe I was wrong...

For me, ideally is_deeply() ans is() would be the same, I think it's a little 
confusing that

is($ref, $ref."")

is a pass.

> If so, what about the case where overloaded constants are in operation
> and my $fred = "fred" creates an object ...

Anyone lobbing overloaded constants into their tests and expecting everything 
to work as normal is asking for trouble. You can't have overloaded _and_ 
behaving exactly as normal, otherwise what's the point?

> > What if you had overloaded eq on $ref and were accidentally always 
returning 
> > true? is_deeply($ref, "any string") would be a pass.
> 
> Yes. But that would be the *correct* behaviour.

Absolutely not. I said "accidentally" - as in there is a bug. The current 
is_deeply() would catch this bug. The is_deeply you want would give a false 
positive.

The worst thing you can do in testing is give a false positive. Tests should 
be as strict and predictable as possible.

> If you want to be twiddling with the innards of objects to see if
> they're the same then you should be using something different.

is_deeply's docs explicitly say it twiddles the innards, perhaps we need

is_mostly_deeply() and
is_really_deeply_honestly_it_is_guvnor()

;-)

> > Like I said, I've asked Michael Schwern for his take.
> 
> Like I said, he's given it:

Sorry, I was using internet cafes for the last few days, so with the help of 
several hundred "Re: Your details" emails I missed it.

> 	> is_deeply [ State->columns('Primary') ] => [qw/name/],
> 	>   'State Primary:' . join ", ", State->columns('Primary');
> 
> 	This should work.  The test should not ignore stringification otherwise
> 	you can't do black box testing using is_deeply().

The test ignores numification, why shouldn't it ignore stringification?

In this case, is_deeply is unnecessary, you're just using it as a convenient 
way to test a function in array context, at the risk of going on about it

cmp_deeply([ State->columns('Primary') ], [str("name")])

would do exactly what you want and it makes the stringification explicit.

As for the black box stuff, I don't follow this at all. is_deeply() already 
prevents black-box testing insofar as it requires you to fully predict the 
data structure that will be returned. The closest you can get to black box 
testing using is_deeply is

$black_box = Black::Box::some_function();

is_deeply(
  $thing,
  {
    key => $black_box
  }
);

which works perfectly with the current is_deeply.

Something else Michael said was

> Put another way, the Class::DBI API says that columns() returns a list of
> column names as strings.  There's nothing about them being objects.  When
> writing the test we should not have to be aware that they are really
> objects with stringification overloaded.

which I found a bit strange. If the API says strings and nothing about 
objects, then it should give strings, not objects. The API is a contract and 
if I'm told I will get strings then I have every right to use code that 
explictly _depends_ on them being strings and not objects. There are many 
functions that are polymorphic in their arguments and behave differently when 
given a string or a ref

So I can do

poly_fn("string");

or

poly_fn({Type => "special", Name => "string"});

however if I do

poly_fn(State->columns('Primary'));

I'm in for a surprise and possibly a long debugging session until I realise 
that the API docs are not quite right. The API docs should make it clear that 
you get something which stringifies, that way it's free to give a real string 
or a fake one and it's caveat usor,

F