[erlang-questions] erlang:max/2 and erlang:min/2

Nicholas Frechette zeno490@REDACTED
Mon Sep 13 16:41:00 CEST 2010

Yes precisely, I was arguing mixed mode arithmetic should be banned or have
no defined behavior to force programmers to think and be sure to know what
they are doing.
I agree with your claim that the behavior is currently unintuitive and prone
to error. I simply meant to illustrate erlang isn't alone in that respect
and that when one programs in a language, one has to know the potential
pitfalls associated with it...
I wouldn't be surprised if at this point they were unwilling to correct this
situation considering it is of fairly high impact. Although perhaps it could
be done with compiler/erl switches to turn on/off the old/new behavior.

The bit about the ternary operator is to show that for most of these
languages, a similar construct is likely used which causes order to matter.
In erlang, it could have been done as follow (wild guess as it is probably a
nif but likely of a similar nature given the observed result):
max(A, B) when A >= B -> A;
max(_, B) -> B.

A more correct implementation could have been (not actually compiled to test
but you get the idea):
%% Integer/integer
max(A, B) when is_integer(A) and is_integer(B) and A >= B -> A;
max(A, B) when is_integer(A) and is_integer(B) -> B;
%% Float/float
max(A, B) when is_float(A) and is_float(B) and A >= B -> A;
max(A, B) when is_float(A) and is_float(B) -> B;
%% Mixed integer/float coerced result as float but could be made to raise an
exception/exit process
max(A, B) when is_integer(A) and is_float(B) and float(A) >= B -> float(A);
%% Not sure if float() can be used in a guard?
max(A, B) when is_integer(A) and is_float(B) -> B;
max(A, B) when is_float(A) and is_integer(B) and A >= float(B) -> A;
max(A, B) when is_float(A) and is_integer(B) -> float(B);
%% General case of other types, would it make sense though?
max(A, B) when A >= B -> A;
max(_, B) -> B.

Unfortunately, the above doesn't extend to tuples/lists/other types.

*** Side note ***
Also, while it is true the ternary operator can't always be used as an
l-value, some compilers still allow it as it must have been once part of the
For example, wikipedia still mentions it at:
The link I included (which you may not have read) also says: "The type of
the result is the common type, and it is an l-value if both the second and
third operands are of the same type and both are l-values."
Unfortunately I can't find a copy of the C++ standard laying around but here
are some results on the compilers I currently have on hand:
In VS2008, the below compiles for win32, for the xbox360 (somewhat expected
since they are both Microsoft compilers) and perhaps surprisingly on sony's
compiler for the PS3 (PPU) (which is oftentimes more strict than GCC but
obviously not for this special case) :
void* A;
void* B;
((someCondition) ? A : B) = NULL;
Alas, I digress.

On Sun, Sep 12, 2010 at 10:43 PM, Richard O'Keefe <ok@REDACTED> wrote:

> On Sep 11, 2010, at 11:53 AM, Nicholas Frechette wrote:
> > This is fairly standard as far as I know in languages without strong
> typing in function arguments (when that is the case, usually coercion
> happens which hides this behavior from you).
> > For example in ruby:
> > >> [1, 1.0].min
> > => 1
> > >> [1.0, 1].min
> > => 1.0
> The traditional retort to this is "And if all your friends jumped off
> a cliff, would you copy that too?"
> >
> > For these languages (and erlang), order DOES matter.
> And all that means is that they are buggy too.
> >
> > This isn't unlike a naive implementation of min/max in C++:
> > #define max(A, B) A >= B ? A : B
> Er, that is a couple of steps of beginnerness _below_ "naive".
> #define max(A, B) ((A) >= (B) ? (A) : (B))
> is normally required, and of course the preferred method in C++
> is
>        template <typename T>
>        T max(T a, T b) {
>            return a >= b ? a : b;
>        }
> for obvious reasons.
> >
> > However, even using c++0x:
> > auto SomeResult = max(1, 1.0); // what happens here? what is the type of
> SomeResult? (float) See note below
> > auto SomeResult = max(1.0, 1); // what happens here? what is the type of
> SomeResult? (float) See note below
> What happens here is quite irrelevant to dynamically typed languages.
> The if-then-else operator has to return a result of *some* definite type,
> so there really are only two choices for it.
> Either it demands that both choices be the same type,
> or else it has to convert at least one of them.
> This has no significance whatever for a language that *doesn't* require
> all arms of an if to have the same type and *doesn't* have to convert.
> >
> > *** Note below (really a side note, you can skip this) ***
> > The 'ternary' operator is special (in C/C++). In the case above, both A
> and B can't be of different types.
> I think "can't" was meant to be "can".
> > The type will be coerced to a common base type only if possible,
> otherwise compilation will fail.
> > See for details:
> http://msdn.microsoft.com/en-us/library/e4213hs1%28VS.71%29.aspx (applies
> to other compilers as well)
> > The ternary operator can also be used as an l-value (for fun and profit)
> :)
> > ((A >= B) ? A : B) = 123; // Don't do this.. otherwise your coworkers
> will want to kill me for teaching you
> > ***
> One reason they will be displeased with you is that most compilers reject
> this.
> A typical error message:
>        "foo.c", line 2: warning: conditional expression is not a valid
> lvalue
> >
> > In C++ at least, both types will be converted to a common base type with
> the ternary operator or you'll traditionally write functions which will
> coerce the arguments for you in order to use hardware specific instructions
> to do the selection:
> > max(A,B) becomes 2 instructions for floats/doubles (on powerpc, same for
> min): C = A - B, C >= 0 ? B : A (powerpc has an instruction to compare a
> float against zero and select the result (fsel))
> On a SPARC or a Pentium or an ARM, it would be
>        [assume A is in register x]
>        [assume B is in register y]
>        [assume result is wanted in register x]
>        compare x with y                one compare instruction
>        move y to x if x < y            one conditional move
> It is not clear to me how this is supposed to be relevant to Erlang,
> which has to deal with mixed integer/float comparisons, or to the
> question of how you implement max/2 and min/2 so that they satisfy
> the laws that a programmer *ought* to be able to take for granted.
> C and C++ can get away with (user-provided) definitions that convert
> basically because they can't have arrays whose elements are of varying
> types, so if you do
>        auto x = a[0];
>        for (int i = 1; i < n; i++) x = max(x, a[i]);
> there _can't_ be any problems introduced by conversion, because
> all the a[i] must be the *same* type.
> But in Erlang, the elements of a list CAN be of different types.
> Intuitions based on experience with strongly typed languages
> (Ada, Clean, Haskell) would tell you that mixed-mode arithmetic
> should simply be banned.  Any time it is intentional, an explicit
> coercion can be provided by the programmer.
> Intuitions based on experience with weakly typed languages
> (C, C++, Java, C#) would mislead you into thinking that operations
> that don't actually obey the mathematical laws they are supposed
> to are OK because thanks to the all-elements-of-an-array-are-the-
> same-type setup, uses of < and max DO obey the laws in question.
> (Sorting a floating-point array is unproblematic in C
> *IF* (1) there is at most one -infinity
>     (2) there is at most one +infinity
>     (3) there are no NaNs
>     (4) either you don't care about the order of -0 and +0
>         or they don't both occur.
>  Sorting a mixed integer/float array just can't happen.)

More information about the erlang-questions mailing list