🎉 Celebrating 25 Years of GameDev.net! 🎉

Not many can claim 25 years on the Internet! Join us in celebrating this milestone. Learn more about our history, and thank you for being a part of our community!

Why don't languages do for each loops this way?

Started by
15 comments, last by SeraphLance 7 years, 6 months ago
I've been thinking a lot about language design and thought of a way to handle for each loops.

function square(input: x) {
    return x * x;
}

var singleValue = 2;
var arrayValue = [1, 2, 3];

var singleResult = square(singleValue);
var arrayResult = square(each in arrayValue);
// arrayResult is [1, 4, 9]

Is it clear what the value of arrayResult will be? I am particularly interested in the opinion of newer developers here since they aren't used to seeing and thinking in forEach or map.

I think the value of this style really shows its strength when you deal with a few more complicated examples

var arrayResult = pow(each in arrayValue, 3);
// arrays are iterated over in parallel, stopping when the first array ends
var actionResult = doAction(each in arrayA, transformValue(each in arrayB));

// each<n> allows you to nest loops with the smaller values taking the outer loop
// reduce combines array elements in different ways
var matrixSum = reduce sum(matrix.get(
    each<0> in range(0, matrix.rowCount), 
    each<1> in range(0, matrix.columnCount))
)
compare that to doing something similar by traditional means
var arrayResult = arrayValue.map((value) => pow(value, 3));
var actionResult = range(0, min(arrayA.length, arrayB.length)).map((index) => {
    return doAction(arrayA[index], transformValue(arrayB[index]));
});

var matrixSum = range(0, matrix.height).flatMap((row) => {
    return range(0, matrix.width).map((colum) => {
        return matrix.get(row, column);
    })
}).reduce(sum, 0);
I recognize that this alternate syntax isn't perfect but I really like. There are two immediate wins I see with the alternate syntax.

1) You don't have to restructure you code if a variable type changes from a single value to an array

Traditionally, changing a type from a single value to an array requires you to restructure code to use a forEach.
doAction(singleValue);
arrayValue.forEach(doAction);
With the new style, no restructure needed, just add 'each in' where needed
doAction(singleValue);
doAction(each in arrayValue)
2) The compiler knows more about the forEach loop and can make better optimizations

map can generate a lot of junk for the garbage collector since it needs to create an entire array that will be just discarded like in the case of the matrix sum example. Iterators like in Linq solve the temporary memory problem but create a chain full of virtual calls and labmda functions that can be difficult to optimize. The new syntax can allow the compiler to transform the forEach code into a simple loop and can know if a new array needs to be created, or if values are combined into a single result.

Thoughts on the syntax? Are there any glaring issues you see with it?
My current game project Platform RPG
Advertisement
I don't like it.


I prefer being able to recognize the return type and arity of a function by just looking at its signature. Now I have to look at all the call sites to see if my square() function is potentially going to suddenly start returning an array instead of a scalar. Seems like it'd be easy to trap yourself in a dark corner with slightly more complex situations.

Wielder of the Sacred Wands
[Work - ArenaNet] [Epoch Language] [Scribblings]

What about this?

var singleValue = 2;
var arrayValue = [1, 2, 3];

var singleResult = square(singleValue);
var arrayResult = square.apply_to(arrayValue);

Here you can call a function normally on a single value, but the function itself is an object - so you can "apply" it to a range of values, which calls for-each internally, and in such a way that there is a visually-obvious distinction between the two cases. The return type of the function therefore doesn't change based on the context, because the function itself and apply_to are not the same thing.
I'm used to spelling that as "map(square, SomeArray)" but that's probably my functional influences showing :-)

Wielder of the Sacred Wands
[Work - ArenaNet] [Epoch Language] [Scribblings]

Looks pleasing enough for the simple cases. It starts to get a lot less clear (to me) when there are multiple 'each in' in a single expression such as your parallel arrays example.

How does your new syntax fair with something like this?:


arrayValue.map((value) => pow(value, value));

// arrays are iterated over in parallel, stopping when the first array ends
var actionResult = doAction(each in arrayA, transformValue(each in arrayB));


That seems pretty ambiguous and difficult to control.

How about using 'foreach' itself as an expression instead of a statement, similar to functional languages that use 'if' as an expression instead of a statement?

This allows for more reasonable syntax and doesn't mess with referential transparency:

