www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Feature to get or add value to an associative array.

reply Giles Bathgate <giles.bathgate gmail.com> writes:
Hi,

I wanted a way to lazily insert a value into an associative 
array, and I am proposing a new function called getOrAdd in  
https://github.com/dlang/druntime/pull/2162

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

```
auto p = lookup.getOrAdd("giles", new Person);
p.eyeColor = Color.Brown;
//...
assert("giles" in lookup);
```

Corresponding documentation added in 
https://github.com/dlang/dlang.org/pull/2343

Implementation details:

Traditionally the above example could be done like so:
```
auto p = "giles" in lookup;
if (p is null) {
     p = new Person;
     lookup["giles"] = p;
}
p.eyeColor = Color.Brown;
//...
assert("giles" in lookup);
```

I find this later code clunky and it requires two hashes/lookups. 
The proposed implementation adds support directly to rt/aaA.d to 
avoid this. I should also point out that the allocation of a new 
Person in 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.
Apr 15 2018
next sibling parent reply Giles Bathgate <giles.bathgate gmail.com> writes:
On Sunday, 15 April 2018 at 22:52:47 UTC, Giles Bathgate wrote:
 I am proposing a new function called getOrAdd in
I posted details of the PR here because at least 2 people do not like the name getOrAdd (and one of those people being myself) Time for a bikeshed discussion...
Apr 15 2018
next sibling parent reply Jordan Wilson <wilsonjord gmail.com> writes:
On Sunday, 15 April 2018 at 22:55:41 UTC, Giles Bathgate wrote:
 On Sunday, 15 April 2018 at 22:52:47 UTC, Giles Bathgate wrote:
 I am proposing a new function called getOrAdd in
I posted details of the PR here because at least 2 people do not like the name getOrAdd (and one of those people being myself) Time for a bikeshed discussion...
I think Adding a Yes.add flag to the existing get makes sense, but property functions only have max 2 args. Soooo...getOrAdd or getOrSet would be my pick. Jordan
Apr 15 2018
next sibling parent Giles Bathgate <giles.bathgate gmail.com> writes:
On Monday, 16 April 2018 at 03:42:18 UTC, Jordan Wilson wrote:
 I think Adding a Yes.add flag to the existing get makes sense
That's an interesting idea. I forgot to mention that getOrAdd is a mutable function whereas get is inout, so that might also be problematic.
Apr 16 2018
prev sibling parent bauss <jj_1337 live.dk> writes:
On Monday, 16 April 2018 at 03:42:18 UTC, Jordan Wilson wrote:
 On Sunday, 15 April 2018 at 22:55:41 UTC, Giles Bathgate wrote:
 On Sunday, 15 April 2018 at 22:52:47 UTC, Giles Bathgate wrote:
 I am proposing a new function called getOrAdd in
I posted details of the PR here because at least 2 people do not like the name getOrAdd (and one of those people being myself) Time for a bikeshed discussion...
I think Adding a Yes.add flag to the existing get makes sense, but property functions only have max 2 args. Soooo...getOrAdd or getOrSet would be my pick. Jordan
I'm opposed to flags. I hate the whole "yes" "no" thing when it comes to such things, just like the "true" "false"... It's a no go for me. A function name that fits would be perfect in my opinion.
Apr 16 2018
prev sibling next sibling parent user1234 <user1234 12.nl> writes:
On Sunday, 15 April 2018 at 22:55:41 UTC, Giles Bathgate wrote:
 On Sunday, 15 April 2018 at 22:52:47 UTC, Giles Bathgate wrote:
 I am proposing a new function called getOrAdd in
I posted details of the PR here because at least 2 people do not like the name getOrAdd (and one of those people being myself) Time for a bikeshed discussion...
It's hard to name... I cant find better than "valueOrDefault"
Apr 16 2018
prev sibling parent reply Giles Bathgate <giles.bathgate gmail.com> writes:
On Sunday, 15 April 2018 at 22:55:41 UTC, Giles Bathgate wrote:
 Time for a bikeshed discussion...
I have had some thoughts about the name and would like to share my idea. Firstly to summarise here are the names that have been found/discussed so far: Java : computeIfAbsent Python : setdefault Rust : entry(key).or_insert_with Jordan Wilson : getOrAdd, getOrSet User1234 : valueOrDefault Nicholas Wilson : getOrInsert Steven Schveighoffer : getPtr, getRef, getInitialized Nick Treleaven : slot MrSmith : getOrCreate My latest idea is just a working title, but I think I prefer it to my original getOrAdd. Giles Bathgate : require My thinking is that if you `require` there to be a value in the associative array, then you should have the ability to add one iff it doesn't exist. It name also has parallels with the term in other programming languages to require a module, meaning to import it if it hasn't already been loaded.
Apr 18 2018
next sibling parent reply Jordan Wilson <wilsonjord gmail.com> writes:
On Wednesday, 18 April 2018 at 17:19:45 UTC, Giles Bathgate wrote:
 On Sunday, 15 April 2018 at 22:55:41 UTC, Giles Bathgate wrote:
 Time for a bikeshed discussion...
I have had some thoughts about the name and would like to share my idea. Firstly to summarise here are the names that have been found/discussed so far: Java : computeIfAbsent Python : setdefault Rust : entry(key).or_insert_with Jordan Wilson : getOrAdd, getOrSet User1234 : valueOrDefault Nicholas Wilson : getOrInsert Steven Schveighoffer : getPtr, getRef, getInitialized Nick Treleaven : slot MrSmith : getOrCreate My latest idea is just a working title, but I think I prefer it to my original getOrAdd. Giles Bathgate : require My thinking is that if you `require` there to be a value in the associative array, then you should have the ability to add one iff it doesn't exist. It name also has parallels with the term in other programming languages to require a module, meaning to import it if it hasn't already been loaded.
Thinking seems sound, although having a name starting with "get" does have the advantage of being more related to the existing get. Weirdly, in all cases if I replace "Or" with "Else", it seems to read easier for me... getElseAdd, getElseCreate, etc. Ultimately I'm not too bothered with any name really, I'm just looking forward to using it eventually :-) Jordan
Apr 18 2018
parent reply Giles Bathgate <giles.bathgate gmail.com> writes:
On Wednesday, 18 April 2018 at 21:04:53 UTC, Jordan Wilson wrote:
 Thinking seems sound, although having a name starting with 
 "get" does have the advantage of being more related to the 
 existing get.
