-
-
Notifications
You must be signed in to change notification settings - Fork 422
This adds a require and update function for Associative Arrays #2162
Conversation
|
Thanks for your pull request and interest in making D better, @GilesBathgate! We are looking forward to reviewing it, and you should be hearing from a maintainer soon.
Please see CONTRIBUTING.md for more information. If you have addressed all reviews or aren't sure how to proceed, don't hesitate to ping us with a simple comment. Bugzilla referencesYour PR doesn't reference any Bugzilla issue. If your PR contains non-trivial changes, please reference a Bugzilla issue or create a manual changelog. Testing this PR locallyIf you don't have a local development environment setup, you can use Digger to test this PR: dub fetch digger
dub run digger -- build "master + druntime#2162" |
62fbe02
to
d1aed77
Compare
|
Save for the name the idea is awesome and it was a blocker for eg use of builtin AAs in DMD. I never seen add mean insert in D language except maybe in my codepoint set stuff in std.uni. Bikesheding the name can be elsewhere though;) |
|
@DmitryOlshansky I don't really like the name |
|
The NG (aka forum.dlang.org - general) |
|
@wilzbach @DmitryOlshansky : ok we can discuss here then: https://forum.dlang.org/post/ejdiweskylrprbculbna@forum.dlang.org |
src/object.d
Outdated
| assert("bar" !in aa); | ||
| } | ||
|
|
||
| auto getOrAdd(K, V)(ref V[K] aa, K key, lazy V value) |
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.
Return by ref, please. Could also use V.init as a default for value. Then we can write code like this:
struct S { int value; }
S[string] aa;
aa.getOrAdd("foo").value = 42;
assert(aa == ["foo": S(42)]);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.
I wondered whether it should be ref (which is why I added the basic value unit test), I hadn't considered this scenario, I will add a test for it and update.
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.
The default value is also a nice idea, so I will add that too (I didn't know lazy args could have a default value)
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.
Hm... I just suggested a name of getPtr on the newsgroup, as I assumed that's what you were doing, but it seems you actually are returning by value. I think returning by ref is preferable to either (maybe getRef?). Note that if you wanted to create a pointer to the value, you have to go through a triple lookup today (Edit: it's not necessary to do it this way, but not as obvious, see my comment below):
Value *p = void;
if(!(p = key in aa)) // one
{
aa[key] = initializeValue(); // two
p = key in aa; // three
}Having this function return by ref makes this very easy to do in one lookup.
Note that a class value type is going to be confusing here unless you do a default value of new Value.
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.
@schveiguy getPtrand getRef are certainly both terse which I like. My only criticism is that they don't as clearly communicate that the value will be added/inserted into the associative array.
I am a little bit confused by the triple lookup example. Can I not just do p = (aa[key] = initializeValue())
That said this PR will make this much more simple: Value* p = aa.getOrAdd(key, initializeValue()); Is there a unittest you can suggest to ensure this works as you expected?
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.
Can I not just do
p = (aa[key] = initializeValue())
You are still thinking in terms of classes. You may want a pointer to a value type (think int[int]), where you need to use key in aa to avoid further lookups.
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.
Hm... I guess you can do p = &(aa[key] = initializeValue()), which I didn't realize is possible. So no, it's not necessarily a triple lookup.
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.
they don't as clearly communicate that the value will be added/inserted into the associative array
A fair criticism. My response would be, it's just something people will learn. If you are getting a reference to the AA value, it needs to be in there.
In all honesty, before looking closer, I thought the get function already did this.
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.
In all honesty, before looking closer, I thought the get function already did this.
Ha, yeah I made the same mistake...that lead to a few hours of head scratching ;)
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.
@schveiguy I have added a unittest which I think covers your example. @aG0aep6G I've made the updates as you've suggested too.
src/object.d
Outdated
| assert(b == S(2)); | ||
|
|
||
| S* c = &aa.getOrAdd("baz", S(4)); | ||
| assert(c is &aa["baz"]); |
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.
@schveiguy This is the unittest I am referring to.
9373b0b
to
3843de8
Compare
src/object.d
Outdated
|
|
||
| unittest | ||
| { | ||
| struct S |
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.
static struct here, otherwise there is a connection between the unittest stack frame and every S instance.
e08c689
to
1c7e2f0
Compare
50efe1a
to
6a8b06f
Compare
df11c74
to
1ac1702
Compare
I believe the requested changes were made
|
I do think this could benefit from a changelog entry, however. It's quite an important change, I think it would be good to advertise it's availability. See https://github.com/dlang/druntime/blob/master/changelog/README.md |
|
@GilesBathgate I'm just waiting on a changelog entry. |
|
Disabled auto-merge so I can manually squash commits when all tests pass. |
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.
Nice!
src/object.d
Outdated
| * update = The delegate to apply on update. | ||
| */ | ||
| void update(K, V, C, U)(ref V[K] aa, K key, C create, U update) | ||
| if ((is(C : V delegate()) || is(C : V function())) && (is(U : V delegate(ref V)) || is(U : V function(ref 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.
I'm not sure this is the right approach. It forbids opCall and restrict some conversion which would be legal under the typesystem.
Maybe use is(typeof({ V* ptr; *ptr = create(); }) ?
Also shouldn't the arguments create and update be scope to avoid pointless allocation when passing delegate literals ?
src/object.d
Outdated
|
|
||
| unittest | ||
| { | ||
| static class C{} |
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.
Nit: add a space
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.
@Geod24 Between C and {}?
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.
Yes
src/object.d
Outdated
| * Returns: | ||
| * The value. | ||
| */ | ||
| ref V require(K, V)(ref V[K] aa, K key, lazy V value = V.init) pure |
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.
I would remove the pure annotation and make the template works its magic, or is there a specific reason you put it ?
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.
@Geod24 I Just copied the function attributes from the get function.
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.
@Geod24 Just so I am clear this is the 'magic' you are referring to: http://klickverbot.at/blog/2012/05/purity-in-d/#templates-and-purity i.e templates infer pure?
Presumably this wasn't the case when 'get' was implemented.
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.
Template infers attributes, so @nogc, @safe/@system, and pure
In this case since only pure and nothrow are put on the extern (C) declaration, that's as much as could get infered. Maybe there were some inference bug at the time ?
You can make sure this works by making a pure unittest
|
I'll refrain from merging this until @Geod24's comments are addressed. |
|
@GilesBathgate Nice Work! And, thank you for sticking with it. @Geod24 Could you please give this one last look. I'm itching to merge this. |
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.
Looks very good to me, thanks!
|
Disabled auto-merge so I can manually squash commits when all tests pass. |
|
I'm proposing a small modification (addition) to the API: Thank you @GilesBathgate for originally adding these, I got a lot of use out of them! :) |
The require function provides a means to get a value corresponding to the key, but if the value doesn't exist it will evaluate the lazy argument to create a new value, add this to the associative array and then return it.
The update function provides a means to perform additional operations during an aa create or update.
Corresponding documentation added in dlang/dlang.org#2343
Implementation notes
Traditionally the first example could be done like so:
I find this later code clunky and it requires two hashes/lookups. The proposed implementation adds support directly to
rt/aaA.dto avoid this. I should also point out that the allocation of anew Personin the example is trivial, but it might often be the case that the person instance is created by fetching a record from a db, or an Image loaded from disk, or a movie downloaded from the internet.Furthermore, the second example could be done like so:
While the this isn't any more clunky it requires two hashes/lookups.
Other languages
Several other languages have support for these operations:
C# has GetOrAdd, AddOrUpdate
https://msdn.microsoft.com/en-us/library/ee378677(v=vs.110).aspx
https://msdn.microsoft.com/en-us/library/ee378675(v=vs.110).aspx
Java has computeIfAbsent
https://docs.oracle.com/javase/8/docs/api/java/util/Map.html#computeIfAbsent-K-java.util.function.Function-
Python has setdefault
https://docs.python.org/2/library/stdtypes.html#dict.setdefault