Thread overview
extending foreach for keyed ranges and other usage of datastructures
May 15
monkyyy
May 16
monkyyy
May 16
monkyyy
May 17
monkyyy
May 15

extend the api of foreach to allow for foreach(key,value;data) for user data types,
and user defined "autodecoding"

related debates

the (unnesery) deperacation of "implict casts" of array indexes to int https://forum.dlang.org/thread/qdsypzjwptbebffktohx@forum.dlang.org

key'd ranges in general https://forum.dlang.org/thread/akczwyrmlpocihmaitbz@forum.dlang.org

definitions

keyd range: a struct that defines the functions front,popFront,empty and key

autodecoding: when a data structure is implicitly transformed into a range by a imported function and ufcs (not string specific)

foreach header: the part of the foreach call that comes before the ;

priority

  1. if the template header and data combo are currently valid, dont change behavior(whatever that looks like)

  2. if the data is a keyd range but the foreach header has a key; define key to be range.key and value to be range.front; handle ref if possible

  3. if data is not a range, attempt to call opSlice to see if thats a range (static arrays)

  4. if data does not have an opSlice, "autodecode" data.toRange!(details from foreach header)

foreach header parsing

because ref isnt on types I dont believe you can pass "ref int" into a template, and maybe theres something about named arguments, I believe this should be handled with strings.

unattributed arguments are replaced with void, so the user can handle hard cases tuples as they wish(2d iteration, or zipped ranges)

foreach(int k,value:ref @safe v;...) would be parsed as ("int","value:ref @safe") Im unsure what user code does with "@safe" but let the user figure it out

foreach(a,b,c,d,e,f,g;...) would be parsed as (void,void,void,void,void,void)

keyd range example code

struct strangecounter{
  int front;
  int last;
  void popFront(){front++;}
  int key()=>front*front;
  bool empty()=> last>=front;
}
foreach(k,v;strangecounter(0,10)){
  writeln(k,":",v);//0:0, 1:1, 4:2,9:3.....
}

foreach(ref k,v;strangecounter(0,10)) would fail to compile, some complaint about lvalues or suggesting the user to define a torange!("ref",void)
foreach(k,ref v;strangecounter(0,10)) would compile
foreach(size_t k,v;strangecounter(0,10)) would fail to compile, suggesting the user to define a torange!("size_t",void)
foreach(a,b,c;strangecounter(0,10)) would fail to compile, and suggesting the user define a torange!(void,void,void)
foreach(v;strangecounter(0,10)) would compile (using keys are optional)

torange example code

handling int indexing