Ah yes, good point. I think now we've had the discussion about other use cases though that ultimately there might need to be an "atomic" AddOrUpdate style function too, which I was thinking could just be called 'update'. The necessity to keep them all starting with get seemed less important.
Apr 19 2018
parent Dominikus Dittes Scherkl <dominikus.scherkl continental-corporation.com> writes:
On Thursday, 19 April 2018 at 08:20:02 UTC, Giles Bathgate wrote:
 On Wednesday, 18 April 2018 at 21:04:53 UTC, Jordan Wilson 
 wrote:
 Thinking seems sound, although having a name starting with 
 "get" does have the advantage of being more related to the 
 existing get.
Ah yes, good point. I think now we've had the discussion about other use cases though that ultimately there might need to be an "atomic" AddOrUpdate style function too, which I was thinking could just be called 'update'. The necessity to keep them all starting with get seemed less important.
How about something like "forceGet" to make clear you will get it, even if it wasn't there before.
Apr 20 2018
prev sibling parent reply Jonathan M Davis <newsgroup.d jmdavisprog.com> writes:
On Wednesday, April 18, 2018 17:19:45 Giles Bathgate via Digitalmars-d 
wrote:
 On Sunday, 15 April 2018 at 22:55:41 UTC, Giles Bathgate wrote:
 Time for a bikeshed discussion...
I have had some thoughts about the name and would like to share my idea. Firstly to summarise here are the names that have been found/discussed so far: Java : computeIfAbsent Python : setdefault Rust : entry(key).or_insert_with Jordan Wilson : getOrAdd, getOrSet User1234 : valueOrDefault Nicholas Wilson : getOrInsert Steven Schveighoffer : getPtr, getRef, getInitialized Nick Treleaven : slot MrSmith : getOrCreate My latest idea is just a working title, but I think I prefer it to my original getOrAdd. Giles Bathgate : require My thinking is that if you `require` there to be a value in the associative array, then you should have the ability to add one iff it doesn't exist. It name also has parallels with the term in other programming languages to require a module, meaning to import it if it hasn't already been loaded.
Out of all of those, I _really_ hope that you don't go with require. It sounds like the sort of thing that you'd get in a library having to do with unit testing or contracts and gives no indication whatsoever as to what it does. The only one in that list that seems similarly opaque is slot. Those names say nothing about either getting a value or adding / inserting one. Every other name at least gives some clue as to what the function does. If I were adding it, and we already had get, I would just make it an optional argument to get, which wouldn't necessarily be clear about inserting, but it would at least be clear about getting. However, since we used overloaded operators, there's no get. Another option would be getOrInit, though I agree that they're all kind of ugly. Either way, IMHO, getOrAdd is infinitely better than require. - Jonathan M Davis
Apr 19 2018
parent Giles Bathgate <giles.bathgate gmail.com> writes:
On Friday, 20 April 2018 at 02:12:37 UTC, Jonathan M Davis wrote:
 Out of all of those, I _really_ hope that you don't go with 
 require. It sounds like the sort of thing that you'd get in a 
 library having to do with unit testing or contracts and gives 
 no indication whatsoever as to what it does.
Yes I know. I gave up on trying to find a single verb or runTogetherWords that would describe exactly what it does, and instead opted for something that I like, and added documentation to describe what it does. I don't think I will find a name that everybody likes. But I am open to more suggestions as its probably the easiest aspect of the PR to change.
Apr 20 2018
prev sibling next sibling parent reply JN <666total wp.pl> writes:
On Sunday, 15 April 2018 at 22:52:47 UTC, Giles Bathgate wrote:
 Hi,

 I wanted a way to lazily insert a value into an associative 
 array, and I am proposing a new function called getOrAdd in  
 https://github.com/dlang/druntime/pull/2162
I am not sure if it's a good idea to combine accessor and insertion in a same method call. Is it really such a big overhead to do "if in assocarray then take else create new"? It's only one additional "in", but makes the code more explicit and clear. I think in most cases, you will want to check if you are dealing with a fetched object or a default created one, so that will complicate the function even further.
Apr 16 2018
parent reply Giles Bathgate <giles.bathgate gmail.com> writes:
On Monday, 16 April 2018 at 12:41:07 UTC, JN wrote:
 It's only one additional "in", but makes the code more explicit 
 and clear. I think in most cases, you will want to check if you 
 are dealing with a fetched object or a default created one, so 
 that will complicate the function even further.
You can still use a combination of `in` and the update syntax if you want, this doesn't take that away. In the future, for a concurrent implementation of associative arrays, a way of getting or adding an element as an atomic operation becomes more important. Either way the beauty of hashed based lookups is that they average O(1), seems a shame to double that up for no reason ;)
Apr 16 2018
parent reply Cym13 <cpicard openmailbox.org> writes:
On Monday, 16 April 2018 at 18:59:54 UTC, Giles Bathgate wrote:
 On Monday, 16 April 2018 at 12:41:07 UTC, JN wrote:
 It's only one additional "in", but makes the code more 
 explicit and clear. I think in most cases, you will want to 
 check if you are dealing with a fetched object or a default 
 created one, so that will complicate the function even further.
You can still use a combination of `in` and the update syntax if you want, this doesn't take that away. In the future, for a concurrent implementation of associative arrays, a way of getting or adding an element as an atomic operation becomes more important. Either way the beauty of hashed based lookups is that they average O(1), seems a shame to double that up for no reason ;)
"in" returns a pointer to the object, there'es not double lookup necessary: // if we don't know .get(key, default) exists auto ptr = key in aa; auto value = ptr ? *ptr : default; // to set default value on the fly auto value = ptr ? *ptr : *ptr = default; is a new flag/method really that necessary? In my experience if you have trouble naming it you haven't found its true purpose yet.
Apr 16 2018
next sibling parent Nicholas Wilson <iamthewilsonator hotmail.com> writes:
On Tuesday, 17 April 2018 at 00:04:32 UTC, Cym13 wrote:
     auto value = ptr ? *ptr : *ptr = default;
That is going to crash if ptr is null.
 is a new flag/method really that necessary? In my experience if 
 you have trouble naming it you haven't found its true purpose 
 yet.
I think `getOrInsert` is a good name for this function
Apr 16 2018
prev sibling parent reply Giles Bathgate <giles.bathgate gmail.com> writes:
On Tuesday, 17 April 2018 at 00:04:32 UTC, Cym13 wrote:
 "in" returns a pointer to the object, there'es not double 
 lookup necessary:

     // if we don't know .get(key, default) exists
     auto ptr   = key in aa;
     auto value = ptr ? *ptr : default;
