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
 
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?

Yes, my interpretation was loose.

If SomeType is the same in both statements you provided then what I said is incorrect.

To clarify:

If DoStuff(int) returned an int then:
Yes to the first if SomeType was int[].
Yes to the second if SomeType was int.

In a conventional foreach loop you have access to each separate return value, so receiving an array instead might seem awkward.

However, argument plurality can be used again to iterate over the array of returned values.

So something like this would work:

Hypothetical C#:
// Assume print(string), firstNames and lastNames are string[]s or IEnumerable<string>s
// Print all first-last name combinations
print(each string in string.Format("{0} {1}", each in firstNames, each in lastNames)); // string.Format(string, params object[]) so types don't need to be specified

// Or, if argument plurality mechanism extends to arg of type IEnumerable<string> applying to func(string)
print(string.Format("{0} {1}", each in firstNames, each in lastNames));(1) // expression passed to 'print' evaluates to a string[]

// Same as
string[] nameCombinations = string.Format("{0} {1}", each in firstNames, each in lastNames);
print(nameCombinations);

If you wanted to print an enumerated list using the for (each e in c) {...} form:

Hypothetical C#:
int n = 0;
for (each f in firstNames) // type not needed
  for (each l in lastNames) // type not needed
    print(string.Format("{0}. {1} {2}", ++n, f, l));

It's tempting to try something like this:

Hypothetical C#:
int n = 0;
print(string.Format("{0}. {1} {2}", ++n, each in firstNames, each in lastNames)); // would this work or would every enumeration be '1.'?

Another way of putting it:

Hypothetical C#:
// Assume print(int indent, string text)
print(GetIndent(), each string in names); // is GetIndent() evaluated (called) once, or for each string in names?

If print(<expression>, each string in names); is strictly equivalent to:

Hypothetical C#:
for (each string name in names)
  print(<expression>, name);

Then the answers are 'yes it would work' and 'GetIndent() gets evaluated for each string in names'.

If you wanted GetIndent() to be evaluated only once (or possibly not at all if names contains no strings, resulting in zero invocations):

Hypothetical C#:
int indent;
print(indent = GetIndent(), each string in names); // indent is possibly undefined after this call

So this raises a question about argument evaluation.

Again, assuming strict equivalence with:

Hypothetical C#:
for (each string name in names)
  print(<expression>, name); // no iterations means <expression> does not get evaluated

What seems required is a two-stage evaluation of arguments. The expression 'each string name in names' must be evaluated prior to <expression>, and <expression> will not be evaluated at all if any argument plurality counts are 0.

Hmm...
 
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

Hypothetical C#:
DoStuffFor(each var e in c where filter(e));

Is arguably more concise than:

C#:
foreach (var e in c)
  if (filter(e))
    DoStuffFor(e);

However, I see what you mean about the order of logic being different from that of a conventional for loop. I didn't consider the importance of that.

Also, since 'if' already exists, adding a 'where' clause would introduce some redundancy, ie. two ways of doing the same thing.

It seems an unfortunate trade-off because 'where' would be handy for obtaining subsets and filtering collections passed as arguments.
 
But we can already say foreach(var e in c.Where(filter)) DoStuff(e) or if dostuff takes an e and prepares a response then c.Where(filter).Select(DoStuff) .. The left to right, top to bottom-ness suits my way of reading, like extension methods do

Any extension method can be called like either of:

C#:
TheStaticClass.TheExtensionMethod(theArgument)
theArgument.TheExtensionMethod()

One is ltr, one is inner to outer, especially with chaining:

C#:
Ext3(Ext2(Ext1(myX)))

myX.Ext1().Ext2().Ext3()

The latter works much better for me. This syntax you propose where "sometimes a collection is passed into X and other times X is invoked for every element in the collection" doesn't
 
You seem to have this interesting rule of if you specify the type, then the reference type will not be null. Eg.
C#:
each var name in names    // may return nulls along with strings
each string name in names    // only return non-null strings

But what happens to value types which have the conceptual equivalent of null?
C#:
ICollection<double> collectionOfDouble = GetDoubles();
print(each double value in collectionOfDouble);
Will it also return Double.Nan?

C#:
ICollection<UIntPtr> collectionOfUIntPtr = GetUIntPtrs();
print(each UIntPtr ptr in collectionOfUIntPtr);
Will it also return UIntPtr.Zero?

