|laziness, impatience, and hubris|
Reading this thread (years after it was started), I reflected on the differences in approaches advocated in the replies. OO, with a derived class used to specialize on the type involved, is certainly the way to go if the objects will be persistent, or if a number of functions are created that work together. I quickly change my design from a callback function to a callback object instead of N callback functions.
It is also very familiar to people from more ordinary experience, and in many languages it is really the only choice.
You can also use a class with a virtual function to substitute for a closure if that's what you really wanted. In languages where I wish for the latter, I see that it is indeed a substitution.
In the case discussed about specializing the algorithm (or object) to compare different kinds of objects in a type-suitable manner, I think of overloading. Virtual functions give you that, but actual overloading of the desired function would be easier. In particular, the max function should know what to do for any kind of argument. In many languages, that's fine, and overloading takes care of it. In Perl, it is difficult specifically for strings, because strings might really be numbers. But avoid the issue and look at comparing Dogs. Clearly the max function can know what to do with Dogs, if Dogs are set up in a standard manner to be ordered.
In Perl, > is for numbers and gt is for strings. In Perl 6, after is for "canonical ordering" native to the type. So, if we were writing the max function, using after for the comparison will work natively on whichever type was passed.
In some languages, you would define a template function in such a way that it took two arguments of the same class. Or in this case we want one or more arguments, of the same type. In Perl 6 you can do that:
But, we still run into the issue that we don't always want to use canonical comparisons. In the original example, we were not interested in string comparisons, but in comparing the lengths of the strings. So, next take a page from the C++ STL book. Many algorithms take a comparison operator that defaults to the standard ordering. But you can pass in something else!
Write the max function to take such an extra argument, and still take any number of value arguments, by making this one named.
Now you can pass in the function to use. And a small function is fine. It doesn't need to close over any local variables.
And that makes me hope that the built-in max function has such a feature too. In any case, I used a single function to customize the logic. Why a function instead of a whole object? Well, the object itself also customizes the normal comparison operators. This immediate custom thing only needed a single simple function, so why go overboard?
Now you can also bind parameters to functions to get another function, in a simple way. So you might not have to wrap it in another trivial function. So if I was passing my max function as another argument, but really wanted the customized comparsion, just use &max.assuming(:compare $comp) for that function. The point here is that state information doesn't require "objects", but might be done using closures, or even a tamed specialized form of closure, which is the ability to create new functions on the fly at run time.
In a wood working or repair project, I may pick out a very sharp chisel, a hand scraper, some sandpaper, a rasp, and play with each one a bit on the work at hand to decide on the final approach. I may end up going to get the dremmel tool instead. Each tool has overlapping capabilities, and often the one at hand is "better" than going to get a different one that might have been the better choice. I spoke with someone yesterday who was scared of TIMTOWDI. He didn't call it that, but explained that having more than one way to do simple things scared him away from Perl. But it's not a Frankenlanguage (well...), it's just like these wood tools: nobody thinks it odd that there are different ways to do it. There are just different tools and techniques.
For a library design, I would hopefully study the issue and come up with the optimum blend of overloading, derived classes, parameters, and callbacks. For a one-off, I'll just use whatever I grab first.