challenge: implement the max function

Andrei Alexandrescu (See Website for Email) SeeWebsiteForEmail at erdani.org
Tue Jan 23 00:49:30 PST 2007


Andrei Alexandrescu (See Website For Email) wrote:
> Here's a simple challenge: implement the max function. Requirements:
> 
> a) generic
> b) efficient
> c) preserve lvalueness when possible such that one can write e.g.
> 
> max(arr[0], arr[1]) *= 0.9;
> 
> d) should accept two or more arguments
> e) should return the "smartest" type, e.g. max of an unsigned int and 
> unsigned long should return unsigned long
> f) short and easy to understand
> 
> I don't think it's possible to implement the function to the spec in 
> current D, so designs are allowed to invent new features, as long as 
> they define them thoroughly.

Thanks to all for the great replies. I am glad there is consensus that
max is a good exercise to test a language's expressive power.

Below is an implementation of max that uses some in-design language
features.

template max
{
private:
   template maxtype(storageof(T1) T1, storageof(T2) T2)
   {
     static if (storageof(T1) T1 == storageof(T2) T2)
       alias storageof(T1) T1 maxtype;
     else
       static if (std.is_num!(T1) && std.is_num!(T2))
         static if (T2.max > T1.max)
           alias T2 maxtype;
         else
           alias T1 maxtype;
       else
         alias typeof(true ? T1 : T2) maxtype;
   }
   maxtype(storageof(T1) T1, storageof(T2) T2)
   max2(T1, T2)(storageof(T1) T1 lhs, storageof(T2) T2 rhs)
   {
     if (rhs > lhs) return rhs;
     return lhs;
   }
public:
   alias std.varargs_reduce!(max2) max;
}

A good amount of explanations is in order.

* First off, specifying storageof(T) T as the type of a function
argument means that the compiler will deduce the storage class of the
actual argument: if the argument is an lvalue, inout will be deduced.

* The maxtype template calculates the "best" type for maximum of two
numbers in the obvious manner. It is clear how the code can be adapted
for min. Given that the storage classes are being passed to maxtype,
they will "stick".

(Aside: the "storageof(T) T" notation is Walter's. My preference was "S
T" where S is bound to an independent symbol. I couldn't manage to
convince Walter. One other idea that Walter had was the notation "$T",
thus restoring some face for the wasted symbol "$".)

* A minor point is that the code assumes a future feature of D: if a
template only defines a public member and that member is homonym with
the template name, that member gets automatically promoted as a synonim
for the template name. (This is a relaxation of the existing rule.)

* The weirdest part is that max never deals with varargs. That is the
job of the std.varargs_reduce metafunction. std.varargs_reduce is so
useful, it will be part of the standard library. It takes a function of
two arguments and defines a vararg function that applies the
two-argument function repeatedly to accommodate multiple arguments. For
example, std.varargs_reduce!(max2) defines a vararg template function
that, when applied to three arguments, will apply max2(max2(a, b), c).

std.varargs_reduce is a static version of the well-known reduce
primitive found in functional languages. For background on functional
reduce, see e.g.:

http://www.joelonsoftware.com/items/2006/08/01.html

There is little surprise what the next challenge will be :o). But I will
confine that to a distinct post.


Andrei



More information about the Digitalmars-d mailing list