Max and maximum: why the difference?


#1

max operates only on its arguments*, and is only useful if the number of args is > 1.** maximum operates on a collection and only takes a second argument in specific cases.

Is there any reason why we couldn’t have a single-argument method on max that, if given a collection, returns the equivalent of maximum?

*max has deprecated behavior in that it will operate on collections in an element-wise order.

**This is inconsistent: max(5) returns (the possibly-expected) 5, but max([1,2,3]) does not return [1,2,3], nor does max("a") return a.


#2

This would be really confusing. It’s better to just use two functions rather than trying to guess what should the function do. See https://github.com/JuliaLang/julia/issues/11872#issuecomment-243947437

The deprecated behavior has been, well, deprecated so it’s irrelevant now.

It’s not inconsistent. The “inconsistency” is that some types are comparable and some are not.


#3

The “inconsistency” is that some types are comparable and some are not.

I can understand that if you’re comparing two things. But what are you comparing with a single-argument max, and how is max(x::Real) = x useful?

And I swear it’s complete coincidence that I asked this question within 2 hours of @jeff.bezanson closing that issue out (I didn’t even know it existed).


#4

It’s as useful as +(::Real) and it shouldn’t be assigned a meaning that is incompatible with what the function means otherwise.


#5

Imagine a function like

f(x...) = max(x...)+1

I think people would be annoyed if this worked for f(x,y,z) and f(x,y) but then mysteriously failed on f(x).


#6
julia> max("a","b","c")
"c"

julia> max("a","b")
"b"

julia> max("a")
ERROR: MethodError: no method matching max(::String)
Closest candidates are:
  max(::Any, ::Any) at operators.jl:348
  max(::Any, ::Any, ::Any, ::Any...) at operators.jl:424
  max(::BigFloat, ::BigFloat) at mpfr.jl:577
  ...

julia> f(x...) = max(x...) + 1
f (generic function with 1 method)

julia> f([1,2,3], [4,5,6], [7,8,9])
3-element Array{Int64,1}:
  8
  9
 10

julia> f([1,2,3], [4,5,6])
3-element Array{Int64,1}:
 5
 6
 7

julia> f([1,2,3])
ERROR: MethodError: no method matching max(::Array{Int64,1})
Closest candidates are:
  max(::AbstractArray{T1<:Real,N} where N, ::Real) where T1<:Real at deprecated.jl:50
  max(::AbstractArray{T1<:Real,N} where N, ::AbstractArray{T2<:Real,N} where N) where {T1<:Real, T2<:Real} at deprecated.jl:50
  max(::Any, ::Any) at operators.jl:348
  ...
Stacktrace:
 [1] f(::Array{Int64,1}, ::Vararg{Any,N} where N) at ./REPL[4]:1

Yes. (Though, to be fair, this isn’t the behavior I’m suggesting for a single vector/collection.)


#7

The behavior of max("a") basically means that the current implementation is not consistent enough and should be fixed. It’s not an argument for making it even more inconsistent. The max([1, 2, 3]) case is irrelavent.


#8

You could just add square brackets to the max argument in that case:

f(x...) = max([x...])+1

I would like to have just one function to get the maximum of a collection or several items.
The two functions always confuse me and i think that the dot-notation removes the ambiguities.


#9

I think that the dot notation will allocate memory so won’t be efficient for large collections.


#10

One possible way is this:

f(x...) = (length(x) == 1 ? x[1] : max.(x...))+1

#11

Is there any reason why we couldn’t have a single-argument method on max that, if given a collection, returns the equivalent of maximum?

There was a long discussion in #4235. It explained the logic. Personally, I think there is no simple and intuitive reason for not choosing an unified max method.

From what I understand from related discussion, the drawbacks for using a unified max method are:
(1) A single max with keyword arguments method is run-time slower than the max + maximum combination. So a performance-wise issue.
(2) A single max method breaks the consistency of dot-call usages. For example, the max would work for both max(scalar) and max(vector). Some developers argued that a single method accepts both scalar and vector input is confusing or redundant(use dot-call instead). Two other examples are sin and clamp, both of them do not accept vector input either.

IMHO, current implementation is explainable but not intuitive enough.