[...]
Initially, I had created a delegate that looked like this:
delegate V Func<T, U, V>(T lhs, U rhs);
I would write method like int BinarySearch(IList<T> list, Func<T, T,
int> comparison) and give it to my coworkers.
Well, this is the same as Comparison<T>. However, the specific name is
a *lot* clearer. It doesn't matter what I called that variable, it
still confused my coworkers.
You need better co-workers. Seriously.
You named the method "BinarySearch" and you've got a parameter called
"comparison". I don't care what the types are, anyone who knows anything
about basic algorithms should recognize that pattern and understand that
the parameter "comparison" is going to perform the comparison used in the
binary search.
Now, why your binary search method doesn't take a value to search for, and
why you aren't just using a List<T> and calling its BinarySearch, I'm not
sure. But regardless, I don't see anything confusing at all about the
syntax you started out with, and I definitely don't see why changing the
name of the type should make a dramatic improvement in people being able
to understand the method.
They had no where to go to except to me.
That is BAD. I could be hit by a bus at any minute! When I replaced it
with Comparison<T>, the questions stopped coming. They had the
Conversion<T> delegate to look at for documentation.
Why don't they already know what a binary search is? Why aren't they
already familiar with the well-established convention of writing
comparisons that return -1, 0, and 1? First-year undergrad students, and
even high-school students, run into this the first time they have to use
strcmp() or similar functions. Are we really turning out computer
programmers these days that have so little basic background that being
presented with something like this just blocks them until they can come
talk to you to find out what's going on?
I think that's a pretty sad state of affairs, if true.
I will also point out that while MSDN documents a Comparison<T> delegate
as doing a particular thing, it doesn't enforce it. The method really
still is just a Func<T, T, int>. I'm not saying that makes Comparison<T>
a bad choice, but I do question its value in terms of self-documenting
code. If your co-workers aren't with-it enough to recognize a binary
search with comparison delegate when they see one, I'm not convinced you
can rely on them to know that it's up to them to ensure that their
Comparison<T> method does what MSDN says it's supposed to.
Now, all that said...
Please note that I am not saying there's no room for more-qualified type
names. This whole discussion started with an example of some very
general-looking delegate names, and suggestions for using generic delegate
types that would provide an equivalent pattern without requiring the
declaration of a new delegate type, especially one that doesn't follow the
normal .NET conventions anyway.
It seems that we do differ with respect to at what point we'd consider
declaring a new delegate type, but I am not fundamentally against
more-qualified type names such as Comparison<T>. That one makes a lot of
sense, as it's used over and over again in a wide variety of contexts. It
has a definite, well-defined pattern that is repeatedly used in .NET, and
it's a great candidate for being its own delegate type, rather than
relying on the more general-purpose Func<T, T, int> type.
Personally, I think its value is as much in the fact that it incorporates
three different attributes in a single type parameter as anything else
(reduces the changes of accidently putting the wrong type as one of the
type parameters). But if you find it more useful to have that specific
name rather than "Func" as well, this is a situation in which declaring a
special-purpose delegate type can be leveraged over a fairly wide variety
of code.
Likewise, if you've got a situation where you expect to be using the same
exact delegate type over and over, and that delegate type can be named in
a way that is very specific to its use rather than just redescribing the
signature of the method, a custom delegate type, generic or otherwise,
could well be warranted.
But my opinion is that for one-off uses, or where you're declaring a
delegate type that is only specific to the type and number of method
parameters anyway, the predefined Action<> and Func<> delegate types are
perfectly fine and just as useful as some custom-defined delegate type.
They have the advantage that they are (or should be) well-known and easily
recognized by someone writing .NET code. Using them also avoids
cluttering your namespace with a variety of well-intentioned but redundant
delegate names.
[...]
Here creating a better name would have prevented the error altogether.
Variable names were misleading because they weren't changed with their
types. In the beginning all we had was Func<T, T, bool> and look how
we managed to turn it into a Comparison<T> and a
EqualityComparison<T>. Is that convincing enough? It is for me!
I'm not sure what you mean here. You said he started with his own
custom-defined "Predicate<T, T>". At what point did you have a "Func<T,
T, bool>" and in what way was that making the issue confusing?
I also don't see how changing from a method signature that simply returns
a boolean according to whether the lhs is less than the rhs, to one that
returns a value that is less than 0, 0, or greater than 0 improves the
readability of the code. Presuambly if the boolean return value was
sufficient before, the code was designed such that a simple if() using the
return value worked fine. With a Comparison<T>, now you have to compare
the return value to 0. By your own statement, your coworkers are
apparently not accustomed to the idea of a comparison result being
comparable to 0, so it seems like this would just make things more
confusing.
I also am a bit suspicious of reusing the "Comparison" name in the
"EqualityComparison", given that _that_ delegate is in fact going to
return a boolean rather than the integer that is part of the convention
for Comparison<T>. This is just the sort of naming overlap that can lead
to confusion.
If you're already taking an essentially boolean operation and using the
Comparison<T> delegate type anyway, why not just always use
Comparison<T>? When you need the "less than" behavior, you compare the
result as "< 0", and when you need the "equal to" behavior, you compare
the result as "== 0". Furthermore, if you follow that convention, then
you can write true comparison methods, and use the exact same method
whether you're doing the "less than" or the "equal to" check.
In other words, while I admire the goal to make the code less confusing,
it doesn't really seem to me that you've done so with the addition of a
new delegate type, EqualityComparison<T>. Instead, you'd added a new
scenario for potential confusion while at the same time forced yourself to
write essentially the same logic twice (once for a Comparison<T> method
and once for an EqualityComparison said:
The same guy told me that while he was developing, he changed the name
from Predicate to BinaryPredicate and back again. He had an overloaded
predicate that took two different types. He had a lot of Predicates! I
think more confusing than anything is seeing Predicate<T, T> being
used for less than in 20 methods and equal to in the other 20.
I guess that's an eye-of-the-beholder thing then. I think that
"Predicate" makes it very clear that you're going to take two operands and
return a true/false value. That's what a predicate does.
The type doesn't tell you what the operation is actually going to be, but
even by the description you provided it seems clear that the variable
names did make it clear.
Of course, given the overall problem description, I would (as I mentioned
above) likely go with a straight comparison method, using Comparison<T>
everywhere. But inasmuch as you might have a method that returns a
boolean value based on the inputs, the "predicate" nomenclature is
well-understood and consistent.
I wouldn't know whether to trust the delegate name or the parameter
name.
Huh? You can always trust the delegate name, as it's a type and so must
tell you exactly what the variable _is_. If the type is wrong, the code
isn't going to work.
The parameter name is potentially unreliable, but that's a development
practices issue if the variable doesn't give you a clear indication as to
what it's being used for. You _should_ be able to trust the parameter
name, and even if you have a type that always matches the variable usage
precisely, it's a serious problem to have a parameter name that does not
do so.
Furthermore, since the delegates are the same, you could pass an
"equal to" delegate to a "less than" delegate and everything will
compile. Using specific names forces a compile time check. You
actually have to say
LessThanPredicate lessThanPredicate = new
LessThanPredicate(equalPredicate);
or do a cast to get it to work. That is nice! Again, this saves people
like myself time . . . lots of time.
Actually, a cast won't work. You have to instantiate a new delegate. But
regardless, this is a potential boon or a potential hindrance, depending
on the situation. It's not a general purpose argument against using the
more general-purpose delegates or for always naming a delegate type as
specifically as you can, because only in some cases is it detrimental to
be able to assign predicate variables to each other.
There is also the problem that as long as the signature of a method
matches, you can create any delegate that has the same signature. So,
while what you're talking about above does prevent assignment of one
delegate type to another, it doesn't do anything to stop you from using a
method written for a LessThanPredicate where you expected an
EqualPredicate:
delegate bool LessThanPredicate<T>(T t1, T t2);
delegate bool EqualPredictate<T>(T t1, T t2);
bool FEqualMethod<T>(T t1, T t2)
{
return t1.Equals(t2);
}
void SomeOtherMethod()
{
LessThanPredicate<int> ltp = FEqualMethod<int>; // OOPS!
}
In other words, the point of confusion hasn't been eliminated. It's just
been shifted to a different location in the code.
[...]
If I use a delegate Func<Person,int> then any .NET 3.5 developer worth
their salt will already know what I'm talking about.
They know what a Func<Person, int> looks like, but they don't
necessarily know what it does.
IMHO, I think we've already established that Converter<Person, int> is a
poor choice for the scenario in which this came up. If you were really
mapping a person to an integer in a way in which that integer represented
the original person, it'd be the right delegate to use. But in the given
example, you're simply writing a function that takes as input a person,
and returns some arbtirary integer that is not tied to that specific
person at all.
So, at least in that example, the Func<Person, int> delegate is actually
much less misleading than the Converter<Person, int>. Someone familiar
with .NET reading code that uses Converter<Person, int> is going to assume
that the delegate method is returning an integer that is simply an
alternative representation of that person. Because that's the sort of
thing that Converter said:
Do they look to see what it is or do
they assume. Most developers I know just assume. Better chance of
mistakes that way.
Developers shouldn't assume. But they do, you're right. Which is why
Func<Person, int> is a superior choice to Converter<Person, int> in this
case.
Pete