www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - range methods on associative arrays

reply WebFreak001 <d.forum webfreak.org> writes:
a lot of range methods, such as `filter`, `any`, `all`, `count`, 
`each`, etc. would be useful on associative arrays, taking in key 
and value, returning a processed .byKeyValue range.

I would suggest, at least for phobos v2, we should have these 
functions automatically call `.byKeyValue` on maps and there 
should be support for lambdas with 2 arguments there, which 
automatically unwrap key and value (and possibly all tuples 
actually)

What do you think?

```d
map.each!((key, value) { /* like a foreach, but functional style 
*/ });

bool hasId = map.any!((key, value) => key == "id" && value !is 
null);
```

for this I think the implementation would basically boil down to:

- implicitly call `.byKeyValue` in the map-accepting range methods
- allow tuples and the KeyValue pair to be extended into multiple 
parameters on CT lambdas that have multiple arguments

Users wanting to only use keys or only values can still use 
.byKey or .byValue.
Jul 02 2022
next sibling parent reply Steven Schveighoffer <schveiguy gmail.com> writes:
On 7/2/22 4:14 PM, WebFreak001 wrote:
 a lot of range methods, such as `filter`, `any`, `all`, `count`, `each`, 
 etc. would be useful on associative arrays, taking in key and value, 
 returning a processed .byKeyValue range.
 
 I would suggest, at least for phobos v2, we should have these functions 
 automatically call `.byKeyValue` on maps and there should be support for 
 lambdas with 2 arguments there, which automatically unwrap key and value 
 (and possibly all tuples actually)
 
 What do you think?
 
 ```d
 map.each!((key, value) { /* like a foreach, but functional style */ });
 
 bool hasId = map.any!((key, value) => key == "id" && value !is null);
 ```
 
 for this I think the implementation would basically boil down to:
 
 - implicitly call `.byKeyValue` in the map-accepting range methods
 - allow tuples and the KeyValue pair to be extended into multiple 
 parameters on CT lambdas that have multiple arguments
 
 Users wanting to only use keys or only values can still use .byKey or 
 .byValue.
This idea misunderstands what a range is. In order for an AA to be considered a "range", each popFront should remove the element from the AA. We don't want that. Just use the range accessors. -Steve
Jul 02 2022
parent reply WebFreak001 <d.forum webfreak.org> writes:
On Saturday, 2 July 2022 at 21:18:06 UTC, Steven Schveighoffer 
wrote:
 On 7/2/22 4:14 PM, WebFreak001 wrote:
 a lot of range methods, such as `filter`, `any`, `all`, 
 `count`, `each`, etc. would be useful on associative arrays, 
 taking in key and value, returning a processed .byKeyValue 
 range.
 
 I would suggest, at least for phobos v2, we should have these 
 functions automatically call `.byKeyValue` on maps and there 
 should be support for lambdas with 2 arguments there, which 
 automatically unwrap key and value (and possibly all tuples 
 actually)
 
 What do you think?
 
 ```d
 map.each!((key, value) { /* like a foreach, but functional 
 style */ });
 
 bool hasId = map.any!((key, value) => key == "id" && value !is 
 null);
 ```
 
 for this I think the implementation would basically boil down 
 to:
 
 - implicitly call `.byKeyValue` in the map-accepting range 
 methods
 - allow tuples and the KeyValue pair to be extended into 
 multiple parameters on CT lambdas that have multiple arguments
 
 Users wanting to only use keys or only values can still use 
 .byKey or .byValue.
This idea misunderstands what a range is. In order for an AA to be considered a "range", each popFront should remove the element from the AA. We don't want that. Just use the range accessors. -Steve
arrays/slices are also not ranges and get special-cased (or at least have compatibility functions in std.range.primitives) to be used with range code, I don't see any reason why not to do this with maps as well, which are a first class feature of the language.
Jul 02 2022
parent reply Steven Schveighoffer <schveiguy gmail.com> writes:
On 7/2/22 5:51 PM, WebFreak001 wrote:
 On Saturday, 2 July 2022 at 21:18:06 UTC, Steven Schveighoffer wrote:
 This idea misunderstands what a range is.

 In order for an AA to be considered a "range", each popFront should 
 remove the element from the AA.

 We don't want that. Just use the range accessors.
arrays/slices are also not ranges and get special-cased (or at least have compatibility functions in std.range.primitives) to be used with range code, I don't see any reason why not to do this with maps as well, which are a first class feature of the language.
arrays/slices are ranges. They are actually the essential range type. Also, an AA is fundamentally different. I can popFront on a slice, and it doesn't affect other slices. If I popFront an element from an AA, it affects all other references to that AA. -Steve
Jul 02 2022
parent reply Paul Backus <snarwin gmail.com> writes:
On Saturday, 2 July 2022 at 23:13:43 UTC, Steven Schveighoffer 
wrote:
 Also, an AA is fundamentally different. I can popFront on a 
 slice, and it doesn't affect other slices. If I popFront an 
 element from an AA, it affects all other references to that AA.
To be fair this is also true of some ranges. The real usability issue here is that if AAs a ranges with reference semantics, then code like this ```d auto aa = ["foo": 123, "bar": 456]; foreach (key, val; aa) doSomethingWith(key, val); ``` ...will consume the range, leaving `aa` empty after the loop completes. So most of the time, you will want to use `.byKeyValue` anyway, even if AAs implement the range interface "natively."
Jul 02 2022
parent Steven Schveighoffer <schveiguy gmail.com> writes:
On 7/2/22 7:53 PM, Paul Backus wrote:
 On Saturday, 2 July 2022 at 23:13:43 UTC, Steven Schveighoffer wrote:
 Also, an AA is fundamentally different. I can popFront on a slice, and 
 it doesn't affect other slices. If I popFront an element from an AA, 
 it affects all other references to that AA.
To be fair this is also true of some ranges.
Right, but by default iterating an AA doesn't do this, and nobody would expect it.
 
 The real usability issue here is that if AAs a ranges with reference 
 semantics, then code like this
 
 ```d
 auto aa = ["foo": 123, "bar": 456];
 foreach (key, val; aa)
      doSomethingWith(key, val);
 ```
 
 ...will consume the range, leaving `aa` empty after the loop completes. 
 So most of the time, you will want to use `.byKeyValue` anyway, even if 
 AAs implement the range interface "natively."
Technically, this doesn't use the range API. However, aa.map!(...) would, and that's where the real problems would begin. The proposal is somewhat cleverer in that it will implicitly call `byKeyValue`, but this I don't think scales well, unless the compiler is made to do it. I think saving the call to `.byKeyValue` isn't worth this trouble. -Steve
Jul 02 2022
prev sibling next sibling parent monkyyy <crazymonkyyy gmail.com> writes:
On Saturday, 2 July 2022 at 20:14:10 UTC, WebFreak001 wrote:
 make more special cases like auto decoding
Its a general problem that ranges are bad are mimicking some types of for loop patterns; not a specific one ```d foreach(e;list){ if(e.foo==cond){break;} e.bar; } //if foo is local, it may not scope correctly into std.filter`s lamda ``` ```d auto store; foreach(ref e;list){ e=foo(e,store); store=bar(e,store); e.foobar; } ``` one could argue that there are 4 useful data structures that come out of the box, static arrays, slices, strings, and aa's and that the benefit of templates is that you get (in theory) great implications when combining non-specialized data structures and algorithms; and you would be in effect suggesting the std increases its special cases to 2 out of 4. (If not 3 with slices and gc) I suggest that aa's should follow the rough standard syntax that is the best I can mimic of defining `[]` to return a range for my data structures instead of the verbose thing I always have to look up. And that the algorithms the std define are robust enough to handle the general problems. Fundamentally the std is written as if caring that ranges should be reference types; I dislike that take, but you'd be fighting an uphill battle to suggest it changes on a real level. And your suggestion would make the same sort of hacks that string has and the issues that follow.
Jul 02 2022
prev sibling parent bauss <jj_1337 live.dk> writes:
On Saturday, 2 July 2022 at 20:14:10 UTC, WebFreak001 wrote:
 a lot of range methods, such as `filter`, `any`, `all`, 
 `count`, `each`, etc. would be useful on associative arrays, 
 taking in key and value, returning a processed .byKeyValue 
 range.

 I would suggest, at least for phobos v2, we should have these 
 functions automatically call `.byKeyValue` on maps and there 
 should be support for lambdas with 2 arguments there, which 
 automatically unwrap key and value (and possibly all tuples 
 actually)

 What do you think?

 ```d
 map.each!((key, value) { /* like a foreach, but functional 
 style */ });

 bool hasId = map.any!((key, value) => key == "id" && value !is 
 null);
 ```

 for this I think the implementation would basically boil down 
 to:

 - implicitly call `.byKeyValue` in the map-accepting range 
 methods
 - allow tuples and the KeyValue pair to be extended into 
 multiple parameters on CT lambdas that have multiple arguments

 Users wanting to only use keys or only values can still use 
 .byKey or .byValue.
I think this confuses an associative array/hashmap with a "list" of key-value pairs; which isn't the same. An associative array isn't really range-like in nature, if it was up to me then you shouldn't even be able to natively iterate over it in a foreach like you can now, but that's just me being too strict for the norm I guess.
Jul 03 2022