Max and maximum: why the difference?

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.

3 Likes

This would be really confusing. It’s better to just use two functions rather than trying to guess what should the function do. See Merging `max` and `maximum` · Issue #11872 · JuliaLang/julia · GitHub

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.

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).

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

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).

5 Likes
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.)

1 Like

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.

2 Likes

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.

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

1 Like

One possible way is this:

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

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.

2 Likes