This doesn't work. `in` returns null when the key doesn't exist. So you are de-referencing null: https://github.com/dlang/druntime/blob/master/src/rt/aaA.d#L417
 is a new flag/method really that necessary? In my experience if 
 you have trouble naming it you haven't found its true purpose 
 yet.
My personal reason for not liking it is because the same name is used by Microsoft: https://msdn.microsoft.com/en-us/library/ee378676(v=vs.110).aspx I think I have clearly explained its purpose.
Apr 17 2018
parent reply Giles Bathgate <giles.bathgate gmail.com> writes:
On Tuesday, 17 April 2018 at 07:40:23 UTC, Giles Bathgate wrote:
 My personal reason for not liking it is because the same name 
 is used by Microsoft
The java name for such a function is `computeIfAbsent` https://docs.oracle.com/javase/8/docs/api/java/util/Map.html#computeIfAbsent-K-java.util.function.Function-
Apr 17 2018
parent Giles Bathgate <giles.bathgate gmail.com> writes:
On Tuesday, 17 April 2018 at 09:21:14 UTC, Giles Bathgate wrote:
 The java name for such a function is `computeIfAbsent`
More names from other languages, this time python: https://docs.python.org/3/library/stdtypes.html#dict.setdefault It's horrid though, a method called `set` that returns a value? Maybe I am biased against anything pythonic ;)
Apr 17 2018
prev sibling next sibling parent Steven Schveighoffer <schveiguy yahoo.com> writes:
On 4/15/18 6:52 PM, Giles Bathgate wrote:

 I find this later code clunky and it requires two hashes/lookups. The 
 proposed implementation adds support directly to rt/aaA.d to avoid this. 
 I should also point out that the allocation of a new Person in 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.
I think this is a great addition. I've always disliked the double-lookup requirement for ensuring a key was initialized. I'll note that C++ gets around this by always initializing on any access. The name "getOrAdd" is a bit mechanical sounding. The real reason you want this is to ensure that key's value is initialized. ensureInitialized is long. get is already taken (if get weren't already taken, I'd suggest that name). Others have suggested using flags, but I'll note to them, `Flag` and `Yes` are part of std.typecons, and not accessible here. getWithDefault sounds reasonable but probably would be confusing with the current `get` overload. Maybe getInitialized? I don't know that either of these are giant leaps ahead of getOrAdd. Another possibility is getPtr. get currently gets a value, or returns a default value if it doesn't exist. But getPtr would return a pointer to the value, ensuring it's initialized. In any case, thumbs up to the concept, and I'm ambivalent on the name. -Steve
Apr 17 2018
prev sibling next sibling parent reply Nick Treleaven <nick geany.org> writes:
On Sunday, 15 April 2018 at 22:52:47 UTC, Giles Bathgate wrote:
 The 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.

 auto p = lookup.getOrAdd("giles", new Person);
Thanks for making this pull, I've thought about solving this before. I think the function needs to provide a way to tell if the value was already present. I also think it's more ergonomic not to have to use a lazy argument, and probably more efficient, so I had in mind: Value* slot(AA aa, K key, scope bool* inserted = null); bool inserted; auto p = aa.slot("key", &inserted); if (inserted) { ... *p = new Value(...); } // use *p This pattern needs a pointer to be returned, instead of using `ref`. Note that `&inserted` is valid in safe code, but only with -dip1000. I called the function `slot` because it always returns the address of the slot which the value is stored in.
Apr 17 2018
next sibling parent reply Giles Bathgate <giles.bathgate gmail.com> writes:
On Tuesday, 17 April 2018 at 16:18:32 UTC, Nick Treleaven wrote:
 I called the function `slot` because it always returns the 
 address of the slot which the value is stored in.
I like the name. I think your version is quite low level which ultimately provides more power at the expense of making the callee code less clean. I am not sure with D which of those two aspects is preferred. Perhaps both functions could be provided? What is the use case for knowing whether a value was inserted.
Apr 17 2018
parent reply MrSmith <mrsmith33 yandex.ru> writes:
On Tuesday, 17 April 2018 at 17:27:02 UTC, Giles Bathgate wrote:
 I like the name. I think your version is quite low level which 
 ultimately provides more power at the expense of making the 
 callee code less clean. I am not sure with D which of those two 
 aspects is preferred. Perhaps both functions could be provided? 
 What is the use case for knowing whether a value was inserted.
You may need to perform extra logic when value is inserted in the container and something else when value already existed. I have a special version of function for that: Value* getOrCreate(Key key, out bool wasCreated, Value default_value = Value.init) here is an example of usage: https://github.com/MrSmith33/voxelman/blob/master/plugins/voxelman/entity/entityobservermanager.d#L33
Apr 17 2018
parent Giles Bathgate <giles.bathgate gmail.com> writes:
On Tuesday, 17 April 2018 at 19:33:16 UTC, MrSmith wrote:
 You may need to perform extra logic when value is inserted in 
 the container and something else when value already existed.
Fair enough, I will consider adding this and some tests to the PR, a function overload seems like the way to go.
Apr 17 2018
prev sibling parent reply Steven Schveighoffer <schveiguy yahoo.com> writes:
On 4/17/18 12:18 PM, Nick Treleaven wrote:
 On Sunday, 15 April 2018 at 22:52:47 UTC, Giles Bathgate wrote:
 The 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.

 auto p = lookup.getOrAdd("giles", new Person);
Thanks for making this pull, I've thought about solving this before. I think the function needs to provide a way to tell if the value was already present.
Not as straightforward, but it can be done: bool inserted = false; auto p = aa.getOrAdd("key", {inserted = true; return new Person; }); Note that most of the time, the only reason you need to know it's new is to initialize it. But the lazy parameter takes care of that for you.
 I also think it's more ergonomic not to have to use a 
 lazy argument, and probably more efficient, so I had in mind:
 
 Value* slot(AA aa, K key, scope bool* inserted = null);
I like the name slot. I'm less enthused about the extra machinery needed for initializing the value. Why do you think it's less efficient to use a lazy parameter?
 This pattern needs a pointer to be returned, instead of using `ref`. 
 Note that `&inserted` is valid in  safe code, but only with -dip1000. I 
 called the function `slot` because it always returns the address of the 
 slot which the value is stored in.
Returning ref makes more sense to me -- you are never going to return null. -Steve
Apr 17 2018
next sibling parent reply Giles Bathgate <giles.bathgate gmail.com> writes:
On Tuesday, 17 April 2018 at 20:49:30 UTC, Steven Schveighoffer 
wrote:
 Not as straightforward, but it can be done:

 bool inserted = false;
 auto p = aa.getOrAdd("key", {inserted = true; return new 
 Person; });