And then happens if those collections contain nullable types?
C#:
ICollection<DateTime?> collectionOfNullableDateTime = GetNullableDateTimes();

and what happens if the collection itself is null?
C#:
var string[] names = null;
print(each string name in names);
 
You seem to have this interesting rule of if you specify the type, then the reference type will not be null. Eg.
C#:
each var name in names // may return nulls along with strings
each string name in names // only return non-null strings
But what happens to value types which have the conceptual equivalent of null?
C#:
ICollection<double> collectionOfDouble = GetDoubles();
print(each double value in collectionOfDouble);
Will it also return Double.Nan?

C#:
ICollection<UIntPtr> collectionOfUIntPtr = GetUIntPtrs();
print(each UIntPtr ptr in collectionOfUIntPtr);
Will it also return UIntPtr.Zero?

Right, so if I specify a type then it means I want every instance of that type (and derived classes of that type), so this excludes instances of base classes and nulls.

I thought it would be a concise way of accessing type subsets within a collection.

Some examples (divorced from 'Argument Plurality'):

Hypothetical C#:
// Assume print(object) and persons is Person[] containing everyone in a community

foreach (var e in persons) // no type specified, therefore include all elements (nulls too)
  print(e);

foreach (e in persons) // same as previous, preferred form
  print(e);

foreach (Person p in persons) // filters for instances of Person (skips nulls)
  print(p.Age); // safe

foreach (FireFighter ff in persons) // filters for instances of FireFighter (ie. 'Give me each FireFighter in that collection')
  print(ff.VolumeAir); // safe

foreach (PoliceOfficer po in persons) // filters for instances of PoliceOfficer (ie. 'Give me each PoliceOfficer in that collection')
  print(po.NumBullets); // safe

foreach (PoliceOfficer? po in persons) // filters for instances of PoliceOfficer or null
  print(po.NumBullets); // not safe

foreach ((FireFighter || PoliceOfficer) p in persons) // specifying multiple class types (maybe)
  print(p.YearsOfService); // safe, all specified types must guarantee member and member must be of same type for all or compiler error

So what happens in the case where there's a three-way conceptual split or 'trichotomy' such as Double (with numeric value) vs null vs Double.NaN?

I would treat this as an exceptional case where an additional filtering step is required:

Hypothetical C#:
foreach (Double d in doubles) // skips nulls but includes Double.NaN
  if (d != Double.NaN)        // second filtering
    print(d);

Same for:

Hypothetical C#:
ICollection<UIntPtr> collectionOfUIntPtr = GetUIntPtrs();
foreach (UIntPtr ptr in collectionOfUIntPtr) // skips nulls but includes UIntPtr.Zero
  if (ptr != UIntPtr.Zero)                   // second filtering
    print(ptr);

And then happens if those collections contain nullable types?
C#:
ICollection<DateTime?> collectionOfNullableDateTime = GetNullableDateTimes();
a

I think this would be handled same as any other class:

Hypothetical C#:
ICollection<DateTime?> collectionOfNullableDateTime = GetNullableDateTimes();

foreach (DateTime dt in collectionOfNullableDateTime) // skips nulls
  print(dt);

foreach (DateTime? dt in collectionOfNullableDateTime) // includes nulls
  print(dt);

and what happens if the collection itself is null?
C#:
var string[] names = null;
print(each string name in names);

If collection is null then a NullReferenceException should be thrown.
 
Last edited:
double and UIntPtr can never be null because they are value types. But they have specific values which are functionally null. This is why I brought up that specific scenario.
 
double and UIntPtr can never be null because they are value types. But they have specific values which are functionally null. This is why I brought up that specific scenario.

I actually didn't know assigning NaN to a primitive was possible (it is):

C#:
double d = Double.NaN; // valid

Double.NaN values would be included when double or Double type is specified:

C#:
foreach (double d in doubles) // includes Double.NaN
  print(d * 2);               // safe, Double.NaN * 2 results in Double.NaN

In other words, all values (elements) would be included for non-Nullable value types:

C#:
foreach (<value type> v in values) // includes all elements
  DoStuff(v);                      // v is never null

And for Nullable value types:

Hypothetical C#:
ICollection<bool?> collectionOfNullableBool = new bool?[] { true, false, null };

