Argument Plurality

Avernite

Member
Joined
Jan 17, 2023
Messages
18
Programming Experience
10+
This post discusses a hypothetical C# feature called 'Argument Plurality'.

Consider the following:

C#:
foreach (var name in names)
  print(name);

Being written as:

Hypothetical C#:
for (each var name in names) // replaces foreach
  print(name);

The introduced 'each' keyword allows iteration-over-collection to be specified at the argument level:

Hypothetical C#:
print(each var name in names); // Argument plurality invokes 'print' for each element in 'names' (this effects a foreach loop without writing one)

Since 'name' isn't being referenced in this example it can be omitted:

Hypothetical C#:
print(each var in names); // includes nulls

print(each String in names); // includes only String[s] (excludes nulls)

Add to this tighter integration of SQL-style querying:

Hypothetical C#:
print(each String name in names where name.StartsWith("B")); // null safe due to implied filtering for String[s]

Iterating over nested collections:

Hypothetical C#:
// Assume persons is Person[] containing everyone in the world
// Assume Person.inventory is Item[]

// Print everyone's items
for (each Item item in (each Person in persons).inventory)
  print(item);

// The above, using argument plurality
print(each Item in (each Person in persons).inventory);

// Can similarly be written as
print(each var in (each Person in persons).inventory); // includes nulls

// However, specifying type qualifiers provides implicit filtering for those types
print(each ItemTool in (each Firefighter in persons).inventory); // implied filtering for ItemTool[s] and Firefighter[s]

// This is functionally equivalent to
print(each Item item in (each Person person in persons where person is Firefighter).inventory where item is ItemTool);

// Print all heavy tools of on-duty Firefighter[s] in city of Toronto
print(each ItemTool in (each Firefighter in persons where onDuty && location.Equals("Toronto")).inventory where weight >= 10);

Joining peer pluralities via operator:

Hypothetical C#:
var firstNames = new string[] { "Tom", "Bill", "Mike", "Gary", "Brad" };
var lastNames = new string[] { "Smith", "Johnson", "Edwards", "Brown" };

print(each var in firstNames + " " + each var in lastNames); // Print every first-last name permutation (lines outputted = 5 * 1 * 4 = 20)

print(each String f in firstNames where f.StartsWith("B") + " " + each var in lastNames); // with filtering (lines outputted = 2 * 1 * 4 = 8)

// Print multiplication table (assume factors = int[] {1..12})
print(each int m in factors + " x " + each int n in factors + " = " + (m * n)); // lines outputted = 12 * 1 * 12 = 144

Argument plurality alongside non-plural (conventional) argument:

Hypothetical C#:
// Assume 'print' function now has signature print(int indent, String text)
int indent = 4;
print(indent, each String in firstNames);

Is (effectively) expanded to:

C#:
print(4, "Tom");
print(4, "Bill");
print(4, "Mike");
print(4, "Gary");
print(4, "Brad");

Zero invocations are possible too:

Hypothetical C#:
var names = new string[] { }; // empty
print (4, each String in names); // called zero times

Function receiving multiple argument pluralities:

Hypothetical C#:
// Assume printProduct has signature (int m, int n)
// Assume factors = int[] {1..12}

printProduct(each int in factors, each int in factors); // lines outputted = 12 * 12 = 144

Lambda receiving collection param as a plurality:

Hypothetical C#:
c => foreach (var e in c) func(e) // conventional method of iterate-over-collection 'c'

var e in c => func(e) // using plurality mechanism
 
As an aside, strings can be null.
 
With Roslyn being open source, I believe people have taken it and done tweaks to try implementing their own language extensions to C#. Perhaps give it a spin?

Personally, the first question that comes to mind when you start doing multiple as parameters to a method invocation is: what is the order of evaluation? Left to right? Right to left?

Personally, I hated the idea of LINQ (e.g. that literal language imbedded queries, not the extension methods). Why would I want to write SQL inside C#? With this proposed language change, now it looks like I'm trying to write COBOL.

Anyway, it looks like what you want can be closely approximated by writing a ForEach() extension method that looks almost like the TPL Parallel.ForEach(), but without the parallelism.
 
I hated the idea of LINQ (e.g. that literal language imbedded queries, not the extension methods). Why would I want to write SQL inside C#?
You seem to be saying that you hated the idea of LINQ query syntax but are OK with LINQ function syntax. They're both LINQ. Personally, I use function syntax most of the time and only use query syntax for long/complex queries, where I find it more readable. Many people find query syntax more natural. That may be because they are used to SQL but many beginners seem to find it so too. Maybe that's just because that's how they learned it in their initial classes/tutorials.
 
Yes. I learned imperative/procedural programming for the first. It's a big mental shift for me to read LISP, Haskell, F# or other functional programming languages. SQL falls into that same category of "functional" for me.
 
This post discusses a hypothetical C# feature called 'Argument Plurality'.

Consider the following:
Consider you might be happier with python, but feel free to make your suggestion to C# team (this isn't the right place for that) - it wouldn't get my vote
 
With Roslyn being open source, I believe people have taken it and done tweaks to try implementing their own language extensions to C#. Perhaps give it a spin?

Thank you for the suggestion. I'll take a look.

Personally, the first question that comes to mind when you start doing multiple as parameters to a method invocation is: what is the order of evaluation? Left to right? Right to left?

I believe invocation order should be as follows:

Hypothetical C#:
// Assume printProduct(int, int) and factors = int[] {1..12}
printProduct(each int in factors, each int in factors);

// Same invocation order as
for (each int m in factors)
    for (each int n in factors)
        printProduct(m, n);

Consider you might be happier with python, but feel free to make your suggestion to C# team (this isn't the right place for that) - it wouldn't get my vote