Yes, I like that approach. I don't want to bloat the feature at this stage, although there is nothing stopping adding an overload later. I agree returning ref makes sense since we never return null. Consequently, I am leaning toward thinking it should be called getOrCreate now though. Rust calls its version of this function `or_insert_with` (blegh) https://doc.rust-lang.org/std/collections/hash_map/enum.Entry.html#method.or_insert_with
Apr 17 2018
parent Giles Bathgate <giles.bathgate gmail.com> writes:
On Tuesday, 17 April 2018 at 21:40:55 UTC, Giles Bathgate wrote:
 Rust calls its version of this function `or_insert_with` (blegh)
Of course, a rustic API could be built atop this PR: template entry(K, V) { static struct Entry { alias get this; V[K] aa; K key; V get() { return aa[key]; } V orInsert() { return aa.getOrAdd(key); } V orInsertWith(lazy V value = V.init) { return aa.getOrAdd(key, value); } } Entry entry(ref V[K] aa, K key) { return Entry(aa,key); } } void main() { class C{} C[string] aa; auto v1 = aa.entry("foo"); auto v2 = aa.entry("bar").orInsert(); auto v3 = aa.entry("baz").orInsertWith(new C); } But I think, this would be out of scope.
Apr 17 2018
prev sibling next sibling parent reply Nick Treleaven <nick geany.org> writes:
On Tuesday, 17 April 2018 at 20:49:30 UTC, Steven Schveighoffer 
wrote:
 Why do you think it's less efficient to use a lazy parameter?
Wouldn't an extra function call have to happen, at least in some cases?
 This pattern needs a pointer to be returned, instead of using 
 `ref`. Note that `&inserted` is valid in  safe code, but only 
 with -dip1000. I called the function `slot` because it always 
 returns the address of the slot which the value is stored in.