foreach (bool b in collectionOfNullableBool) // skip nulls
  DoStuff(b);

foreach (bool? b in collectionOfNullableBool) // include nulls
  DoStuff(b);

The examples you chose are good ones.
 
Last edited:
double and UIntPtr can never be null because they are value types. But they have specific values which are functionally null. This is why I brought up that specific scenario.

I understand what you mean here but strictly speaking it's not true (the 'functionally null' part):

C#:
double d = Double.NaN;
Double? D = d; // conversion to null?

print("D is " + D.ToString()); // outputs 'D is NaN'; Double.IsNaN(D) == true (D != null)
 
Yes, it is not true. But I was trying to point out that you seem to have a special case for skipping null objects, you should also have a special case for handling "null" value types.

I don't understand why your new proposed C# feature has this thing where if you give the type name, it will skip the null objects, but if you don't give the type name, it will include the nulls?

If you go down this route, what type name will you use when the type is anonymous and/or when the type name is complicated due to the result of a LINQ query? Ex.
C#:
var items = repository
    .Students
    .Select(s => new { Last = Surname, Initial = First[0] })
    .GroupBy(s => s.Initial);
 
Yes, it is not true. But I was trying to point out that you seem to have a special case for skipping null objects, you should also have a special case for handling "null" value types.

As you've mentioned there are values such as Double.NaN that are 'conceptually null' or at least 'exceptional' in that they don't fit with the other 'normal' values. For logical consistency the scheme should include a way of skipping over such values, one that is counterpart to the proposed null-skipping (instance-type-filtering) mechanism provided for object and nullable value type collections.

This could be right, I think it's something that would become clear if the scheme was ever tried out in practice. My current position is that these values (Double.NaN, etc.) are not 'conceptually equivalent to null', but I could be wrong. I'm really just not sure on this particular point.

One of the distinctions for me is: the value Double.NaN is clearly associated with double, but the value null is not associated with any object or nullable type.

I don't understand why your new proposed C# feature has this thing where if you give the type name, it will skip the null objects, but if you don't give the type name, it will include the nulls?

I don't know how to answer this other than by saying that skipping nulls when a type is specified is fundamental to the scheme being proposed here. If nulls were included when type was specified then most of the examples I've given wouldn't work. This question gives me the feeling I'm missing something obvious... am I?

If you go down this route, what type name will you use when the type is anonymous and/or when the type name is complicated due to the result of a LINQ query? Ex.
C#:
var items = repository
.Students
.Select(s => new { Last = Surname, Initial = First[0] })
.GroupBy(s => s.Initial);

I'm sure you have a good point here I just need help understanding the second question before attempting to answer this one.

I'll try to put everything together below here as plainly as I can and maybe it will clarify.

So currently in C#, the type you specify in a foreach loop defines the type of the identifier and nothing else. If you use 'var' then the type is inferred from the collection element type.

