# \`findmax\` and friends: confusing behaviour to be introduced in 1.7

**URL:** <https://discourse.julialang.org/t/findmax-and-friends-confusing-behaviour-to-be-introduced-in-1-7/61904>\
**Category:** Internals & Design\
**Created:** [May 27, 2021, 7:23am UTC](https://discourse.julialang.org/t/findmax-and-friends-confusing-behaviour-to-be-introduced-in-1-7/61904 "2021-05-27T07:23:32Z")\
**Posts on this page:** 20\
**Page:** 1

<div class="post-metadata">

**Author:** ![aplavin](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/aplavin/32/222056_2.png) [@aplavin](https://discourse.julialang.org/u/aplavin)\
**Post date:** [May 27, 2021, 7:23am UTC](https://discourse.julialang.org/t/findmax-and-friends-confusing-behaviour-to-be-introduced-in-1-7/61904/1 "2021-05-27T07:23:33Z")

</div>

Several people, including myself, have tried to raise this issue during last months. There were multiple posts on slack, and even a GH issue ([https://github.com/JuliaLang/julia/issues/39203](https://github.com/JuliaLang/julia/issues/39203)). I’ll try to raise it once more, here on discourse, hoping that it gets more traction.

The `findmax(A)` function, which everyone is used to, returns the maximum value in the array `A` and its index. However, Julia 1.7 will introduce a `findmax(f, A)` version, that doesn’t return any index at all:

```julia
julia> A = [5, 1, 3, 8, 0];

julia> maxval, maxix = findmax(A)
(8, 4)
julia> A[maxix]
8

# but:
julia> findmax(identity, A)
(8, 8)

```

There are at least two independent reasons why this feels really weird, confusing, and even surprising (as evidenced by multiple questions on slack).

First: we still have no effecient way to find array index of the maximal value wrt some function! That is, something like _(imaginary)_ `findmax(["a", "bc", "d"], by=length)`. For arrays and many other collections, obtaining index is a more fundamental operation: easy to go from index to value, hard to go the other way.

Second: the `findmax(f, A)` form, to be introduced in 1.7, is completely inconsistent with both `findmax(A)` and with other reductions that take a function and an array:

```julia
julia> findfirst([false, false, true])
3
julia> findfirst(identity, [false, false, true])
3 # same as above

# but:
julia> findmax([false, false, true])
(true, 3)
julia> findmax(identity, [false, false, true])
(true, true) # definitely not the same

```

Even the docstrings for these two forms have literally nothing in common:

```julia
findmax(A): Return the maximal element of the collection itr and its index or key.
findmax(f, A): Returns a pair of a value in the codomain (outputs of f) and the corresponding value in the domain (inputs to f) such that f(x) is maximised.

```

Currently, any (?) reduction of the form `r(f, A)` in base julia is just a more efficient form of `r(map(f, A))`.

Several different solutions were suggested in the linked GH issue.  
**I believe the `findmax(f, A)` form to-appear-in-1.7 should be removed anyway while it’s not released, and one of those suggested approaches implemented instead, either in 1.7 or later.**

NB: discussion above applies to the whole `findmax, findmin, argmax, argmin` function family.

As I understand PR [https://github.com/JuliaLang/julia/pull/35316](https://github.com/JuliaLang/julia/pull/35316), the motivation for the soon-to-be-released `findmax(f, A)` form is based on the mathematical interpretation of `argmax(f, A) == argmax f(x) over x in A`. This is, however, not the meaning currently attached to this function family in julia. No optimization package overrides `argmax` to perform maximization of `f` over some domain, like `argmax(f, 0..Inf)`. And `findmax(f, A)` doesn’t even have this similarity to any mathematical notation.

---

<div class="post-metadata">

**Author:** ![lstagner](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/lstagner/32/448_2.png) [@lstagner](https://discourse.julialang.org/u/lstagner)\
**Post date:** [May 27, 2021, 7:35am UTC](https://discourse.julialang.org/t/findmax-and-friends-confusing-behaviour-to-be-introduced-in-1-7/61904/2 "2021-05-27T07:35:43Z")

</div>

Hmm…I get the new findmax and I get the mathematical reasons so I dont really see a problem with it. Multiple dispatch means you can share a name.

The array version can be replicated using the function version using `(maxval, maxind) = findmax(i->A[i], 1:length(A))` The `identity` function isn’t the right function to use to compare.

---

<div class="post-metadata">

**Author:** ![aplavin](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/aplavin/32/222056_2.png) [@aplavin](https://discourse.julialang.org/u/aplavin)\
**Post date:** [May 27, 2021, 7:42am UTC](https://discourse.julialang.org/t/findmax-and-friends-confusing-behaviour-to-be-introduced-in-1-7/61904/3 "2021-05-27T07:42:26Z")

</div>

> [@lstagner](#):
>
> I get the new findmax and I get the mathematical reasons

What are mathematical reasons for the 1.7 `findmax(f, A)` again? As I said,

> findmax(f, A) doesn’t even have this similarity to any mathematical notation.

> [@lstagner](#):
>
> Multiple dispatch means you can share a name.

Technically, of course, unrelated functions can share a name. But it would be weird to override e.g. `Base.length(x::Interval) = x.right - x.left`: original `length` function has a completely different meaning. Same with current `findmax(A)` vs 1.7 `findmax(f, A)`.

> [@lstagner](#):
>
> The `identity` function isn’t the right function to use to compare.

But it is the right function for all other reductions in base julia. Why make an exception here?

---

<div class="post-metadata">

**Author:** ![aplavin](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/aplavin/32/222056_2.png) [@aplavin](https://discourse.julialang.org/u/aplavin)\
**Post date:** [May 27, 2021, 7:49am UTC](https://discourse.julialang.org/t/findmax-and-friends-confusing-behaviour-to-be-introduced-in-1-7/61904/4 "2021-05-27T07:49:32Z")

</div>

> [@lstagner](#):
>
> The array version can be replicated using the function version using `(maxval, maxind) = findmax(i->A[i], 1:length(A))`

This is only equivalent to `findmax(A)` for 1-based 1d arrays, btw. `findmax(i->A[i], keys(A))` would be more general, but inefficient for dicts.

---

<div class="post-metadata">

**Author:** ![Tamas\_Papp](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/tamas_papp/32/25949_2.png) [@Tamas\_Papp](https://discourse.julialang.org/u/Tamas_Papp)\
**Post date:** [May 27, 2021, 9:18am UTC](https://discourse.julialang.org/t/findmax-and-friends-confusing-behaviour-to-be-introduced-in-1-7/61904/5 "2021-05-27T09:18:10Z")

</div>

> [@lstagner](#):
>
> The array version can be replicated using the function version using `(maxval, maxind) = findmax(i->A[i], 1:length(A))` The `identity` function isn’t the right function to use to compare.

I have to admit that I also find this a bit confusing. I do not doubt that in some cases `findmax(f, itr)` is useful, but I am wondering if it should be renamed something else. This looks like a pun on `findmax` & friends.

I am wondering if a package would be the best place for further extensions and experimentation along similar lines.

---

<div class="post-metadata">

**Author:** ![bkamins](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/bkamins/32/208538_2.png) [@bkamins](https://discourse.julialang.org/u/bkamins)\
**Post date:** [May 27, 2021, 9:59am UTC](https://discourse.julialang.org/t/findmax-and-friends-confusing-behaviour-to-be-introduced-in-1-7/61904/6 "2021-05-27T09:59:58Z")

</div>

The issue is that in very many places in Julia we have

```julia
f(identity, collection)

```

is the same as

```julia
f(collection)

```

(actually the second method often calls the first)

This is something users are very used to and breaking this mental pattern is problematic I think. Just some quick examples cut-out from the Julia Base:

```julia
reduce(op, A::AbstractArray; kw...) = mapreduce(identity, op, A; kw...)
count(A::AbstractArrayOrBroadcasted; dims=:, init=0) = count(identity, A; dims, init)
_all(a, ::Colon) = _all(identity, a, :)
_any(a, ::Colon) = _any(identity, a, :)

```

---

<div class="post-metadata">

**Author:** ![Sukera](https://avatars.discourse-cdn.com/v4/letter/s/ce7236/32.png) [@Sukera](https://discourse.julialang.org/u/Sukera)\
**Post date:** [May 27, 2021, 11:50am UTC](https://discourse.julialang.org/t/findmax-and-friends-confusing-behaviour-to-be-introduced-in-1-7/61904/7 "2021-05-27T11:50:09Z")

</div>

Some more confusing behaviour:

```julia
julia> findmin(Dict(2 => 3, 4 => 5)) # added after the discussion below
(3, 2)                              

julia> findmin(identity, Dict(2 => 3, 4 => 5))                   
(2 => 3, 2 => 3)                                                 
                                                                 
julia> findmin(sqrt, Dict(2 => 3, 4 => 5))                       
ERROR: MethodError: no method matching sqrt(::Pair{Int64, Int64})
Closest candidates are: ...

```

@cmcaine @ColinCaine is that intended (sorry, don’t know which one is correct)? I would have expected this to iterate over the keys of the dict…

---

<div class="post-metadata">

**Author:** ![tchr](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/tchr/32/15138_2.png) [@tchr](https://discourse.julialang.org/u/tchr)\
**Post date:** [May 27, 2021, 12:21pm UTC](https://discourse.julialang.org/t/findmax-and-friends-confusing-behaviour-to-be-introduced-in-1-7/61904/8 "2021-05-27T12:21:32Z")

</div>

One alternative might be to have a keyword argument, say `index` or `return_index`, defaulting to `true` (i.e. the behavior of returning an index, not a value) - and throwing for non-indexable iterables. Then using `findmax` and friends for non-indexable iterables would require explicitly setting `return_index = false`.

---

<div class="post-metadata">

**Author:** ![Skoffer](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/skoffer/32/378_2.png) [@Skoffer](https://discourse.julialang.org/u/Skoffer)\
**Post date:** [May 27, 2021, 12:23pm UTC](https://discourse.julialang.org/t/findmax-and-friends-confusing-behaviour-to-be-introduced-in-1-7/61904/9 "2021-05-27T12:23:10Z")

</div>

That would be type unstable, I presume.

---

<div class="post-metadata">

**Author:** ![aplavin](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/aplavin/32/222056_2.png) [@aplavin](https://discourse.julialang.org/u/aplavin)\
**Post date:** [May 27, 2021, 12:23pm UTC](https://discourse.julialang.org/t/findmax-and-friends-confusing-behaviour-to-be-introduced-in-1-7/61904/10 "2021-05-27T12:23:19Z")

</div>

`findmax()` without index is just `maximum()`.

---

<div class="post-metadata">

**Author:** ![tchr](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/tchr/32/15138_2.png) [@tchr](https://discourse.julialang.org/u/tchr)\
**Post date:** [May 27, 2021, 1:34pm UTC](https://discourse.julialang.org/t/findmax-and-friends-confusing-behaviour-to-be-introduced-in-1-7/61904/11 "2021-05-27T13:34:04Z")

</div>

This is technically true but (I believe) most likely wouldn’t show up in real code due to constant propagation (you would always assign `return_index` literally), see e.g.:

```nohighlight
julia> f(x; kw) = kw ? 1+x : 1.0+x
f (generic function with 2 methods)

julia> g(x) = f(x; kw=true)
g (generic function with 2 methods)

julia> h(x) = f(x; kw=false)
h (generic function with 1 method)

julia> @code_warntype g(1)
Variables
  #self#::Core.Const(g)
  x::Int64

Body::Int64
1 ─ %1 = (:kw,)::Core.Const((:kw,))
│ %2 = Core.apply_type(Core.NamedTuple, %1)::Core.Const(NamedTuple{(:kw,), T} where T<:Tuple)
│ %3 = Core.tuple(true)::Core.Const((true,))
│ %4 = (%2)(%3)::Core.Const((kw = true,))
│ %5 = Core.kwfunc(Main.f)::Core.Const(var"#f##kw"())
│ %6 = (%5)(%4, Main.f, x)::Int64
└── return %6

julia> @code_warntype h(1)
Variables
  #self#::Core.Const(h)
  x::Int64

Body::Float64
1 ─ %1 = (:kw,)::Core.Const((:kw,))
│ %2 = Core.apply_type(Core.NamedTuple, %1)::Core.Const(NamedTuple{(:kw,), T} where T<:Tuple)
│ %3 = Core.tuple(false)::Core.Const((false,))
│ %4 = (%2)(%3)::Core.Const((kw = false,))
│ %5 = Core.kwfunc(Main.f)::Core.Const(var"#f##kw"())
│ %6 = (%5)(%4, Main.f, x)::Float64
└── return %6

julia> @code_warntype h(1.0)
Variables
  #self#::Core.Const(h)
  x::Float64

Body::Float64
1 ─ %1 = (:kw,)::Core.Const((:kw,))
│ %2 = Core.apply_type(Core.NamedTuple, %1)::Core.Const(NamedTuple{(:kw,), T} where T<:Tuple)
│ %3 = Core.tuple(false)::Core.Const((false,))
│ %4 = (%2)(%3)::Core.Const((kw = false,))
│ %5 = Core.kwfunc(Main.f)::Core.Const(var"#f##kw"())
│ %6 = (%5)(%4, Main.f, x)::Float64
└── return %6

```

There’s [some discussion of relying more upon this kind of constant propagation e.g. for `qr`](https://github.com/JuliaLang/julia/pull/25303) (but it seems it might require inlining or `@aggressive_constprop` to be robust?).

---

<div class="post-metadata">

**Author:** ![tchr](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/tchr/32/15138_2.png) [@tchr](https://discourse.julialang.org/u/tchr)\
**Post date:** [May 27, 2021, 1:38pm UTC](https://discourse.julialang.org/t/findmax-and-friends-confusing-behaviour-to-be-introduced-in-1-7/61904/12 "2021-05-27T13:38:38Z")

</div>

Maybe I wasn’t clear: the idea would be to control whether the second element of the returned tuple contains the _value_`x[i]` in the array `x` under which `f` is optimized (`return_index = false`) or the index `i` (`return_index = true`). The first element of the returned tuple would of course still return `f(x[i])` (i.e., it would _not_ just be `maximum`…).

---

<div class="post-metadata">

**Author:** ![mcabbott](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/mcabbott/32/6603_2.png) [@mcabbott](https://discourse.julialang.org/u/mcabbott)\
**Post date:** [May 27, 2021, 1:55pm UTC](https://discourse.julialang.org/t/findmax-and-friends-confusing-behaviour-to-be-introduced-in-1-7/61904/13 "2021-05-27T13:55:23Z")

</div>

Might it be less confusing if `findmax(f, A)` returned 3 things, not two?

```julia
julia> v = [2.0, 3.0, -4.0];

julia> findmax(v)
(3.0, 2)

julia> findmax(abs2, v) # view as abs2 <| getindex(v,_) <| eachindex(v)
(16.0, -4.0, 3) # suggestion

```

---

<div class="post-metadata">

**Author:** ![Skoffer](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/skoffer/32/378_2.png) [@Skoffer](https://discourse.julialang.org/u/Skoffer)\
**Post date:** [May 27, 2021, 1:57pm UTC](https://discourse.julialang.org/t/findmax-and-friends-confusing-behaviour-to-be-introduced-in-1-7/61904/14 "2021-05-27T13:57:55Z")

</div>

But it again breaks expect behaviour `findmax(v) == findmax(identity, v)`

It is only my opinion, but it really looks like it’s better to introduce another function name for current version of `findmax(f, v)` instead of tormenting it and users with additional keywords and unexpect return types.

---

<div class="post-metadata">

**Author:** ![BioTurboNick](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/bioturbonick/32/6380_2.png) [@BioTurboNick](https://discourse.julialang.org/u/BioTurboNick)\
**Post date:** [May 27, 2021, 1:57pm UTC](https://discourse.julialang.org/t/findmax-and-friends-confusing-behaviour-to-be-introduced-in-1-7/61904/15 "2021-05-27T13:57:56Z")

</div>

@mcabbott , this was also my suggestion in the GitHub issue.

---

<div class="post-metadata">

**Author:** ![mcabbott](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/mcabbott/32/6603_2.png) [@mcabbott](https://discourse.julialang.org/u/mcabbott)\
**Post date:** [May 27, 2021, 2:00pm UTC](https://discourse.julialang.org/t/findmax-and-friends-confusing-behaviour-to-be-introduced-in-1-7/61904/16 "2021-05-27T14:00:19Z")

</div>

Oh right, I see your suggestion now.

I agree `identity` isn’t neutral, but at least it’s much more obvious that something different has happened.

---

<div class="post-metadata">

**Author:** ![aplavin](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/aplavin/32/222056_2.png) [@aplavin](https://discourse.julialang.org/u/aplavin)\
**Post date:** [May 27, 2021, 2:00pm UTC](https://discourse.julialang.org/t/findmax-and-friends-confusing-behaviour-to-be-introduced-in-1-7/61904/17 "2021-05-27T14:00:28Z")

</div>

This seems almost as error-prone as the current nightly version of findmax. I.e.:

```julia
val, ix = findmax(A) # fine

val, ix = findmax(f, A) # no error, but wrong result!
# julia will just swallow the remaining 3rd value

```

---

<div class="post-metadata">

**Author:** ![rdeits](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/rdeits/32/286_2.png) [@rdeits](https://discourse.julialang.org/u/rdeits)\
**Post date:** [May 27, 2021, 2:04pm UTC](https://discourse.julialang.org/t/findmax-and-friends-confusing-behaviour-to-be-introduced-in-1-7/61904/18 "2021-05-27T14:04:45Z")

</div>

You could return them in the order `(value, index, domain)` instead, in which case

```julia
val, ix = findmax(A)

```

and

```julia
val, ix = findmax(identity, A)

```

would both produce the same result.

---

<div class="post-metadata">

**Author:** ![aplavin](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/aplavin/32/222056_2.png) [@aplavin](https://discourse.julialang.org/u/aplavin)\
**Post date:** [May 27, 2021, 2:06pm UTC](https://discourse.julialang.org/t/findmax-and-friends-confusing-behaviour-to-be-introduced-in-1-7/61904/19 "2021-05-27T14:06:22Z")

</div>

This looks reasonable - to me at least (:

---

<div class="post-metadata">

**Author:** ![aplavin](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/aplavin/32/222056_2.png) [@aplavin](https://discourse.julialang.org/u/aplavin)\
**Post date:** [May 27, 2021, 2:09pm UTC](https://discourse.julialang.org/t/findmax-and-friends-confusing-behaviour-to-be-introduced-in-1-7/61904/20 "2021-05-27T14:09:54Z")

</div>

My main suggestion for now is to drop the current nightly `findmax(f, A)` method, and decide on the proper interface for this functionality after that without any rush. Note that it doesn’t even fundamentally require a positional argument overload and could work the same way as `sort` does: `sort(A, by=f)`.

[Next page](https://discourse.julialang.org/t/findmax-and-friends-confusing-behaviour-to-be-introduced-in-1-7/61904.md?page=2)
