ID:2472728
 
Applies to:DM Language
Status: Open

Issue hasn't been assigned a status value.

The ability to overload the "in" operator to allow for the full recreation of a list datum/structure.


List/proc/operatorin()
Yes please. If there's an operator it should be overrideable.
It would definitely be nice to have in theory, but how would it work in practice? BYOND doesn't have iterators, so what would it return? A list?

EDIT: Oh right, the actual operator use of it
Yeah, that one would be pretty easy to figure out
It wouldn't be a full recreation without both uses of it though
Why not have operatorin() and operatoriter()?
Yeah, but how would the latter work?
We want this:
a in b // "is a in b?"
// calls b.operator???(a) which returns a bool

for(a in b) // "for each a in b:"
// calls b.operator???() which returns a list

With these, we can make our own lists and use them the same exact way.
Basically, C#-style iterators are any class with this interface :
iterator
proc
// Returns TRUE after moving to the next value, if any.
// Returns FALSE otherwise.
MoveNext()

// Returns the current value.
Current()

// C# also has a Reset(), but I don't think it's really necessary here.
// Also, C# calls this "IEnumerator", but I'm calling it "iterator".

They can be iterated one at a time or all at once with the pattern:
var/iterator/it = // some iterator

// Do something with the next value:
if(it.MoveNext())
// it.Current() is the new current value

// Do something with all the rest of the values:
while(it.MoveNext())
// it.Current() becomes each of the rest of the values

There is an example of this in the engine already -- database queries:
database/query
proc
// Returns TRUE after moving to the next row, if any.
// Returns FALSE otherwise.
NextRow()

// Returns the current row data.
GetRowData()

// e.g.
var/database/query/q = new("SELECT ...")
q.Execute(...)
while(q.NextRow())
// q.GetRowData() is the data of the new current row

Clearly, database queries can be used as iterators of row data.

In C#, the "foreach()" statement can be used with anything that can provide an iterator over its elements.
(C# calls those providers "IEnumerable", I'm calling them "iterable".)
// From this:
var iterable = // some iterable that provides an iterator through GetEnumerator
var it = iterable.GetEnumerator();
while(it.MoveNext()) {
var value = it.Current();
// value is each value in the iterable
}

// To this:
var iterable = // same as above
foreach(var value in iterable)
// value is each value in the iterable

In DM, you can provide an iterator for the built-in /list like so:
iterator/from_list
var
list/source
index = 0

New(list/source) src.source = source

MoveNext()
if(++index <= source.len) return TRUE
index = source.len
return FALSE

Current() return source[index]

// e.g.
var/iterator/it = new/iterator/from_list(list(1, 2, 3))
while(it.MoveNext())
world << it.Current()

/* outputs
1
2
3
*/

So, foreach() in DM would work like this:
// From this, which we can already do:
var/iterable/values = // some iterable
var/iterator/it = values.GetIterator()
while(it.MoveNext())
var value = it.Current()
// value is each value in values

// To this, which this feature request could do for us:
foreach(var/value in values )
// value is each value in values

And of course this would also apply to database queries (which are their own iterator):
var/database/query/q = new("SELECT ...")
q.Execute(...)
foreach(var/list/row_data in q)
// row_data is each q.GetRowData() in the result


In C#, functions can be made to return an iterator that iterates values that are "yield returned" in the function itself:
IEnumerator Numbers() {
yield return 1;
yield return 2;
yield return 3;
}
// e.g.
foreach(var number in Numbers())
System.Console.WriteLine(number);

"yield return" makes the function wait there until the next value is requested.