-
-
Notifications
You must be signed in to change notification settings - Fork 5.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add get(coll, key) for Associative returning Nullable #18211
Conversation
@@ -6,6 +6,13 @@ const secret_table_token = :__c782dbf1cf4d6a2e5e3865d7e95634f2e09b5902__ | |||
|
|||
haskey(d::Associative, k) = in(k,keys(d)) | |||
|
|||
""" | |||
getnull(d::Associative, key) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if exported, this should also go in the rst docs - see CONTRIBUTING.md for full explanation, but you put the signature there then run make docs
and the docstring content should get populated over.
would the names |
or even just define this is 2-argument |
This should get its own name. Otherwise, it will not generalize to multidimensional indexed arrays. Technically this is |
eea0e0c
to
ee7325a
Compare
@vtjnash @TotalVerb Based on your comments, I changed the name to Please let me know if you find any other issues. |
@@ -698,6 +706,11 @@ function get{K,V}(default::Callable, h::Dict{K,V}, key) | |||
return (index < 0) ? default() : h.vals[index]::V | |||
end | |||
|
|||
function tryget{K,V}(h::Dict{K,V}, key) | |||
index = ht_keyindex(h, key) | |||
return (index<0) ? Nullable{V}() : Nullable{V}(h.vals[index]::V) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is this more efficient than the abstract definition above? the generic one should be correct here right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This avoids computing the hash twice.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@@ -452,6 +456,10 @@ let d = ImmutableDict{String, String}(), | |||
@test get(d4, "key1", :default) === v2 | |||
@test get(d4, "foo", :default) === :default | |||
@test get(d, k1, :default) === :default | |||
@test tryget(d1, "key1") === Nullable{String}(v1) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@tkelman tryget(::ImmutableDict, key)
is tested here.
The name |
@eschnett As for the function being unlikely to be used, I will point out that in Python, for example, Julia seems to lack a type-stable equivalent, which is what motivated this addition. |
I understand and agree with the motivation. What I meant to say is that the difference in function name length between |
FWIW, I'm not a fan of I wonder if it'd be worth considering an alternative method like: julia> Base.get{T}(::Type{T}, dict, key, default) = haskey(dict, key) ? T(dict[key]) : T(default)
julia> t = Dict{String,Int}("a"=>1, "b"=>2, "c"=>3)
Dict{String,Int64} with 3 entries:
"c" => 3
"b" => 2
"a" => 1
julia> get(Nullable{Float64}, t, "a", 0)
Nullable{Float64}(1.0)
julia> get(Nullable{Float64}, t, "d", 0)
Nullable{Float64}(0.0)
julia> get(Int8, t, "a", 0)
1
julia> get(Int8, t, "d", 0)
0 It's nice because: 1) it doesn't require any new methods and 2) it generalizes to non-Nullable situations where I might just want to ensure I always get the same type out of a dict (think about |
@eschnett @quinnj I'd like to narrow the scope of this PR. Can we please move debates about renaming the existing I'm trying to resolve the narrow issue of avoiding repeat lookups in a dictionary when both testing for a key and retrieving its value. When @vtjnash pointed out the existing If the community later concludes that all the |
I think the name |
I still think 4-arg |
If we must go there, then I'll say that: x = get(Nullable{Int64}, dict, "myKey", Nullable{Int64}()) makes my eyes bleed. More general or not, I would want to have a succinct and readable way of expressing this. Spelling out the Nullable-decorated value type of the dictionary (twice!) is something only a Java programmer could love. (duck!) x = tryget(dict, "myKey") (or |
Maybe just |
@StefanKarpinski Is there any precedent in the language for doing something like that? Admittedly, it's no longer than |
Nice and concise. Combined with @quinnj's proposal, it would just be something like get(::Type{Nullable}, dict, key) = get(Nullable{valtype(dict)}, dict, key)
function get{T}(::Type{Nullable{T}}, dict, key, default=Nullable{T}())
# ...
end I guess? It only seems the semantics would be ambiguous if someone decides to store |
It would be less ambiguous (and more general) if it were |
@TotalVerb So we would presumably define: f() = Nullable{Int}() # neutral element
f(x) = Nullable{Int}(x) # present element And get(Nullable{Int}, dict, key) With the function coming first, this almost seems to suggest a x = get(dict, "key") do
# ??? needs to be defined for both 0- and 1- arg invocations
end |
Unfortunately, I just realized that three-argument |
@TotalVerb Doesn't the existing 3-arg get(x::Associative, key, default) # use this signature for existing
get(f::Function, x::Associative, key) # use this signature for the new proposed form Maybe this is hard to get right -- I don't know. |
There is also a 3-arg get with function as first argument. It tries to get the value, then returns it unchanged; otherwise, it returns the value obtained by invoking the function. See for instance julia> get(Nullable{Int}, Dict(:a => 1), :a)
1
julia> get(Nullable{Int}, Dict(:a => 1), :b)
Nullable{Int64}() |
@TotalVerb Amazingly (I think) this function is almost exactly what we want, but we'd have to change the semantics. The current behavior is: get(f, dict, key) = haskey(dict, key) ? dict[key] : f() The behavior we want is: get(f, dict, key) = haskey(dict, key) ? f(dict[key]) : f() (Caveat: The real implementation would need to lookup the key only once, not twice as shown here.) It's close, but I guess this would constitute a breaking change if implemented. |
The trouble is that changing these semantics is a pretty big breaking change, especially since I believe this form is used often with get(dict, key) do
# create default
end Perhaps there is an efficient (no runtime overhead, ideally) way to fall back to |
Perhaps the cure is worse than the disease. What guiding principles does the Julia community use to determine when it's better to overload an existing method, and when it's better to just have a separately named one? Personally, I'm starting to lean toward the latter again. |
I honestly am really not a fan of using the same name for what are very, very different operations. We don't use the same name for
|
@TotalVerb I don't understand why this could not be the 2-arg |
@rfourquet I am going to walk back my argument, because in fact |
In light of all of this, I'd like to propose that we use the 2-argument form of get{K,V}(a::Associative{K,V}, key) = haskey(a, key) ? Nullable{V}(a[key]::V) : Nullable{V}() This would seem to have the following benefits:
Up-Vote / Down-Vote. Reactions? |
I can get behind that. Though I would note that this would seem to be our first big foray into a real "commitment" to Nullables. Not saying it's a bad thing or irreversible or anything like that, I just know it took a while for Nullables to come about and this is a big (probably good) step to really integrating them with the rest of Base. |
@quinnj That's a very good point. I hope that committing to I remember hearing at least one opinion at this year's JuliaCon that To what degree is type-constrained dispatch optimization being considered for Julia 1.0? Is it strong enough to advise against using |
Always returning a The same strategy could be applied to |
@nalimilan I was not suggesting that the 3-arg form of |
Even if only the two-argument form is changed, this needs a deprecation path to avoid breaking existing code. |
@nalimilan There is no existing 2-arg form. This is the proposed addition, not a proposed change. |
Sorry, I would have sweared it existed. So it's actually a good thing it wasn't added before, as it makes it much easier to use it instead of |
ee7325a
to
b8c1e81
Compare
Just updated the PR to implement: get(h::Associative, key) -> Nullable Could someone please review? |
This returns a Nullable value corresponding to the key (or null). Includes specialization for various dictionary types. (Fix bug in priority queue test.) Fixes JuliaLang#13055
b8c1e81
to
0e01220
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This needs rebasing (e.g. PriorityQueue
), but looks good to me. I had forgotten about this issue and was about to start a PR doing exactly this. I really don't see what fits better a two-arg get
than this, and this seems to be exactly the kind of function Nullable
is designed for (not in the statitistician meaning of course). I'm just uncertain about the deprecation problem If nullable gets renamed to Option
or some other name. If this happens "soon", could be better to wait for the new name first (and use tryget
in the meantime). But this functionality is needed, can someone make a decision?
This is the poster child for the |
Nullable is no longer in Base |
The issue itself is still relevant though. See PR #25131 for a similar change (merging |
This returns a Nullable value corresponding to the key (or null).
Includes specialization for dictionaries.
Fixes #13055