Findfirst() with Dict for which nothing is a valid key


#1

Well, it does have the slight problem:

d=Dict(); d[1]=1;  d[nothing]=2;
d
#Dict{Any,Any} with 2 entries:
#  nothing => 2
#  1       => 1

@show findfirst(equalto(0), d)
#findfirst(equalto(0), d) = nothing
@show findfirst(equalto(1), d)
#findfirst(equalto(1), d) = 1
@show findfirst(equalto(2), d)
#findfirst(equalto(2), d) = nothing

The new output of findfirst is dangerous
#2

That’s not true, if nothing can be an index (key) into the collection.
All the change did was hurt performance and move the place where things break down from one place (where 0 might be a valid index [now possible with OffsetArrays, or other AbstractArrays)

I think a better interface, that would not lose any performance, and would be able to handle the case of nothing being an index (where I worked we actually used that, to store JSON null as a key in AssociativeArrayss), would be to add two functions, found(collection, retval) and found_key(collection, retval) (and maybe a macro to make life a bit easier).
If a particular type can use something like 0, or typemin(Int) as a in-band sentinel value for a not-found index/key, for example with String, then you’d have the definition: found(::Type{String}, val) = val != 0 and found_key(::Type{String}, val) = val.
For something where no in-band sentinel value is possible (i.e. the key is <: Any, or any union that includes Nothing), then the return value can simply be a tuple, i.e. (true, value) or (false, nothing). If nothing is found, it would return (true, nothing), and everything works correctly, no funky special cases. The definitions then would be found(::Type{MyType}, ret) = ret[1], and found_key(::Type{MyType}, ret) = ret[2].


#3

See also previous discussion at Suggestion for more general/performant sentinel for find* functions.