Re: [code-review] Please review String::FlexMatch
Fergal Daly <[email protected]> Mon, 8 Sep 2003 17:12:37 +0100
| Newsgroups | gmane.comp.lang.perl.code-review-ladder |
|---|---|
| Message-ID | <[email protected]> |
On Monday 08 September 2003 10:50, Tony Bowden wrote:
> On Sun, Sep 07, 2003 at 11:15:42PM +0100, Fergal Daly wrote:
> > 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
>
> Nope. Consider tying.
I have, although I try to consider it as little as possible. It's an ugly hack
that's only needed because Perl's builtin types are not objects.
The important difference is that a tied string is indistinguishable from a
non-tied string whereas an overloaded reference is very easily
distinguishable, it responds to a whole load of things that a string won't
respond to like ->[] or ->{} or ref($thing).
There is no way for any test to spot tied values and so there's no way to do
data only tests with tied values without the risk of strange breakage, this
means that data only tests on tied values are of little value, you should be
explicitly testing the data and behaviour of the underlying object.
This all makes tied values outside the scope of data tests. There's no need to
add a further class of objects to the list of not-reliably testable things.
> For values of intuitive that mean intuitive to you but not to me...
Absolutely but I did apply the only criteria that I could think of, that is
does it behave like all the other equalities I can think of: reflexive - a=a;
transitive - a=b and b=c means a=c; symmetric - a=b means b=a. I think this
is part of most people's idea of equality.
> > - as deep as possible, something shouldn't claim to be deep but change
it's
> > mind sometimes.
>
> Again, I think you're using a different concept of deep from me.
Yes, my point is that my concept is as deep as possible. Do you agree that my
deep is deeper than yours? If you just take deep to mean the depth you have
descended in the structure then it certainly is and I can't think of any
other useful definition. So given that one is a deeper than the other, which
should be called deep?
> > - non-destructive
>
> I don't know what you mean by destructive, but again I expect that tying
> gives the lie to this.
Destructive as in it doesn't alter the data it's testing.
> > The definition you want
> > - includes behaviour,
>
> As does the current version.
No it doesn't, currently it looks only at data and never calls any methods.
> > sometimes and for some reason "" is more important than
> > 0+ overloading
>
> I've never suggested this. I've only suggested that when overloading is
> in place that is and is_deeply should do the same thing, ideally by
> using the same code.
This is the fundamental difference, I don't think they should do the same
thing, is() is explicitly documented to use eq, is_deeply is explicitly
documented to walk _each_ structure it finds.
> > - allows bugs to create false positives *** big no no ***
>
> It is always possible to have a false positive when you have bugs.
No, currently is_deeply cannot give a false positive no matter what bugs you
have (excluding tieing again). If is_deeply says the structures are the same
then they are the same. That's not to say that the function that returned the
structre has no bugs but it does definitely say that it worked this time and
that's the most you can hope for.
If you allow overloading is_deeply will tell you either "it looks exactly like
the structure you told me to check for, guaranteed, no doubt" _or_ "it
slipped me a fiver and told me not to look in the bag and that's good enough
for me".
> > - is awkward to describe precisely
>
> I don't believe it is.
Please describe it accurately.
> > - caters for a rather specialised need (in my opinion)
>
> I'd argue this was the other way around.
How many people use string overloading deep inside their objects? How many
people have is_deeply tests where they include a string but would be happy
enough if it was string or an overloaded object? Testing should be explicit
and you should be explicitly testing one of A: a string; Boverloaded object
striginifying to something; or C anything which stringifies to the correct
value and you should only be testing for C if you know the object will only
ever be used in string context and for returns values that means you have
documented that it must only be used in string context. If you are not just
one or the other. As I pointed out, there are many functions that accept
polymorphic agruments and can behave totally differently with strings vs
refs. So it is not at all safe to be passing overloaded ref and telling
people that they really are strings.
> > - is more complicated to implement
>
> This is irrelevant, and I believe wrong, as the behaviour already
> existed and has been changed.
The behaviour existed but was causing another bug, I fixed that bug and to my
mind I accidentally fixed another.
> > 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".
>
> No - my concept doesn't involve overloading per se. It involves making
> (keeping?) is_deeply() be a straightforward recursive application of is().
But it has never been a recursive application of is() and it can never be
because is() produces a test result every time it is called and is() is not
eben implemented in Test::More. By involving stringwise equality you _are_
involving overloading.
> > My concept of deep equality is that the 2 things are
> > "indistiguishable", your's is indistinuguishable up to a point.
>
> What does "indistinguishable" even mean? This is Perl after all...
Indistingushable means I can call f($this) or f($that) and as long as f
doesn't care about the class of it's argument (because Michael has said class
isn't included) I'll get the same answer. There is no meaningful difference
between them.
> > 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.
>
> If is() doesn't already DTRT thing here, then it should. If it does,
> then it should just be used.
is() does the right thing, it does what it's docs say it will do, it uses eq.
My point is is_deeply() doesn't say what it will use, so why should it use
the same as is()?
> > 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.
>
> So using it on anything that's tied should be a fatal error?
There is no way to spot a tied value so it won't give an error. However if
there was, I'd probably say yes. Passing tied objects into data tests is just
dangerous. As I said before you have no way to know how many times a piece of
data will be accessed during an is_deeply test and you should not need to
know this either.
> I'm not asking for string equality. I'm asking that is(X, Y) give the
> same result as is([X], [Y]). To do otherwise *I* would say is non-intuiutve.
I think is() was probably the wrong name, given that it is simply a wrapper
aroung Test::Builder->is_eq it should have been called is_eq but Michael put
convenience before absolute accuracy which is fine because he documented it.
> > 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.
>
> I guess my drugs aren't working well then. Because to me that states the
> only "deepness" that it cares about is the position in a list or hash.
> Seeing if they are "equivalent" is a matter of ambiguity, but there is
> no reason for equivalency here to be in any way different from the
> equivalency assured by is().
Yes, there is plenty of room for maneuver in the word "equivalent". But there
is none in "walking each data structure". An overloaded reference has
structure and given that we're ignoring blessings on all other structures, it
doesn't make sense sudeenly stop doing that because it's a very specific type
of blessing.
> If you think this needs documented it should be documented at is(), not
> is_deeply() as that's where the "problem" if there is one (and I don't
> think there is) appears.
It _is_ documented in is(), it says it uses eq.
> > is_deeply("string", $overloaded_object)
> > should definitely fail even when your
> > is_deeply($overloaded_object, "string")
> > would pass.
>
> Absolutely not.
The second argument is the expected value, if the expected value is an
overloaded object then nothing less than an overloaded object should be
acceptable as the tested value, otherwise the test is pointless.
The tested value should have no say whatsoever in how the test is carried out,
just like students don't get to write "yes I know the answer, you can take my
word for it" on an exam paper.
> is_deeply (SCALAR, SCALAR) should be able to completely delegate to
> is(SCALAR, SCALAR). The docs for is_deeply say NOTHING AT ALL about
> testing anything contained within a scalar - only lists and hashes.
Firstly the docs are wrong there because it does descend into scalar refs and
I think that's implied by "deeply", "lists and hashes" was not supposed to be
an exhaustive list of the things it will descend into.
Secondly I'm not sure what you mean about descending into scalars vs lists and
hashes. If the scalar is a simple scalar it's not possible to descend into it
but an overloaded object is a million miles from being a simple scalar, in
fact it is almost always a ref to an array or a hash and therefore descent is
appropriate.
> > 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_.
>
> No - it's documented to walk hashes and lists comparing what's at the
> equivalent position in each.
What was $fred in that example? It was an object and therefore it was a ref to
a hash or an array. Yes it could have been a blessed globref, scalarref or
regexpref, the same arguments apply.
> > For me, ideally is_deeply() ans is() would be the same,
>
> I agree. Your proposals make this not true. (See 15 lines ago!)
I'm afraid you don't agree because I phrased that badly. What I should have
said is... Ideally is() would have been a very strict deep comparison to
determine if 2 things are exactly the same. What we currently call is() would
be called is_eq() or is_stringly(), there would have been no is_deeply().
> > 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?
>
> I'm not talking about doing it explicitly in the test. I'm talking about
> testing in the presence of a module that does this.
I'm not familiar with it at all but if it's in happening in the test script
then it must have been requested by the test script. If it's happening
without the test script asking for it then someone has written a rather
naughty module that will break a lot more than just this script.
> The computer cannot tell whether or not I did it "accidentally". I may
> have done it deliberately. If you can make the tests pass if I did it
> deliberately and not if I did it accidentally, then I'll happily accept
> the behaviour.
The purpose of the test is not to determine whether it is deliberate, it's to
determine whether it's happening or not. When the current is_deeply() say
"OK" or "NOT OK" you know it's true. When your is_deeply() says "OK" it could
be right or you could have a bug somewhere else. The only way to be sure is
to also test every single reference in the data to make sure that it's not
got a dodgy eq method.
> > > 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
>
> Where? My docs don't mention objects at all. It only talks about
> descending lists and hashes.
What are objects except blessed arrays and hashes? In this thread
http://www.mail-archive.com/[email protected]/msg01679.html
Michael explains that is_deeply() ignores blessings to make it easy to test
objects, so treating objects as just arrays and hashes is definitely in the
plan. He also said that having it be smart about how it tests certain things
depending on the expected value is undesirable, so I was a little surprised
when he changed his mind,
F