I think it's an interesting idea and wanted to get feedback (critical or supportive) before making any kind of serious pitch (assuming it ever got that far).

If I may ask, what is it specifically about the idea that doesn't appeal to you?
 
How is that feature supposed to act on the case of a method that looks like:
C#:
void DoStuff(IEnumerable<int> values)
?

C#:
DoStuff(each int in factors)

Would it just devolve to:

C#:
DoStuff(factors)

?
 
How is that feature supposed to act on the case of a method that looks like:
C#:
void DoStuff(IEnumerable<int> values)
?

C#:
DoStuff(each int in factors)

Would it just devolve to:

C#:
DoStuff(factors)

?

The method DoStuff(each int in factors) would require a method with signature DoStuff(int).

In other words, if you can't pass a single int to the method then it won't suit DoStuff(each int in factors) either, ie. "can not convert from 'int' to IEnumerable<int>".

In this case, yes, you would need to pass the int array itself to the DoStuff(IEnumerable<int> values) method you have specified, otherwise get a compiler error.
 
The method DoStuff(each int in factors) would require a method with signature DoStuff(int).

In other words, if you can't pass a single int to the method then it won't suit DoStuff(each int in factors) either, ie. "can not convert from 'int' to IEnumerable<int>".

In this case, yes, you would need to pass the int array itself to the DoStuff(IEnumerable<int> values) method you have specified, otherwise get a compiler error.
What if DoStuff is not void? Would you expect either of these to work, or something else?
C#:
SomeType myVar = DoStuff(each int in factors);

IEnumerable<SomeType> myVar = DoStuff(each int in factors);
 
I worked out some more hypothetical 'dream' grammar if anyone would like to ponder and comment:

Hypothetical C#:
// Assume names is string[] containing some names and a sprinkling of nulls

// Direct counting (count <plurality> ie. count <iterations-over-collection>)
int numNulls = count each null in names;
int numStrings = count each String in names; // count instances of String in 'names' (skips nulls)
int numEmptyStrings = count each String name in names where name.Length == 0; // null safe
int numNamesStartingWithB = count each String name in names where name.StartsWith("B"); // null safe

// Subset evaluation ('value' keyword is a reference to each element examined)
String[] nullNames = names where value == null;                              // obtain nulls (not very useful but it's possible)
String[] nonNullNames = names where value is String;                         // obtain String instances
String[] emptyNames = names where (value as String).Length == 0;             // implicit filtering for instances of String via 'as String' (null safe)
String[] namesStartingWithB = names where (value as String).StartsWith("B"); // null safe

// Argument Plurality, assume method print(string)
print(each in names);                                        // print all elements in 'names' (no type specified therefore include nulls)
print(each String in names);                                 // print all instances of class String (skips nulls)
print(each var name in names where name is String);          // same as previous
print(each name in names where name is String);              // same as previous ('var' omitted)
print(each String name in names where name.StartsWith("B")); // null safe
print(each String in names where ord < 10);†                 // print first 10 instances of String (skips nulls)
print(each String in names where ord >= names.Length - 10);  // print last 10 instances of String (skips nulls)

// Progressive filtering using multiple 'where' clauses:
print(each String name in names where name.StartsWith("B") where ord < 10); // print first 10 names that start with "B"
print(each String name in names where name.StartsWith("B") && ord < 10);    // examine first 10 Strings and print each that starts with "B" (different from previous)
print(each String name in names where ord < 10 where name.StartsWith("B")); // examine first 10 Strings and print each that starts with "B"
print(each String name in names where ord < 10 && name.StartsWith("B"));    // same output as previous

// Can non-iterated collection arguments also invoke plurality?¹
print(names);                                                // print all elements in 'names' (includes nulls)
print(names where ord < 10);                                 // print first 10 names (includes nulls)
print(names where ord >= names.Length - 10);                 // print last 10 names (includes nulls)
print(names where (value as String).Length < 6);             // print all short names (implied filtering for String[s], null safe)

†Note that:

Hypothetical C#:
print(each String in names where ord < 10);

Is equivalent to:

Hypothetical C#:
print(each var in (names where value is String) where ord < 10); // ord applies to intermediate collection that has been pre-filtered for String[s]

Not:

Hypothetical C#:
print(each var name in names where name is String && ord < 10); // ord applies to unfiltered collection 'names'

This distinction is important for understanding the determination of 'ord' values, which start at 0 and increment by one per included element ie. method invocation.

Footnotes:
¹ If overload print(string[]) exists it will have precedence over print(string)
 
Last edited:
What if DoStuff is not void? Would you expect either of these to work, or something else?
C#:
SomeType myVar = DoStuff(each int in factors);

IEnumerable<SomeType> myVar = DoStuff(each int in factors);

Nice. I hadn't even thought of how multiple return values would be handled.

Yes, I would expect both of those to work, and that myVar would be an array of SomeType[n], where n is all argument counts multiplied.
 
If I may ask, what is it specifically about the idea that doesn't appeal to you?
I read from left to right rather from inside to out. It also seems to be a very narrow use case that is already easily readable possible with the existing syntax..

..but the chief complaint is that it doesn't seem to save anything other than a couple of chars, or make anything easier to understand or use and introduces a complexity in that it goes against the visual execution order of everything else in the language

But feel free to raise it with the c# team; that's not this forum
 
I would expect both of those to work, and that myVar would be an array of SomeType[n], where n is all argument counts multiplied.
I don't see how they could both work, so maybe you misunderstood. I was assuming that SomeType was the return type of DoStuff. In that case, if you expect an array to be returned (seems like a custom type implementing IEnumerable<T> would be more appropriate) then the second option would be valid but the first couldn't work unless you were going to allow for an implicit First call, in which case why would you be using that syntax in the first place?
 
Back
Top Bottom