This project is read-only.

Unnamed Range Variables (and System.Void)

Topics: C# Language Design
Aug 11, 2014 at 6:50 PM
Hi,

Reading the proposal for records and pattern matching in C#, I noticed that a new wildcard character * is used and it led me to want the possibility to use this same character as the name for range variables in which the remainder of a query has no use for them.

For example, I find that fairly commonly I'll write a sequential query (consecutive SelectMany) such as the following:
var q = from _ in doFirstThing()
        from __ in doSecondThing()
        from ___ in doThirdThing()
        select ___;
where the _ characters are used to denote a range variable that I don't care about.

Note how the length of the ___ grows with each subsequent use. Ugly.

What about the following instead?
var q = from * in doFirstThing()
        from * in doSecondThing()
        from * in doThirdThing()
        select Void;
In the previous example, the compiler would effectively choose internal names for each range variable like $a, $b and $c, or perhaps simply optimize their storage locations away if possible.

As a result of this language feature, it would then also be great to be able to project the System.Void type as the query's output. It would be the BCL alternative to Reactive Extension's System.Unit type, which I find to be used quite often.

If the return types of each of the do* methods are IEnumerable<>, then the type of q is IEnumerable<Void> (assuming that System.Linq is currently in scope).

Thanks,
Dave
Aug 12, 2014 at 11:18 AM
Why would you want to do this? I can't think of any reason why would you want to use LINQ to Objects this way. Or are you using this with a different LINQ provider?

Also, the CLR doesn't support IEnumerable<void> (try it: typeof(IEnumerable<>).MakeGenericType(typeof(void))).
Aug 12, 2014 at 12:00 PM
Edited Aug 12, 2014 at 12:01 PM
The CLR doesn't support IEnumerable<void>
Ah, so it's a CLR limitation. Thanks. I had always just assumed that the C# compiler error was because the C# team figured that using Void as a type would be confusing. Well in this case, it would be nice to have System.Unit in the FCL then.
Why would you want to do this?
For side effects. Though you're correct, I use it primarily with Rx (quite often actually) rather than LINQ to Objects. But I see no usage reason why it shouldn't just be a language feature for query comprehensions in general.

Reactive queries often look like something the following (though mutable state is just one kind of example):
var q = from city in cityChanges
        where city.Added
        from _ in city.SetLatLongAsync(locationClient)
        from __ in city.SetCurrentConditionsAsync(weatherClient)
        where mustAlert(city)
        from alert in generateAlerts(city)
        from subscriber in alertSubscribers
        from ___ in alertClient.SendAsync(alert, subscriber)
        select Unit.Default;

alerts.Add(q);
alerts.Merge().Subscribe(Alerted, AlertsFailed, AlertsCompleted);
As you can see, the problem is worsened by additional operators like where and from with a required range variable, since it's easy to forget how many _'s you're up to at the bottom of the query. :)
var q = from city in cityChanges
        where city.Added
        from * in city.SetLatLongAsync(locationClient)
        from * in city.SetCurrentConditionsAsync(weatherClient)
        where mustAlert(city)
        from alert in generateAlerts(city)
        from subscriber in alertSubscribers
        from * in alertClient.SendAsync(alert, subscriber)
        select void;
Aug 12, 2014 at 12:53 PM
davedev wrote:
The CLR doesn't support IEnumerable<void>
Ah, so it's a CLR limitation. Thanks. I had always just assumed that the C# compiler error was because the C# team figured that using Void as a type would be confusing. Well in this case, it would be nice to have System.Unit in the FCL then.
A real Unit type would be so nice... Every time I write a generic wrapper function, I have to write it twice because void cannot be a generic parameter :
// I know about Stopwatch, but it is missing in Silverlight, and this is just an example.
public static T Time<T>(string name, Func<T> func)
{
    var startDate = DateTime.Now;
    try { return func(); }
    finally { Log(name, DateTime.Now - startDate); }
}
public static void Time(string name, Action action)
{
    var startDate = DateTime.Now;
    try { action(); }
    finally { Log(name, DateTime.Now - startDate); }
}
Of course I could use Time<DBNull> or something to simulate void, but it causes friction about everywhere.
This kind of duplication irks me so much... I wonder if something could be done about it without breaking everything.


About your proposal of from * in, it reminds me of the special meaning of _ in Scala: it is (among other uses) an identifier that means "I don't want to be able to refer to that name again, and don't warn me if it looks unused because that is deliberate". If * could be the same, we would also be able to use it in other places:
void F(int *)
{
    foreach (var * in Enumerable.Range(0, 1000))
        ;
}
But then maybe * would not be the best name.
Aug 12, 2014 at 1:24 PM
Edited Aug 12, 2014 at 1:25 PM
@eldritchconundrum: Agreed, there are other uses for * outside of query comprehensions. I like your parameter example in particular. Here's another:
obs.Subscribe(* => Foo());
But then maybe * would not be the best name.
Yea, it's not ideal. Perhaps it's a bit too ambiguous since it reads like "any". I just wanted some single character though and was inspired by its use elsewhere.

# might work instead, since it's not being used in C# for anything else at the moment.
void F(int #)
{
    obs.Subscribe(# => Foo());
}
Aug 12, 2014 at 1:54 PM
@davedev You're using from/SelectMany() in a way that was never intended. I think what you want is less SelectMany() and more an async version of Observable.Do() (which doesn't seem to exist in Rx), so I think the right solution would be not to allow * in from, but to add a new do operator:
var q = from city in cityChanges
        where city.Added
        do city.SetLatLongAsync(locationClient)
        do city.SetCurrentConditionsAsync(weatherClient)
        where mustAlert(city)
        from alert in generateAlerts(city)
        from subscriber in alertSubscribers
        do alertClient.SendAsync(alert, subscriber)
        select Unit.Default;
But I'm not sure that would be a good fit for Language INtegrated Query. Even the documentation of Observable.Do() makes it sound like it should not be used in normal code:
This method can be used for debugging, logging, etc.
Aug 12, 2014 at 2:25 PM
Edited Aug 12, 2014 at 2:26 PM
@svick: Well, I already have another request for query comprehension keywords:

http://visualstudio.uservoice.com/forums/121579-visual-studio/suggestions/3181196-add-rx-and-ix-query-comprehension-syntax-keywords

But I don't agree that Do is the correct semantics in general for this problem. It just so happens that in my example all uses of SelectMany project into singleton sequences, but that's not a requirement.

Do is for executing a side effect for every value, passing through the value to the next operator.
SelectMany is for executing a projection for every value, with potentially infinite results.
var q = from city in cityChanges
        from _ in city.PopulationCount.Changes
        select Unit.Default;

q.Subscribe(RefreshUI);