Recursiont 1.0.0
dotnet add package Recursiont --version 1.0.0
NuGet\Install-Package Recursiont -Version 1.0.0
<PackageReference Include="Recursiont" Version="1.0.0" />
paket add Recursiont --version 1.0.0
#r "nuget: Recursiont, 1.0.0"
// Install Recursiont as a Cake Addin #addin nuget:?package=Recursiont&version=1.0.0 // Install Recursiont as a Cake Tool #tool nuget:?package=Recursiont&version=1.0.0
Recursion't
Recursion't is a C# library that allows efficiently implementing recursive algorithms without worrying for stack overflows. It achieves this by creatively using async
method builders.
Features
- Lightweight If recursion in a function does not go too deep, Recursion't will not allocate.
- Single-threaded A common way to tackle infinite recursion is by keeping recursing in a different thread. Recursion't runs entirely in one thread.
- Memory-efficient Recursion't uses techniques like object pooling to keep steady-state memory allocations low.
How to use
You can convert a recursive function to use Recursion't by following these steps:
- Add
using Recursiont;
to your code. - Make the function
async
. - Change the function's return type to
RecursiveOp
if it returnsvoid
, or toRecursiveOp<T>
if it returnsT
. await
the recursive calls.- Create a helper function that uses
RecursiveRunner.Run
that serves as the entry point for your recursive function.
To see an example, imagine the following recursive function that computes the Ackermann function:
static uint Ackermann(uint m, uint n)
{
if (m == 0)
{
return n + 1;
}
if (n == 0)
{
return Ackermann(m - 1, 1);
}
return Ackermann(m - 1, Ackermann(m, n - 1));
}
If you call Ackermann(4, 1)
your code will crash with a stack overflow. Here's the same function, rewritten to use Recursion't:
using Recursiont;
static uint Ackermann(uint m, uint n)
{
return RecursiveRunner.Run(AckermannImpl, m, n);
static async RecursiveOp<uint> AckermannImpl(uint m, uint n)
{
if (m == 0)
{
return n + 1;
}
if (n == 0)
{
return await AckermannImpl(m - 1, 1);
}
return await AckermannImpl(m - 1, await AckermannImpl(m, n - 1));
}
}
Now, calling Ackermann(4, 1)
will not cause stack overflows but will still take an extraordinary amount of time to complete.
RecursiveRunner.Run
has overloads that accept functions with up to six parameters. If your function has more you can create a lambda that accepts a tuple:
RecursiveRunner.Run(static ((int X1, int X2, int X3, int X4, int X5, int X6, int X7) x) =>
F(x.X1, x.X2, x.X3, x.X4, x.X5, x.X6, x.X7), (0, 0, 0, 0, 0, 0, 0));
static async RecursiveOp F(int x1, int x2, int x3, int x4, int x5, int x6, int x7)
{
// ...
}
The rules
Using Recursion't comes with a set of simple rules that you have to follow.
The term "recursive functions" refers to methods that return
RecursiveOp
orRecursiveOp<T>
.
- Immediately
await
after calling recursive functions. Recursion't is not a coroutine library. The following code might either fail or produce unexpected results:
```csharp
static async RecursiveOp Foo()
{
RecursiveOp op1 = Bar();
RecursiveOp op2 = Bar();
await op1;
await op2;
}
```
- Do not call recursive functions outside of other recursive functions. The only way to call a recursive function from a non-recursive one is by passing a delegate to it in
RecursiveRunner.Run
. Consider the following code:
```csharp
// ❌ Wrong
F();
// ✅ Correct
RecursiveRunner.Run(F);
static async RecursiveOp F()
{
// ...
}
```
- Exercise caution when using
RecursiveRunner.Run
inside recursive functions. Let's say you have the following two functions:
```csharp
static void A()
{
RecursiveRunner.Run(AImpl);
static async RecursiveOp AImpl()
{
// ...
}
}
static void B()
{
RecursiveRunner.Run(BImpl);
static async RecursiveOp BImpl()
{
// ...
A();
// ...
}
}
```
`B` is inefficient and -if `A` and `B` are mutually recursive- downright dangerous and prone to stack overflows. The way Recursion't avoids stack overflows is by storing the stack to the heap when it goes too deep, up to the last point where `RecursiveRunner.Run` was called. Using it in recursive functions limits how many stack frames can be moved to the heap.
What you can do is to make `A()` directly return `RecursiveOp` and call and `await` it from B.
```csharp
static async RecursiveOp A()
{
// ...
}
static void B()
{
RecursiveRunner.Run(BImpl);
static async RecursiveOp BImpl()
{
// ...
await A();
// ...
}
}
```
> Unless you are doing things like recursive user callbacks, you don't have to expose recursive functions in a public API. You can create a public wrapper function that calls `RecursiveRunner.Run` and keep Recursion't as an implementation detail.
Maintainer(s)
Product | Versions Compatible and additional computed target framework versions. |
---|---|
.NET | net5.0 was computed. net5.0-windows was computed. net6.0 is compatible. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 was computed. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 was computed. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. |
.NET Core | netcoreapp2.0 was computed. netcoreapp2.1 was computed. netcoreapp2.2 was computed. netcoreapp3.0 was computed. netcoreapp3.1 was computed. |
.NET Standard | netstandard2.0 is compatible. netstandard2.1 is compatible. |
.NET Framework | net461 was computed. net462 was computed. net463 was computed. net47 was computed. net471 was computed. net472 was computed. net48 was computed. net481 was computed. |
MonoAndroid | monoandroid was computed. |
MonoMac | monomac was computed. |
MonoTouch | monotouch was computed. |
Tizen | tizen40 was computed. tizen60 was computed. |
Xamarin.iOS | xamarinios was computed. |
Xamarin.Mac | xamarinmac was computed. |
Xamarin.TVOS | xamarintvos was computed. |
Xamarin.WatchOS | xamarinwatchos was computed. |
-
.NETStandard 2.0
- System.Threading.Tasks.Extensions (>= 4.5.4)
-
.NETStandard 2.1
- No dependencies.
-
net6.0
- No dependencies.
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
Version | Downloads | Last updated |
---|---|---|
1.0.0 | 470 | 1/7/2024 |