My idea is that in addition to the above, the type you specify should also act as a qualifier because 'why would I want anything else but instances of the type I specified?'. Additionally, in the case where you don't wish to specify a type (ie. 'var') you would be able to omit var because it should be the default (this is a relatively minor point, of course). When you don't specify a type (by either omitting type or using 'var') you get everything in the collection, including nulls (classic C# foreach behavior).

For example:

C# and Hypothetical C# (same):
// Assume persons is ICollection<Person>
foreach (var p in persons) // no type specified therefore no filtering, ie. 'give me all the elements'
  DoStuff(p);              // p here is of type Person (inferred from persons) but may or may not be an instance of Person (could be null)

If I wanted to count instances of PoliceOfficer in persons then I would specify PoliceOfficer as type:

Hypothetical C#:
foreach (PoliceOfficer in persons) // filtering for instances of PoliceOfficer (identifier omitted because not needed)
  ++numPoliceOfficers;

The previous code example would be equivalent to the following in conventional C#:

C#:
foreach (var p in persons)
  if (p is PoliceOfficer)
    ++numPoliceOfficers;

I will try to formalize what is being proposed in this 'foreach type filtering and syntax embellishment' scheme (there are some new concepts).

Existing C# foreach syntax:

C#:
foreach (<type> <identifier> in <collection>) {...} // type and identifier required

Proposed syntax (type is now a qualifier, ie. type_qualifier):

Hypothetical C#:
foreach ([type_qualifier] [identifier] in <collection>) {...} // type_qualifier and identifier are both optional

Examples:

Hypothetical C#:
// Assume persons is ICollection<Person>

foreach (p in persons) // type_qualifier omitted therefore include all elements (nulls too)
  DoStuff(p);          // p is type Person but not necessarily an instance of Person (could be null)

// Omitting both type_qualifier and identifier
foreach (in persons) // Possible. Useful?
  DoStuff();

// Count nulls in persons
foreach (null in persons) // type_qualifier specified as 'null' (formally, type would be 'Null' because 'null' is a value but this works too)
  ++numNulls;

// Count non-null elements (object instances)
foreach (object in persons)
  ++numObjects;

// Tally weight of all Person[s]
foreach (Person p in persons) // p is type 'Person' and p value is Person (instance of)
  totalWeight += p.Weight;    // safe

// Count PoliceOfficer[s] in persons
foreach (PoliceOfficer in persons) // identifier omitted
  numPoliceOfficers++;

// Tally bullets of all PoliceOfficer[s]
foreach (PoliceOfficer po in persons)
  totalNumBullets += po.NumBullets; // safe

// Count PoliceOfficer[s] and nulls in persons
foreach (PoliceOfficer? in persons) // identifier omitted
  numPoliceOfficersAndNulls++;

And (possibly) the following syntax for specifying value literal qualifiers:

Hypothetical C#:
foreach ([value literal] in <collection>) {...}

Examples:

Hypothetical C#:
// Assume factors is ICollection<int>
// Count number of 27s in 'factors'
foreach (27 in factors)
  ++num27s;

// Assume names is ICollection<string>
// Count instances of string literal "Tom" in 'names'
foreach ("Tom" in names)
  ++numToms;

And (possibly) the following syntax for invoking extension method(s):

Hypothetical C#:
foreach (([type_qualifier] [identifier] in <collection>).extensionMethod) {...} // type_qualifier and identifier are both optional

Example:

Hypothetical C#:
foreach((string in names).Print); // print names via String extension method 'Print' (null safe)

And (possibly) the following syntax for specifying a filter extension method (via '?'):

Hypothetical C#:
foreach (([type_qualifier] [identifier] in <collection>).filterExtensionMethod ?) {...} // type_qualifier and identifier are both optional

Examples:

Hypothetical C#:
bool IsBloke(string name) // Implemented as extension method of String
{
  return "Tom".Equals(name) || "Dick".Equals(name) || "Harry".Equals(name);
}

foreach((string in names).IsBloke ?) // tally blokes
  numBlokes++;

foreach(IsBloke(string in names) ?) // equivalent to previous
  numBlokes++;

foreach((string name in names).IsBloke ?) // print names of blokes
  print(name);

foreach(IsBloke(string name in names) ?) // equivalent to previous
  print(name);

// 'Inline' form
foreach((string in names).IsBloke ? numBlokes++); // tally blokes
foreach((string name in names).IsBloke ? print(name)); // print names of blokes

The examples above covered foreach iteration over collections of objects.

What about iterating over collections of nullable value types?

Hypothetical C#:
ICollection<bool?> nullableBools = new bool?[] { true, false, null };

foreach (bool b in nullableBools) // skip nulls
  DoStuff(b);

foreach (bool? b in nullableBools) // includes nulls
  DoStuff(b);

foreach(var b in nullableBools) // equivalent to previous
  DoStuff(b);

foreach(b in nullableBools) // equivalent to previous
  DoStuff(b);

And iterating over collections of non-nullable value types:

Hypothetical C#:
ICollection<double> doubles = new double[] { 0, 1d, 2d, 3d, Double.NaN };

foreach (double d in doubles) // includes Double.NaN
  print(d * 2);               // safe, Double.NaN * 2 = Double.NaN

foreach (var d in doubles) // equivalent to previous
  print(d * 2);

foreach (d in doubles) // equivalent to previous
  print(d * 2);

In other words, all values (elements) would always be included for non-nullable value types:

Hypothetical C#:
foreach ([type] v in nonNullableValues) // includes all elements
  DoStuff(v);                           // v is never null
 
Last edited:
Some things in my previous post were incorrect.

I wanted to put up a few more examples while I work on correcting them:

Hypothetical C#:
ICollection<SpaceMarine> squad = GetSquad();

foreach ((in squad).FireAt(alien)); // FireAt is method of SpaceMarine (type inferred from 'squad', not null-safe)

foreach (var sm in squad) sm.FireAt(alien); // C# equivalent to previous

foreach ((in squad)?.FireAt(alien)); // null-safe version

foreach (var sm in squad) sm?.FireAt(alien); // C# equivalent to previous

If squad is ICollection<Person> (ie. can contain more than just SpaceMarine):

Hypothetical C#:
foreach ((SpaceMarine in squad).FireAt(alien)); // FireAt is method of SpaceMarine (null-safe)

foreach (var p in squad) // C# equivalent to previous
  if (p is SpaceMarine)
    (p as SpaceMarine).FireAt(alien);

Secondary filtering by method:

Hypothetical C#:
// Assume squad is ICollection<Person>

foreach ((in squad).IsDead() ? ++numFatalities); // IsDead is a method of Person (not null-safe)

foreach ((in squad)?.IsDead() ? ++numFatalities); // null-safe version

foreach ((SpaceMarine in squad).IsDead() ? ++numFatalities); // also null-safe

foreach ((SpaceMarine sm in squad).IsDead() ? print(sm)); // print dead SpaceMarine[s]

Ternary form:

Hypothetical C#:
// Assume squad is ICollection<Person>

foreach ((SpaceMarine sm in squad).Ammo > 0 ? sm.FireAt(alien) : sm.MeleeAttack(alien)); // null-safe

foreach (var p in squad) // C# equivalent to previous
{
  if (p is SpaceMarine)
  {
    SpaceMarine sm = p as SpaceMarine;

    if (sm.Ammo > 0)
      sm.FireAt(alien);
    else
      sm.MeleeAttack(alien);
  }
}
 
Last edited:
The foreach Instance-Type-Filtering and Syntax Enhancement Specification 2.0 makes the following important change (to previous spec):

Basic Syntax:

Hypothetical C#:
foreach ([type_qualifier] [identifier] in ICollection<T>) {...} // type_qualifier and identifier both optional

Omitting <type_qualifier> is the same as specifying var.

Specifying var means <type_qualifier> will be inferred from the collection (ie. type T).

Elements that are not instances of <type_qualifier> are skipped during iteration.

This means the behavior of var has been inverted (from previous spec) so that omitting <type_qualifier> or specifying var will skip nulls.

Therefore:

Hypothetical C#:
ICollection<SpaceMarine> squad = GetSquad();

foreach ((in squad).FireAt(alien)); // 'FireAt' is method of SpaceMarine (type inferred from collection, instances of SpaceMarine filtered for, null-safe)

foreach ((var in squad).FireAt(alien)); // equivalent to previous

foreach ((SpaceMarine in squad).FireAt(alien)); // same effect as previous, explicit type_qualifier

ICollection<object> squad = GetSquadObjects(); // squad is now a 'grab bag' containing Animal[s], SpaceMarine[s], Person[s], Items[s], Weapon[s], Vehicles[s], etc.

foreach ((in squad).FireAt(alien)); // compiler error: 'FireAt' is not method of 'Object' (type inferred from collection)

foreach ((var in squad).FireAt(alien)); // equivalent to previous

foreach ((SpaceMarine in squad).FireAt(alien)); // compiles ok (safe)

foreach ((SpaceMarine? in squad).FireAt(alien)); // includes nulls (not safe)

foreach ((SpaceMarine? in squad)?.FireAt(alien)); // includes nulls but only invokes 'FireAt' on instances (safe)

ICollection<Person> persons = GetPersons();

foreach (null in persons) // count number of nulls in 'persons'
  ++numNulls;

foreach (in persons) // count instances of Person (type inferred from collection, skip nulls)
  ++numPersons;

foreach (var in persons) // equivalent to previous
  ++numPersons;

foreach (Person in persons) // same effect as previous, explicit type_qualifier
  ++numPersons;

foreach (Person? in persons) // count instances of Person or null
  ++numPersonsOrNulls;
 
Last edited:
I suggest IEnumerable<T> instead of ICollection<T> just like most of the framework.
 

Latest posts

Back
Top Bottom