-
Notifications
You must be signed in to change notification settings - Fork 4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Feature Request: Recursive Iterators (non-quadratic) #15
Comments
👍 |
It's a nice feature, and we had it on the list when we did IEnumerable, or the release after, not entirely sure. But it is kind of niche feature. I think the syntax was going to be something like:
Erik Meijer had an algorithm to make it near non-recursive. |
@mattwar
Which is still Quadratic. This does something different. Let's use another example. Yielding the permutations of a list of items.
Let's create the public method the initiates the iterator and calls the method.
|
The yield foreach I was referring to was just a syntax. It did not translate to a foreach instruction and required a very different codegen for the iterator methods. |
I think it already has the VB tag, right? Am I confused about something? Cheers, From: Adam Speight [mailto:notifications@github.com] @theoyhttps://github.com/theoy Co-Evolution? Why no VB.net tag? — |
I missed the tag, then replied. Then saw the tag, so removed to comment. DOH! |
Didn't we discuss this in some length on the codeplex forums? If I recall while there was an alternate algorithm which made these scenarios much faster they were also found to be much slower for the common non-nested cases. |
For reference, the paper about "yield foreach" with non-quadratic runtime (part of the Spec# work) http://research.microsoft.com/en-us/projects/specsharp/iterators.pdf |
I think it may be more comfortable to add IEnumerable<BinaryNode<T>> TreeWalk<T>(BinaryNode<T> curr)
{
if (curr == null) yield break;
yield foreach TreeWalk(curr.Left); // Syntax sugar for loop with yield return
yield return curr;
yield foreach TreeWalk(curr.Rigth); // Syntax sugar for loop with yield return
} |
Here #7630 suggested yield foreach yield foreach TreeWalk(curr.Left); // Syntax sugar for loop with yield return |
@AlexRadch |
What is |
I think you mean that void TreeWalk<T>(BinaryNode<T> curr, IList<T> magicIterator)
{
if (curr == null) return;
TreeWalk(curr.Left, magicIterator);
magicIterator.Add(curr);
TreeWalk(curr.Rigth, magicIterator);
} Am I right? |
No. Suggested reading material ( http://www.dreamincode.net/forums/topic/332455-iterators/ ). You should now see that each recursive call (use the foreach yield) is generates a new instance of a ienumerable and ienumerator. It should be possible generate a single instance of ienumerable and ienumerator, that encompasses all of the subsequent recursive method calls. The compiler should capable of building a state machine for a recursive iterator function. It could internally keep a stack of method calls, where it is in each of those methods. Non tail-recursive would potentially result in stack overflow when evaluated. Tail-recursive (along with tail call optimisation) could potentially never return. (infinite iteration). |
|
You may want to read the Microsoft research paper I linked above (which was also mentioned by mattwar) before doing handwaving and talking of magic IEnumerables. You are making too many assumptions without knowing the already presented facts. Non-quadratic iteration (and implementation of Also keep in mind that the paper only presents one possible solution, I know of at least one other possible implementation which is also non-quadratic, but it also needs a different iteration protocol. Also note that even if new interfaces are introduced they can be optional and the iteration protocol can gracefully fall back to the classic iteration for enumerators which don't implement the new interface (at the cost of the quadratic iteration we currently have). |
I've read the paper again, it doesn't require a new interface. |
@AdamSpeight2008 then you didn't understand the code you wrote, nor the paper
That's testing for the new iterator protocol. If its not present then it falls back to quadratic behavior using a simple foreach-loop. Using a class instead of an interface to test for a feature is just an implementation detail. It's been a year or so since I read the paper so I didn't remember their proposed implementation exactly, but the concept is the same. If you want your custom enumerables support the new protocol they need to implement an NestedEnumerable<T> instead of an IEnumerable<T>. When using the |
I made hyperloop https://github.com/AlexRadch/YieldForEachDN/blob/master/Src/YieldForEachApp/Hyperloop.cs to make recursive yielded loops without quadratic runtime performance. For example next yielded method have quadratic runtime performance because it have recursion. static IEnumerable<int> FromToNestedStandart(int b, int e)
{
if (b > e)
yield break;
yield return b;
foreach(var v in FromToNestedStandart(b + 1, e))
yield return v;
} To make it with liner runtime performance you should rewrite it with Hyperloop usage: static IEnumerable<int> FromToNestedHyperloopWithTail(int b, int e)
{
var hl = new Hyperloop<int>();
hl.AddLoop(FromToNestedHyperloopWithTailLoop(b, e, hl).GetEnumerator());
return hl;
}
private static IEnumerable<int> FromToNestedHyperloopWithTailLoop(int b, int e, IOldHyperloop<int> hl)
{
if (b > e)
yield break;
yield return b;
// yield foreach replaced on hyperloop
hl.GetHyperloop().AddTail(FromToNestedHyperloopWithTailLoop(b + 1, e, hl).GetEnumerator());
}
|
I wonder if local function definitions could be used by the compiler to generate that linear state machine. |
@paulomorgado As far as I understood it, local functions add no benefit for the compiler because its only a language feature and not an IL feature, the compiler could already generate the IL for local functions if he needed it. Anyways, the whole reason why you need a state machine in the first place is because the control flow is non-local (you return to the caller between states) so local function definitions are unlikely to help. |
To reduce quadratic performance in recursive yielded methods you should create fist loop (I called it hyperloop) and send to that hyper loop workitems without creating local loop in local loop in local loop and so on for each recursive call. Here hyperloop code https://github.com/AlexRadch/YieldForEachDN/blob/master/Src/YieldForEachApp/Hyperloop.cs Hyperloop execute recursive work in one loop without layered loops for each recursive call and then return control to continue execution if AddLoop() was used. For tail recursion you can use AddTail() so it does not return control. Next code create Hyperloop and add first loop to them static IEnumerable<int> FromToNestedHyperloopWithTail(int b, int e)
{
var hl = new Hyperloop<int>();
hl.AddLoop(FromToNestedHyperloopWithTailLoop(b, e, hl).GetEnumerator());
return hl;
} Next code is like usual yielded method with recursion but it does not create layered loops for each recursive call but add loops to Hyperloop and Hyperloop execute work without layers (than speedup performance from quadratic to linear) static IEnumerable<int> FromToNestedHyperloopWithTail(int b, int e)
{
var hl = new Hyperloop<int>();
hl.AddLoop(FromToNestedHyperloopWithTailLoop(b, e, hl).GetEnumerator());
return hl;
}
private static IEnumerable<int> FromToNestedHyperloopWithTailLoop(int b, int e, IOldHyperloop<int> hl)
{
if (b > e)
yield break;
yield return b;
// yield foreach replaced on hyperloop
hl.GetHyperloop().AddTail(FromToNestedHyperloopWithTailLoop(b + 1, e, hl).GetEnumerator());
} |
@weltkante @paulomorgado VB has iterator / async lambda functions, they won't help in this situation. As any state information held by them is local to that lambda. Iterator Function Foo() As IEnumerable(Of int)
Dim lamba = Iterator Function()
Yield 0
Yield 1
Yield 2
' These "yield" from the lambda not from the enclosing function "Foo"
End Function ` |
@weltkante, local functions can be reentrant and do not need a delegate invocation. There's a lot that can be done and done better with local functions then with delegates. Of course the compiler can generate the IL. It already does. |
We are now taking language feature discussion on https://github.com/dotnet/csharplang for C# specific issues, https://github.com/dotnet/vblang for VB-specific features, and https://github.com/dotnet/csharplang for features that affect both languages. |
Issue moved to dotnet/csharplang #378 via ZenHub |
Current if you want write a recursive based iterator function.
it ends up being Quadratic runtime
If I could express the Iterator / Yielder as a parameter I could linearise the runtime.
The text was updated successfully, but these errors were encountered: