morelinq / MoreLINQ

Extensions to LINQ to Objects
https://morelinq.github.io/
Apache License 2.0
3.69k stars 413 forks source link

Purpose of returning the execution of the local function `_`? #480

Closed teejay-87 closed 6 years ago

teejay-87 commented 6 years ago

What's the purpose of returning the execution of the local function _ in MoreLINQ/MoreLinq/Batch.cs ?

return _(); IEnumerable<TResult> _() { // statements... }

It is not backward compatible with C# 6.0 and I see no benefits over directly executing // statements... .

teejay-87 commented 6 years ago

I saw it is quite spread over the whole library. Honestly, can't understand why.

leandromoh commented 6 years ago

Note that the local function can have access to the local variables and also the argument of the enclosing method, so you do not have the work of pass the arguments to the private function (iterator block) as pre-c# 7.

Also, one of the major benefits of local functions is encapsulation -- a local function can be called only from its enclosing type. Note that if you have a private method in your class, any member of the class can invoke the private method.

fsateler commented 6 years ago

@teejay-87 the benefit is that the processing done before the return (and the arguments checking they include) are performed as soon as the method is invoked, whereas the instructions of the local function are only executed at the time the returned IEnumerable<> is iterated over. An example might make this clearer. With C# 6, to achieve the same, one needs:

public static IEnumerable<T> Operator<T>(this IEnumerable<T> src) {
    if (src == null) throw new ArgumentNullException(nameof(src));
    return OperatorImpl(src);
}

private static IEnumerable<T> OperatorImpl<T>(this IEnumerable<T> src) {
    // Note there is no null check
   foreach(var i in src) yield return i; // statements
}

The problem with this is that it creates an unnecessary private function that pollutes the internal namespace (ie, OperatorImpl is callable by Operator2 which it shouldn't). With the C#7 version:

public static IEnumerable<T> Operator<T>(this IEnumerable<T> src) {
    if (src == null) throw new ArgumentNullException(nameof(src));
    return _(); IEnumerable<T> _() {
       // Note there is no null check
       foreach(var i in src) yield return i; // statements
    }
}

Now the implementation is not reachable to other operators, thus increasing encapsulation.

teejay-87 commented 6 years ago

In my opinion you simply DON'T NEED any function.

You can write the statements contained in that function directly in the main method.

The argument checking is done at method call time. The rest is executed while enumerating. That's the purpose of yield return.

teejay-87 commented 6 years ago

In fact, when you write () you are immediately calling and thus executing the function.

The deferrability comes into play ONLY when yield return is called.

fsateler commented 6 years ago

@teejay-87 yes you do. Iterator functions are evaluated lazily. This means they don't actually execute until you enumerate the results:

public static IEnumerable<T> Operator<T>(this IEnumerable<T> src) {
    if (src == null) throw new ArgumentNullException(nameof(src));
    foreach(var i in src) yield return i; // statements
}

var res = Operator(null); // No exception here!
foreach(var item in res) {} // Exception here!

I'm going to close this issue now, as it does not appear to be an issue with MoreLINQ.

teejay-87 commented 6 years ago

I'm sorry.

I really ignored that

When you use the yield keyword in a statement, you indicate that the method, operator, or get accessor in which it appears is an iterator.

So you used two methods to split condition checking and iteration.