challenge: implement the max function

Andrei Alexandrescu (See Website For Email) SeeWebsiteForEmail at erdani.org
Wed Jan 24 23:44:52 PST 2007


Donth Ave wrote:
> Andrei Alexandrescu wrote:
> 
>> Notice that the .max tests are guarded by if (std.is_num!(T1) && 
>> std.is_num(T2)). So that code is only executed for numerical types.
>> I've omitted the implementation of std.is_num because it is 
>> trivial.
> If it is trivial to implement, then I do not understand how. Please
> explain how std.is_num is able to determine whether some class
> represents a number type. The only way I can think of is the
> requirement to have some predicate isNum defined in every class.

I didn't mean to be more Catholic than the Pope. std.is_num determines 
whether a type is a _built-in_ numeric type. If not, my solution will 
not make heroic efforts to detect various conventions that user-defined 
types might have implemented. It will conservatively deduce the type to 
be typeof(true ? T : U).

> But even if std.is_num is implemented: you haven't restricted the
> challenge to types who repesent sets of numbers which have a maximum.
> The easiest set of numbers, the natural numbers, does not have a
> maximum. So how will someone define a maximum on this type?
 >
> Although you might want to say "pure academic" one can implement such
> an infinite type by storing a concrete value on an unbounded sequence
> of BlueRays/DVDs/CDs and work on the values by using three ore more
> burners and readers.

This reminds me of a novel by Dostoevsky: a guy makes a remark at a 
party and another guy goes on and lives his life by it. If my 
requirements did not restrict implementation in certain ways, that 
doesn't mean mind should feverishly work on finding stretches and corner 
cases.

>> In the numeric case, things are more interesting. With max it turns
>> out that today's implicit numeric conversions do exactly as needed.
>> 
> I doubt that. How can two different numerical classes be implicitely
> converted to each other?

float -> double among others?

>> Maximum implies some kind of ordering. To me using > versus <= is 
>> more of an academic point than a practical one. Once you have one, 
>> you can reasonably define all others.
> Please check to know the differences between "monotonic ordering" and
> "strict monotonic ordering" of which you are writing and "total
> ordering" or "partial ordering" which I am writing of. To compute a
> maximum only a "partial ordering" is required.
> 
> A set of arguments that is only partially ordered may not have a
> maximum---and because in the challenge there is no specification for
> this case, it would be okay to raise an exception as the sample
> solution does.
> 
> But if there is a maximum the sample solution will fail to return it.
> 
> 
> To see that look at a type which has three elements LEFT, RIGHT and
> TOP. Where LEFT and RIGHT are not ordered but both are less than TOP.
> 
> 
> Calling "max( LEFT, RIGHT, TOP)" with your implementation will result
> in an exception raised on comparing LEFT with RIGHT, although the
> return value should be TOP.

I understand. So basically you want max to find the top of a lattice. I 
personally find that funny, but let me also attempt an explanation.

In a lattice you could define > to throw an exception or to return 
false. The latter is the convention used by STL functions. So if a and b 
are unordered, both a > b and b > a return false. In that case the 
sample solution will work properly.

>> Lazy is never deduced, and it was not part of the requirement.
> It was not excluded.

You don't understand. I don't need to exclude everything that's not 
possible or highly unlikely, otherwise please send me your meteor tax :o).

>> But the case max(obj,4) could be handled better. In that case, the 
>> built-in should be explicitly converted to the object type.
> 
> ... and it will fail if obj does not have such converter. The same
> holds for a call min( o1, o2), where o1 and o2 are from different
> classes. And to have such converters was not given in the challenge.

So sue me. :oD

> Sorrily you failed to see the main point: if one puts a conversion
> into the template that conversion will destroy the otherwise possible
> lvalueness.

Of course I was aware of that point, and my solution was simple: 
lvalueness disappears as soon as the types are not identical, storage 
class included. I'm not sure how one could be much more aggressive than 
this.

> If one does not put a conversion into the template one
> and in case of mixed types one has to prefix every access to the
> value returned from max with a cast. But according to the challenge
> that is okay, because there is no requirement for not casting.

The current sample solution assumes that implicit conversion among 
different types is possible. This is more restrictive than it could, but 
I didn't put much emphasis on that because D's built-in implicit 
conversions will soon change (to the better). You are of course free to 
provide a better solution.

Speaking of which, sometimes more than one comparison is necessary. 
Consider:

int a = -1;
uint b = 3000000000; // three billion
auto x = max(a, b);

What this code wants is unambiguous: max between an int and a uint is 
always computable and returns a uint. But notice that converting either 
of a or b to the other's type will yield the wrong result: cast(uint)a 
is greater than b, and cast(int)b is smaller than a. The right solution 
for mixed-sign max is (nontemplated):

uint max(int a, uint b)
{
   if (a <= 0) return b;
   auto c = cast(uint) a;
   return c > a ? c : a;
}


Andrei



More information about the Digitalmars-d mailing list