auto torange(string s:"int",T)(ref T[] array){
  struct myrange{
    int key_;
    auto key()=>key_;//unref's key
    auto front()=>array[key];
    auto popFront(){key_++;}
    auto empty()=>arra.length>=key;
  }
  return myrange;
}
foreach(int i,v;array){//compiles
May 16

On Wednesday, 15 May 2024 at 20:27:12 UTC, monkyyy wrote:

>

extend the api of foreach to allow for foreach(key,value;data) for user data types,

You can already have multiple variables using opApply, or for a range where front returns a tuple.

https://dlang.org/spec/statement.html#front-seq

For the front tuple case, it doesn't support ref value well.

>
  1. if the data is a keyd range but the foreach header has a key; define key to be range.key and value to be range.front; handle ref if possible
>

keyd range example code

struct strangecounter{
  int front;
  int last;
  void popFront(){front++;}
  int key()=>front*front;
  bool empty()=> last>=front;
}
foreach(k,v;strangecounter(0,10)){
  writeln(k,":",v);//0:0, 1:1, 4:2,9:3.....
}

foreach(ref k,v;strangecounter(0,10)) would fail to compile, some complaint about lvalues or suggesting the user to define a torange!("ref",void)
foreach(k,ref v;strangecounter(0,10)) would compile

ref k could work if key() is an lvalue, although I'm not sure if there are any data types where that makes sense.
ref v can work if front is an lvalue, otherwise it should be an error.

>

foreach(v;strangecounter(0,10)) would compile (using keys are optional)

It may be less efficient for certain ranges to track what the current key is when the foreach statement doesn't need it. Also when iterating the range and the user doesn't need .key.

May 16

On Thursday, 16 May 2024 at 12:16:46 UTC, Nick Treleaven wrote:

>

On Wednesday, 15 May 2024 at 20:27:12 UTC, monkyyy wrote:

>

extend the api of foreach to allow for foreach(key,value;data) for user data types,

You can already have multiple variables using opApply, or for a range where front returns a tuple.

which only work with 1 configuration and doesnt provide the explicit "implicit casting" options; its a single function iteration api when ranges being a 3 function api is just clearly superior

>

https://dlang.org/spec/statement.html#front-seq

For the front tuple case, it doesn't support ref value well.

>
  1. if the data is a keyd range but the foreach header has a key; define key to be range.key and value to be range.front; handle ref if possible
>

keyd range example code

struct strangecounter{
  int front;
  int last;
  void popFront(){front++;}
  int key()=>front*front;
  bool empty()=> last>=front;
}
foreach(k,v;strangecounter(0,10)){
  writeln(k,":",v);//0:0, 1:1, 4:2,9:3.....
}

foreach(ref k,v;strangecounter(0,10)) would fail to compile, some complaint about lvalues or suggesting the user to define a torange!("ref",void)
foreach(k,ref v;strangecounter(0,10)) would compile

ref k could work if key() is an lvalue, although I'm not sure if there are any data types where that makes sense.
ref v can work if front is an lvalue, otherwise it should be an error.

I meant those as specif results of the code above it, that key isnt a ref function

ref keys mutating the range is a rabbit hole that while I support, I can easily use work arounds and it would scare the very people who feel that foreach(int i...) is somehow an "implict" cast and that should have been deprecated

> >

foreach(v;strangecounter(0,10)) would compile (using keys are optional)

It may be less efficient for certain ranges to track what the current key is when the foreach statement doesn't need it. Also when iterating the range and the user doesn't need .key.

I want keys in ranges in general if theres some major reason to not have keys you could have two different "range starters" much like trees having depthfirst or breathefirst ranges (I cant imagine it being that slow, either your poking an int or forwarding the hashmap keys outward)

May 16

On Wednesday, 15 May 2024 at 20:27:12 UTC, monkyyy wrote:

>

extend the api of foreach to allow for foreach(key,value;data) for user data types,
and user defined "autodecoding"

[…]

Can’t opApply not already do that?

May 16

On Thursday, 16 May 2024 at 18:15:09 UTC, Quirin Schroll wrote:

>

On Wednesday, 15 May 2024 at 20:27:12 UTC, monkyyy wrote:

>

extend the api of foreach to allow for foreach(key,value;data) for user data types,
and user defined "autodecoding"

[…]

Can’t opApply not already do that?

opApply cant effect T[] to have int indexes and range.enumerate has problems with strings; and if you have key,value pairs you must always use it in all foreaches even if you only use value while slices get special treatment

May 16

On Thursday, 16 May 2024 at 18:21:16 UTC, monkyyy wrote:

>

On Thursday, 16 May 2024 at 18:15:09 UTC, Quirin Schroll wrote:

>

On Wednesday, 15 May 2024 at 20:27:12 UTC, monkyyy wrote:

>

extend the api of foreach to allow for foreach(key,value;data) for user data types,
and user defined "autodecoding"

[…]

Can’t opApply not already do that?

opApply cant effect T[] to have int indexes

Why would you want it to have int indices?

>

and range.enumerate has problems with strings;

I have zero clue what you mean.

>

if you have key,value pairs you must always use it in all foreaches even if you only use value while slices get special treatment

That’s definitely wrong. You can overload opApply just fine:

struct Range
{
    import std.stdio;
    int opApply(int delegate(ref int) callback)
    {
        writeln("no key");
        return 0;
    }
    int opApply(int delegate(size_t, ref int) callback)
    {
        writeln("with key");
        return 0;
    }
}

void main()
{
    foreach (ref value; Range()) {} // writes "no key"
    foreach (key, ref value; Range()) {} // writes "with key"
}
May 17

On Thursday, 16 May 2024 at 20:59:26 UTC, Quirin Schroll wrote:

>

Why would you want it to have int indices?

cause id rather write 1 cast then 10 when im iterating over data that depends on the index for some math; I find size_t style indexs insane and would happily change slices to int, I cant and theres that deprecation coming

Im far from alone, see a rant thread about the deprecation

> >

and range.enumerate has problems with strings;

I have zero clue what you mean.

imagine a comma seperated value list with unicode
you enumerate and filter by comma than map the tuple to the "index", the indexs that are returned will be wrong

enumerate can only react to the fronts as if they are one at a time, unicode skips ahead; fundamental unsolvable problem with the current range api

> >

if you have key,value pairs you must always use it in all foreaches even if you only use value while slices get special treatment

That’s definitely wrong. You can overload opApply just fine:

struct Range
{
    import std.stdio;
    int opApply(int delegate(ref int) callback)
    {
        writeln("no key");
        return 0;
    }
    int opApply(int delegate(size_t, ref int) callback)
    {
        writeln("with key");
        return 0;
    }
}

void main()
{
    foreach (ref value; Range()) {} // writes "no key"
    foreach (key, ref value; Range()) {} // writes "with key"
}

ok, I was wrong (still on team 3 function ranges and not single function iterators)