// doAction's second parameter is an array
var actionResult = foreach (a in arrayA) { doAction(a, foreach (b in arrayB) }

// doAction's second parameter is a single element
var actionResult = foreach (a in arrayA) { foreach (b in arrayB) { doAction(a, b) } }

// Additional qualifier to provide an index value
var actionResult = foreach indexed (a,i in arrayA) { doAction(a, arrayB[i]) }

// parallel loop, by default it uses the minimum of the N array lengths you pass it
var actionResult = foreach parallel (a in arrayA defaultA, b in arrayB) { doAction(a, b) }

// But if you DO want to do something for the elements that are only present in one array, you can provide a default selector:
var actionResult = foreach parallel (a in arrayA default defaultA, b in arrayB) { doAction(a, b) }
var actionResult = foreach parallel (a in arrayA, b in arrayB default defaultB) { doAction(a, b) }
var actionResult = foreach parallel (a in arrayA default defaultA, b in arrayB default defaultB) { doAction(a, b) }

// Control over whether you get an array or an array-of-arrays out:
var actionResult = foreach         (a in arrayA) { foreach (b in arrayB) { a * b } }
var actionResult = foreach flatten (a in arrayA) { foreach (b in arrayB) { a * b } }

I don't like it.
I prefer being able to recognize the return type and arity of a function by just looking at its signature.


How about needing to annotate a forEach expansion before you could use it, kinda like Nypyren's suggestion
var arrayResult = foreach square(in arrayValue);

How does your new syntax fair with something like this?:

arrayValue.map((value) => pow(value, value));


var arrayResult = foreach pow(in arrayValue, in arrayValue);

How about using 'foreach' itself as an expression instead of a statement, similar to functional languages that use 'if' as an expression instead of a statement? This allows for more reasonable syntax and doesn't mess with referential transparency:


Excellent, that definitely does solve some ambiguity problems. Do you think reducing the boilerplate like this improves it?

// doAction's second parameter is an array
var actionResult = foreach doAction(in arrayA, arrayB)

// doAction's second parameter is a single element
var actionResult = foreach<A> foreach<B> doAction(in<A> arrayA, in<B> arrayB)

// Additional qualifier to provide an index value
var actionResult = foreach key idx doAction(in arrayA, arrayB[idx])

// parallel loop, by default it uses the minimum of the N array lengths you pass it
var actionResult = foreach doAction(in arrayA, in arrayB)

// But if you DO want to do something for the elements that are only present in one array, you can provide a default selector:
var actionResult = foreach doAction(in arrayA default defaultA, in arrayB)
var actionResult = foreach doAction(in arrayA, in arrayB default defaultB)
var actionResult = foreach doAction(in arrayA default defaultA, in arrayB default defaultB)

// Control over whether you get an array or an array-of-arrays out:
var actionResult = foreach<A>         foreach<B> in<A> arrayA * in<B> arrayB
var actionResult = foreach<A> flatten foreach<B> in<A> arrayA * in<B> arrayB
My current game project Platform RPG

does lisp work like that (i hear it is quite flexible)?

i prefer

dest = source.map(function).map(function2)

looking at

dest = function(each source<T>)

it would seam that if function takes T then we don't need each but then its hard to distinquish.

i think

dest = function2.each(function.each(source))

is fine but it reads backwards so (borrowing pipelining from fsharp)

dest = source |> function.each |> function2.each

is the alternative form.

I dont really see the difference to any of it but things should read in the order they are done imo.

// doAction's second parameter is a single element
var actionResult = foreach<A> foreach<B> doAction(in<A> arrayA, in<B> arrayB)

// Additional qualifier to provide an index value
var actionResult = foreach key idx doAction(in arrayA, arrayB[idx])

// Control over whether you get an array or an array-of-arrays out:
var actionResult = foreach<A>         foreach<B> in<A> arrayA * in<B> arrayB
var actionResult = foreach<A> flatten foreach<B> in<A> arrayA * in<B> arrayB


The foreach<A> in<A> separation seems like a hassle. What if you want to use in<A>'s value twice?

Do you say "in<A> arrayA" in every spot you want to reuse it?

Let's say you wanted to replace the first example with a loop that does an action on arrayA, but uses the element for both parameters.

//My way
var actionResult = foreach (a in arrayA) { doAction(a, a) }

//Your way
var actionResult = foreach<A> doAction(in<A> arrayA, in<A> arrayA)

//or if you want to make the grammar more complicated to allow eliding repeated array identifiers:
var actionResult = foreach<A> doAction(in<A> arrayA, in<A>)
The indexed example you give would be hard to use with multiple arrays; ex:

//My way:
var actionResult = foreach indexed (a,indexA in arrayA) { foreach indexed (b,indexB in arrayB) { doIndexedAction(a, indexA, b, indexB) } }

//Your way (I changed it so that 'idx' is now a keyword like 'in', which saves a massive amount of space):
// Also changed nested foreaches without flattening so they can be combined in a comma-list
var actionResult = foreach<A,B> doIndexedAction(in<A> arrayA, idx<A>, in<B> arrayB, idx<B>)

// or something like that?

// The amount of <A> <B> correlation and elided array identifiers you might end up having to support in the grammar seems very painful.

I've been thinking a lot about language design and thought of a way to handle for each loops.


function square(input: x) {
    return x * x;
}

var singleValue = 2;
var arrayValue = [1, 2, 3];

var singleResult = square(singleValue);
var arrayResult = square(each in arrayValue);
// arrayResult is [1, 4, 9]

The trouble with it is that it just does not compose that well. The standard functional map, reduce (Select, SelectMany, Aggregate in C# link) is the better way, sprinkle in a bit of partial application and you have a much more powerful toolbox.

There is a reason why map/reduce have appeared in so many languages either first class or via library, it is just a more powerful and flexible approach to loops. In modern C# for example there have been very few cases where I resort to foreach. If I do find myself typing foreach I look for a better solution as it normally means you are doing it wrong imho

This topic is closed to new replies.

Advertisement