Returning ref makes more sense to me -- you are never going to return null.
How do you implement this if the function returns with ref: bool inserted; auto p = aa.slot("key", &inserted); if (inserted) { ... // set *p } else { // read *p ... // set *p } There is a common basic use case for this - counting the occurrence of a key in a data set. If the key doesn't exist, initialize and insert the value. *Iff* it does exist, increment the value - I don't think you can do this without functional contortions with your ref return. (A side benefit is that returning a pointer is consistent with `in`.)
Apr 18 2018
next sibling parent reply Giles Bathgate <giles.bathgate gmail.com> writes:
On Wednesday, 18 April 2018 at 09:41:48 UTC, Nick Treleaven wrote:
 How do you implement this if the function returns with ref:
I understand where you are coming from, but I am not sure it is appropriate to shoehorn every use case into one api. I think actually what you are describing here is the AddOrUpdate style method https://msdn.microsoft.com/en-us/library/ee378665(v=vs.110).aspx Perhaps the implementation could be: void createOrUpdate(K, V)(ref V[K] aa, K key, V delegate() create, void delegate(V*) update); C newc; aa.createOrUpdate("key", { /* set *p */ newc = new C; return newc; }, (C* u){ // read *p newc = *u; // set *p *u = new C; }); assert(aa["key"] == newc); ¯\_(ツ)_/¯
Apr 18 2018
parent reply Nick Treleaven <nick geany.org> writes:
On Wednesday, 18 April 2018 at 16:04:13 UTC, Giles Bathgate wrote:
 I understand where you are coming from, but I am not sure it is 
 appropriate to shoehorn every use case into one api.
It is not shoehorning, the difference here is just returning by ref vs pointer. I understand that, as the pointer address is not needed by the caller, it is cleaner to use ref. The problem is that local refs are not supported by D, and so I would need to do this (at least in safe code): import std.functional : unaryFun; bool ins; aa.update("key", {ins = true; return 1;}).unaryFun!((v){ if (ins) v++; }); This seems pretty convoluted. It could be made a bit better if D supported UFCS for lambdas, then unaryFun wouldn't be needed. BTW, I like the name `update`.
     void createOrUpdate(K, V)(ref V[K] aa, K key, V delegate() 
 create, void delegate(V*) update);
Again, would the delegate calls always be inlined, in all cases? I think having a low-level API in druntime is appropriate, it's a runtime library after all.
Apr 20 2018
parent Giles Bathgate <giles.bathgate gmail.com> writes:
On Friday, 20 April 2018 at 08:37:59 UTC, Nick Treleaven wrote:
 Again, would the delegate calls always be inlined, in all cases?

 I think having a low-level API in druntime is appropriate, it's 
 a runtime library after all.
So the low-level API you are requesting is part of the pull request, the low-level API is called _aaGetX, whereas the previous API was _aaGetY, the new API adds a boolean out parameter `found` and if you really wanted to you could do this: ----- extern (C) void* _aaGetX(void** paa, const TypeInfo_AssociativeArray ti, in size_t valuesize, in void* pkey, out bool found) pure nothrow; V* slot(K, V)(ref V[K] aa, K key, out bool found) { return cast(V*) _aaGetX(cast(void**)&aa, typeid(V[K]), V.sizeof, &key, found); } ----- I am also benchmarking the `update` function I talked about previously. https://gist.github.com/GilesBathgate/8409b5889ebb7b1302627c50f342a28b The results seem to indicate that there isn't much in it performance wise. test1 [56 ms, 377 µs, and 4 hnsecs] test2 [56 ms, 262 µs, and 2 hnsecs] I can't fathom why the delegates version is faster, but I am just putting it down to luck ;)
Apr 20 2018
prev sibling parent reply ag0aep6g <anonymous example.com> writes:
On 04/18/2018 11:41 AM, Nick Treleaven wrote:
 How do you implement this if the function returns with ref:
 
 bool inserted;
 auto p = aa.slot("key", &inserted);
 if (inserted) {
    ...
    // set *p
 }
 else {
    // read *p
    ...
    // set *p
 }
You can get a pointer from the ref return: Value* p = &aa.slot("key", { inserted = true; return Value.init; }); Then do with the pointer whatever you want.
Apr 18 2018
parent reply Nick Treleaven <nick geany.org> writes:
On Wednesday, 18 April 2018 at 16:47:50 UTC, ag0aep6g wrote:
 You can get a pointer from the ref return:

     Value* p = &aa.slot("key", { inserted = true; return 
 Value.init; });
This is not safe, even with -dip1000: Error: cannot take address of ref return of f() in safe function main
Apr 20 2018
next sibling parent reply ag0aep6g <anonymous example.com> writes:
On 04/20/2018 10:24 AM, Nick Treleaven wrote:
 On Wednesday, 18 April 2018 at 16:47:50 UTC, ag0aep6g wrote:
 You can get a pointer from the ref return:

     Value* p = &aa.slot("key", { inserted = true; return Value.init; });
This is not safe, even with -dip1000: Error: cannot take address of ref return of f() in safe function main
Hm. Do you know why that's not allowed? I can't see how it would be less safe than returning a pointer, and I can't find a mention of it in DIP 1000 [1]. Maybe it's just a case of incomplete/buggy implementation? [1] https://github.com/dlang/DIPs/blob/master/DIPs/DIP1000.md
Apr 20 2018
parent reply Jonathan M Davis <newsgroup.d jmdavisprog.com> writes:
On Friday, April 20, 2018 10:56:37 ag0aep6g via Digitalmars-d wrote:
 On 04/20/2018 10:24 AM, Nick Treleaven wrote:
 On Wednesday, 18 April 2018 at 16:47:50 UTC, ag0aep6g wrote:
 You can get a pointer from the ref return:

     Value* p = &aa.slot("key", { inserted = true; return Value.init;
 });
This is not safe, even with -dip1000: Error: cannot take address of ref return of f() in safe function main
Hm. Do you know why that's not allowed? I can't see how it would be less safe than returning a pointer, and I can't find a mention of it in DIP 1000 [1]. Maybe it's just a case of incomplete/buggy implementation? [1] https://github.com/dlang/DIPs/blob/master/DIPs/DIP1000.md
The compiler assumes that a pointer is valid when determining whether code is safe. It then disallows operations that it can't guarantee are safe even if the pointer is valid, and it disallows taking the address in cases where it can't guarantee that that's safe. Basically, it verifies the safety of pointers when they're created and then assumes that they're safe after that. So, passing around a pointer is perfectly safe, whereas taking the address of a ref return value to get a pointer is not. - Jonathan M Davis
Apr 20 2018
parent ag0aep6g <anonymous example.com> writes:
On Friday, 20 April 2018 at 09:24:26 UTC, Jonathan M Davis wrote:
 The compiler assumes that a pointer is valid when determining 
 whether code is  safe.
[...]
 Basically, it verifies the  safety of pointers when they're 
 created and then assumes that they're  safe after that.
Can't it do the same for ref returns? Unsafe stuff like returning a ref to a local is already disallowed, just like returning a pointer to a local. So the compiler checks the safety on creation, at least in some cases.
 So, passing around a pointer is perfectly  safe, whereas taking 
 the address of a ref return value to get a pointer is not.
It's still not clear to me how taking the address of a ref return is necessarily less safe than using a returned pointer. Do you maybe have an example in code that shows the difference? It seems to me that refs and pointers are very much equivalent. I should always be able to turn one into the other.
Apr 20 2018
prev sibling parent reply Steven Schveighoffer <schveiguy yahoo.com> writes:
On 4/20/18 4:24 AM, Nick Treleaven wrote:
 On Wednesday, 18 April 2018 at 16:47:50 UTC, ag0aep6g wrote:
 You can get a pointer from the ref return:

     Value* p = &aa.slot("key", { inserted = true; return Value.init; });
This is not safe, even with -dip1000: Error: cannot take address of ref return of f() in safe function main
Hm... I would have expected it to work in dip1000. Hard for me to understand what dip1000 is doing, but it seems like an omission: safe ref int foo(return ref int x) { return x; } void main() safe { int x; scope int *xp = &x; // OK with dip1000 xp = foo(x); // Error, even though it's exactly the same } And in this case, it doesn't even need to be scope, as it's GC data. How does one communicate that the ref return is of GC data and can be escaped as much as you want? -Steve
Apr 20 2018
parent reply Uknown <sireeshkodali1 gmail.com> writes:
On Friday, 20 April 2018 at 14:02:17 UTC, Steven Schveighoffer 
wrote:
 On 4/20/18 4:24 AM, Nick Treleaven wrote:
 On Wednesday, 18 April 2018 at 16:47:50 UTC, ag0aep6g wrote:
 You can get a pointer from the ref return:

     Value* p = &aa.slot("key", { inserted = true; return 
 Value.init; });
This is not safe, even with -dip1000: Error: cannot take address of ref return of f() in safe function main
Hm... I would have expected it to work in dip1000. Hard for me to understand what dip1000 is doing, but it seems like an omission: safe ref int foo(return ref int x) { return x; } void main() safe { int x; scope int *xp = &x; // OK with dip1000 xp = foo(x); // Error, even though it's exactly the same } And in this case, it doesn't even need to be scope, as it's GC data. How does one communicate that the ref return is of GC data and can be escaped as much as you want? -Steve
The error seems to be about type mismatching, it works if you use auto. I don't see how you could convert the `ref int` to `int *` https://run.dlang.io/is/eIJMIK
Apr 20 2018
parent reply Steven Schveighoffer <schveiguy yahoo.com> writes:
On 4/20/18 10:34 AM, Uknown wrote:
 On Friday, 20 April 2018 at 14:02:17 UTC, Steven Schveighoffer wrote:
 On 4/20/18 4:24 AM, Nick Treleaven wrote:
 On Wednesday, 18 April 2018 at 16:47:50 UTC, ag0aep6g wrote:
 You can get a pointer from the ref return:

     Value* p = &aa.slot("key", { inserted = true; return Value.init; 
 });
This is not safe, even with -dip1000: Error: cannot take address of ref return of f() in safe function main
Hm... I would have expected it to work in dip1000. Hard for me to understand what dip1000 is doing, but it seems like an omission: safe ref int foo(return ref int x) { return x; } void main() safe {    int x;    scope int *xp = &x; // OK with dip1000    xp = foo(x); // Error, even though it's exactly the same } And in this case, it doesn't even need to be scope, as it's GC data. How does one communicate that the ref return is of GC data and can be escaped as much as you want?
The error seems to be about type mismatching, it works if you use auto. I don't see how you could convert the `ref int` to `int *` https://run.dlang.io/is/eIJMIK
Sorry, it was a typo. In my real code I have: xp = &(foo(x)); That's what I get for hand-typing instead of copy-pasting :) -Steve
Apr 20 2018
parent Uknown <sireeshkodali1 gmail.com> writes:
On Friday, 20 April 2018 at 15:00:39 UTC, Steven Schveighoffer 
wrote:
 On 4/20/18 10:34 AM, Uknown wrote:
 On Friday, 20 April 2018 at 14:02:17 UTC, Steven Schveighoffer 
 wrote:
 On 4/20/18 4:24 AM, Nick Treleaven wrote:
 On Wednesday, 18 April 2018 at 16:47:50 UTC, ag0aep6g wrote:
 [...]
Sorry, it was a typo. In my real code I have: xp = &(foo(x)); That's what I get for hand-typing instead of copy-pasting :) -Steve
It seems like a bug. Changing foo to take and return an `int *` works as expected https://run.dlang.io/is/3NYXYv
Apr 20 2018
prev sibling parent reply Steven Schveighoffer <schveiguy yahoo.com> writes:
On 4/17/18 4:49 PM, Steven Schveighoffer wrote:
 On 4/17/18 12:18 PM, Nick Treleaven wrote:
 Thanks for making this pull, I've thought about solving this before. I 
 think the function needs to provide a way to tell if the value was 
 already present.
Not as straightforward, but it can be done: bool inserted = false; auto p = aa.getOrAdd("key", {inserted = true; return new Person; });
Let me say I was surprised that this doesn't actually work. An in-line lambda will NOT work as a lazy parameter, even though that's EXACTLY what a lazy parameter is implemented as! And variadic lazy parameters are explicitly typed this way. Has that ever worked? I could have sworn it did... -Steve
Apr 20 2018
next sibling parent reply Jonathan M Davis <newsgroup.d jmdavisprog.com> writes:
On Friday, April 20, 2018 16:35:43 Steven Schveighoffer via Digitalmars-d 
wrote:
 On 4/17/18 4:49 PM, Steven Schveighoffer wrote:
 On 4/17/18 12:18 PM, Nick Treleaven wrote:
 Thanks for making this pull, I've thought about solving this before. I
 think the function needs to provide a way to tell if the value was
 already present.
Not as straightforward, but it can be done: bool inserted = false; auto p = aa.getOrAdd("key", {inserted = true; return new Person; });
Let me say I was surprised that this doesn't actually work. An in-line lambda will NOT work as a lazy parameter, even though that's EXACTLY what a lazy parameter is implemented as! And variadic lazy parameters are explicitly typed this way. Has that ever worked? I could have sworn it did...
I'm not sure. I mucked around with lazy like this when I was originally working on stuff like assertThrown and assertPred 7 or 8 years ago, but I've done _very_ little with lazy since then. My gut reaction is that it worked, but it might not have. If you add parens after it so that you call it, it does work, since then the result is the correct type. And if you ignore the exact details of how lazy is implemented and consider that it's supposed to take an expression that evaluates to a specific type, a lambda doesn't match that unless it's called. So, it does make sense from that perspective and is likely why it works the way it does. The compiler would basically have to special-case delegates to accept stuff like lambdas when the type of the lazy parameter is not a delegate. And if it _did_ special-case it, then things might get interesting if you actually had a lazy parameter that was a delegate. So, I don't know if it should work or not, but the workaround is pretty simple - just add parens to call it. - Jonathan M Davis
Apr 20 2018
parent reply Steven Schveighoffer <schveiguy yahoo.com> writes:
On 4/20/18 5:40 PM, Jonathan M Davis wrote:
 On Friday, April 20, 2018 16:35:43 Steven Schveighoffer via Digitalmars-d
 wrote:
 On 4/17/18 4:49 PM, Steven Schveighoffer wrote:
 On 4/17/18 12:18 PM, Nick Treleaven wrote:
 Thanks for making this pull, I've thought about solving this before. I
 think the function needs to provide a way to tell if the value was
 already present.
Not as straightforward, but it can be done: bool inserted = false; auto p = aa.getOrAdd("key", {inserted = true; return new Person; });
Let me say I was surprised that this doesn't actually work. An in-line lambda will NOT work as a lazy parameter, even though that's EXACTLY what a lazy parameter is implemented as! And variadic lazy parameters are explicitly typed this way. Has that ever worked? I could have sworn it did...
I'm not sure. I mucked around with lazy like this when I was originally working on stuff like assertThrown and assertPred 7 or 8 years ago, but I've done _very_ little with lazy since then. My gut reaction is that it worked, but it might not have. If you add parens after it so that you call it, it does work, since then the result is the correct type. And if you ignore the exact details of how lazy is implemented and consider that it's supposed to take an expression that evaluates to a specific type, a lambda doesn't match that unless it's called. So, it does make sense from that perspective and is likely why it works the way it does.
But, it *is* a delegate. literally, that's what gets implemented (and it has to be that way). I suppose if it's inlined, the compiler could take some leeway, but that is the same with a delegate literal passed to a function that takes a delegate anyway. The fact that lazy variadics are explicitly delegates makes this even more annoying. I wish we could have the best of both worlds (non-ugly calls inside the function, and easy binding to whatever you want at the call side). I'm not even sure why lazy variadics are the way they are, I can instantly think of a better syntax for them.
 The compiler would basically have to
 special-case delegates to accept stuff like lambdas when the type of the
 lazy parameter is not a delegate. And if it _did_ special-case it, then
 things might get interesting if you actually had a lazy parameter that was a
 delegate.
This is pretty easy, does your delegate return a delegate or an int? ;) This is a super-solvable problem, I'm just surprised it wasn't this way before, I could have sworn I've done this in the past.
 So, I don't know if it should work or not, but the workaround is
 pretty simple - just add parens to call it.
This results in a double delegate call -- the compiler makes a delegate that calls your delegate. Not optimal, but yes it does work. -Steve
Apr 20 2018
parent Jonathan M Davis <newsgroup.d jmdavisprog.com> writes:
On Friday, April 20, 2018 19:36:57 Steven Schveighoffer via Digitalmars-d 
wrote:
 On 4/20/18 5:40 PM, Jonathan M Davis wrote:
 On Friday, April 20, 2018 16:35:43 Steven Schveighoffer via
 Digitalmars-d

 wrote:
 On 4/17/18 4:49 PM, Steven Schveighoffer wrote:
 On 4/17/18 12:18 PM, Nick Treleaven wrote:
 Thanks for making this pull, I've thought about solving this before.
 I
 think the function needs to provide a way to tell if the value was
 already present.
Not as straightforward, but it can be done: bool inserted = false; auto p = aa.getOrAdd("key", {inserted = true; return new Person; });
Let me say I was surprised that this doesn't actually work. An in-line lambda will NOT work as a lazy parameter, even though that's EXACTLY what a lazy parameter is implemented as! And variadic lazy parameters are explicitly typed this way. Has that ever worked? I could have sworn it did...
I'm not sure. I mucked around with lazy like this when I was originally working on stuff like assertThrown and assertPred 7 or 8 years ago, but I've done _very_ little with lazy since then. My gut reaction is that it worked, but it might not have. If you add parens after it so that you call it, it does work, since then the result is the correct type. And if you ignore the exact details of how lazy is implemented and consider that it's supposed to take an expression that evaluates to a specific type, a lambda doesn't match that unless it's called. So, it does make sense from that perspective and is likely why it works the way it does.
But, it *is* a delegate. literally, that's what gets implemented (and it has to be that way). I suppose if it's inlined, the compiler could take some leeway, but that is the same with a delegate literal passed to a function that takes a delegate anyway.
If we want to make the compiler accept it, then I'm not necessarily against it, but arguably, the fact that it uses a delegate is an implementation detail. In principle, what you're doing with a lazy parameter is just saying that the expression being passed to it isn't immediately evaluated. Having it then accept expressions that don't result in the type of the lazy parameter is then pretty weird.
 The fact that lazy variadics are explicitly delegates makes this even
 more annoying. I wish we could have the best of both worlds (non-ugly
 calls inside the function, and easy binding to whatever you want at the
 call side). I'm not even sure why lazy variadics are the way they are, I
 can instantly think of a better syntax for them.
Well, this thread is the first that I've heard of "lazy variadics." Until I saw your other post about them, I assumed that you meant something like auto foo(Args...)(lazy Args args) { ... } I would haven't have assumed that void foo(int delegate()[] dgs...) { dgs[0](); } had anything to do with lazy, and the fact that it works with anything that doesn't implictly convert to a delegate seems like a bug to me, especially when void foo(int delegate() dg) { dg(); } doesn't compile when it's passed an int. IMHO, they should be consistent.
 The compiler would basically have to
 special-case delegates to accept stuff like lambdas when the type of the
 lazy parameter is not a delegate. And if it _did_ special-case it, then
 things might get interesting if you actually had a lazy parameter that
 was a delegate.
This is pretty easy, does your delegate return a delegate or an int? ;) This is a super-solvable problem, I'm just surprised it wasn't this way before, I could have sworn I've done this in the past.
Of course it's solvable, but that doesn't mean that it's ultimately a good idea. It does have corner cases that may or may not be a problem. e.g. something like void foo(lazy int delegate() dg) { } could be a problem if void foo(lazy int i) { } accepts delegates whose result is an int. I'm not necessarily against making it possible, but I would consider the fact that lazy is implemented via a delegate to be an implementation detail of lazy, and if lazy parameters started accepting lambdas whose result was the right type, then that means that auto foo(string str) {...} and auto foo(lazy string str) {...} would accept different arguments even though their parameters are the same type, and I question that that's a good idea. I'm sure that it would work on some level, and the corner cases probably wouldn't matter often, but it seems much cleaner to me if lazy has no effect on what is accepted and just has an effect on whether that code is run immediately.
 So, I don't know if it should work or not, but the workaround is
 pretty simple - just add parens to call it.
This results in a double delegate call -- the compiler makes a delegate that calls your delegate. Not optimal, but yes it does work.
Sure, it's sub-optimal, and I would hope that the compiler would optimize it accordingly (though it wouldn't surprise me if it doesn't), but it works without having to change anything, and it would presumably continue to work even if we did change the behavior of lazy to accept lambdas whose return type was the type of the parameter. - Jonathan M Davis
Apr 20 2018
prev sibling parent reply Giles Bathgate <giles.bathgate gmail.com> writes:
On Friday, 20 April 2018 at 20:35:43 UTC, Steven Schveighoffer 
wrote:

 Let me say I was surprised that this doesn't actually work. An 
 in-line lambda will NOT work as a lazy parameter, even though 
 that's EXACTLY what a lazy parameter is implemented as! And 
 variadic lazy parameters are explicitly typed this way.
I kind of expected it to work too. However, I just created a template to allow this to work with my PR. https://github.com/dlang/druntime/pull/2162/files#diff-a68e58fcf0de5aa198fcaceafe4e8cf9R2344 Getting it right took a few head-scratchers, but I put that down to my lack of experience with D ;)
Apr 20 2018
parent reply Jonathan M Davis <newsgroup.d jmdavisprog.com> writes:
On Friday, April 20, 2018 22:03:04 Giles Bathgate via Digitalmars-d wrote:
 On Friday, 20 April 2018 at 20:35:43 UTC, Steven Schveighoffer

 wrote:
 Let me say I was surprised that this doesn't actually work. An
 in-line lambda will NOT work as a lazy parameter, even though
 that's EXACTLY what a lazy parameter is implemented as! And
 variadic lazy parameters are explicitly typed this way.
I kind of expected it to work too. However, I just created a template to allow this to work with my PR. https://github.com/dlang/druntime/pull/2162/files#diff-a68e58fcf0de5aa198f caceafe4e8cf9R2344 Getting it right took a few head-scratchers, but I put that down to my lack of experience with D ;)
Honestly, I think that it's a terrible idea to special-case it like that. If we want to argue for making it work in the language, that's fine, but if we special-case it like this, then it will work with some functions that have lazy parameters and not others, and the result will be confusing. Besides, all it takes to be able to pass a lamdba or delegate to a lazy parameter is to actually call it when passing it. So, if you add parens after the braces, it works. There's no need to go and add a special case for it to the function. - Jonathan M Davis
Apr 20 2018
parent reply Giles Bathgate <giles.bathgate gmail.com> writes:
On Friday, 20 April 2018 at 22:21:13 UTC, Jonathan M Davis wrote:
 Honestly, I think that it's a terrible idea to special-case it 
 like that. If we want to argue for making it work in the 
 language, that's fine, but if we special-case it like this, 
 then it will work with some functions that have lazy parameters 
 and not others, and the result will be confusing. Besides, all 
 it takes to be able to pass a lamdba or delegate to a lazy 
 parameter is to actually call it when passing it. So, if you 
 add parens after the braces, it works. There's no need to go 
 and add a special case for it to the function.
Again lack of experience, so I presume you can just do: bool inserted = false; auto p = aa.getOrAdd("key", {inserted = true; return new Person; }()); I hadn't realised that until now. I enjoy your brutal honesty by the way ;)
Apr 20 2018
next sibling parent reply Jonathan M Davis <newsgroup.d jmdavisprog.com> writes:
On Friday, April 20, 2018 22:46:54 Giles Bathgate via Digitalmars-d wrote:
 On Friday, 20 April 2018 at 22:21:13 UTC, Jonathan M Davis wrote:
 Honestly, I think that it's a terrible idea to special-case it
 like that. If we want to argue for making it work in the
 language, that's fine, but if we special-case it like this,
 then it will work with some functions that have lazy parameters
 and not others, and the result will be confusing. Besides, all
 it takes to be able to pass a lamdba or delegate to a lazy
 parameter is to actually call it when passing it. So, if you
 add parens after the braces, it works. There's no need to go
 and add a special case for it to the function.
Again lack of experience, so I presume you can just do: bool inserted = false; auto p = aa.getOrAdd("key", {inserted = true; return new Person; }()); I hadn't realised that until now. I enjoy your brutal honesty by the way ;)
Yes. That should work. e.g. this import std.stdio; void foo(lazy string l) { } void main() { foo({writeln("foo"); return "str";}()); } compiles and runs just fine without printing anything, whereas you get a compilation error if you don't have the parens after the closing brace. - Jonathan M Davis
Apr 20 2018
parent reply Giles Bathgate <giles.bathgate gmail.com> writes:
On Friday, 20 April 2018 at 23:13:58 UTC, Jonathan M Davis wrote:
 Yes. That should work. e.g. this

 import std.stdio;

 void foo(lazy string l)
 {
 }

 void main()
 {
     foo({writeln("foo"); return "str";}());
 }

 compiles and runs just fine without printing anything, whereas 
 you get a compilation error if you don't have the parens after 
 the closing brace.
Ok cool. I've updated the PR since basically, I agree with your points about it being confusing to cater for the special case.
Apr 20 2018
parent Steven Schveighoffer <schveiguy yahoo.com> writes:
On 4/20/18 7:21 PM, Giles Bathgate wrote:
 On Friday, 20 April 2018 at 23:13:58 UTC, Jonathan M Davis wrote:
 Yes. That should work. e.g. this

 import std.stdio;

 void foo(lazy string l)
 {
 }

 void main()
 {
     foo({writeln("foo"); return "str";}());
 }

 compiles and runs just fine without printing anything, whereas you get 
 a compilation error if you don't have the parens after the closing brace.
Ok cool. I've updated the PR since basically, I agree with your points about it being confusing to cater for the special case.
Yeah, I didn't mean for the special case to happen -- I thought it just worked! I should have tested my original code... Sorry. -Steve
Apr 20 2018
prev sibling parent reply Steven Schveighoffer <schveiguy yahoo.com> writes:
On 4/20/18 6:46 PM, Giles Bathgate wrote:
 On Friday, 20 April 2018 at 22:21:13 UTC, Jonathan M Davis wrote:
 Honestly, I think that it's a terrible idea to special-case it like 
 that. If we want to argue for making it work in the language, that's 
 fine, but if we special-case it like this, then it will work with some 
 functions that have lazy parameters and not others, and the result 
 will be confusing. Besides, all it takes to be able to pass a lamdba 
 or delegate to a lazy parameter is to actually call it when passing 
 it. So, if you add parens after the braces, it works. There's no need 
 to go and add a special case for it to the function.
Again lack of experience, so I presume you can just do: bool inserted = false; auto p = aa.getOrAdd("key", {inserted = true; return new Person; }()); I hadn't realised that until now. I enjoy your brutal honesty by the way ;)
The drawback here, of course, is that it's a lambda calling a lambda (if you end up using the value). But of course, your overload was the same thing. I'm just surprised it doesn't work, especially when this works: // lazy variadic void foo(int delegate()[] dgs...) { dgs[0](); } foo(1); // ok, same as { return 1; } foo({inserted = true; return 1;}); // ok Of course, it's not as nice syntax inside the function. -Steve
Apr 20 2018
parent Jonathan M Davis <newsgroup.d jmdavisprog.com> writes:
On Friday, April 20, 2018 19:27:20 Steven Schveighoffer via Digitalmars-d 
wrote:
 On 4/20/18 6:46 PM, Giles Bathgate wrote:
 On Friday, 20 April 2018 at 22:21:13 UTC, Jonathan M Davis wrote:
 Honestly, I think that it's a terrible idea to special-case it like
 that. If we want to argue for making it work in the language, that's
 fine, but if we special-case it like this, then it will work with some
 functions that have lazy parameters and not others, and the result
 will be confusing. Besides, all it takes to be able to pass a lamdba
 or delegate to a lazy parameter is to actually call it when passing
 it. So, if you add parens after the braces, it works. There's no need
 to go and add a special case for it to the function.
Again lack of experience, so I presume you can just do: bool inserted = false; auto p = aa.getOrAdd("key", {inserted = true; return new Person; }()); I hadn't realised that until now. I enjoy your brutal honesty by the way ;)
The drawback here, of course, is that it's a lambda calling a lambda (if you end up using the value). But of course, your overload was the same thing. I'm just surprised it doesn't work, especially when this works: // lazy variadic void foo(int delegate()[] dgs...) { dgs[0](); } foo(1); // ok, same as { return 1; } foo({inserted = true; return 1;}); // ok Of course, it's not as nice syntax inside the function.
Honestly, I would have considered it a bug that it accepts 1, since that's not a delegate or lambda or anything of the sort, and the function is explicitly typed to take delegates. - Jonathan M Davis
Apr 20 2018
prev sibling parent reply Andrea Fontana <nospam example.com> writes:
On Sunday, 15 April 2018 at 22:52:47 UTC, Giles Bathgate wrote:
 Hi,

 I wanted a way to lazily insert a value into an associative 
 array, and I am proposing a new function called getOrAdd in  
 https://github.com/dlang/druntime/pull/2162
Yes please :) Just one question: does this work for value-types? This is a pretty common case for me (simplified and not optimized version): size_t[string] counter; ... foreach(e; elements) { if (e.something !in counter) counter[e.something] = 0; else counter[e.something]++; } So will this work? foreach(e; elements) counter.update(e.something, { return 0; }, (size_t v) { return v+1; }) Andrea Andrea
Apr 24 2018
parent Giles Bathgate <giles.bathgate gmail.com> writes:
On Tuesday, 24 April 2018 at 09:09:37 UTC, Andrea Fontana wrote:
 Just one question: does this work for value-types?
The intention is that it will work for value types. I will add some unit tests to check that use case. - Giles
Apr 25 2018