language-ext
language-ext copied to clipboard
Fix Support for Collection Expressions
This change fixes support for collection expressions like:
Arr<int> arr = [1, 5, 17];
Lst<int> lst = [5, ..arr, 19];
for
-
Arr<T>
-
Lst<T>
-
Seq<T>
-
Set<T>
I've chosen these types because the compiler thinks the collection expressions are already supported for these (it compiles), but the resulting collections are always empty.
Collection Initializers Unaffected
This does not fix collection initializers like:
var arr = new Arr<int> { 1, 5, 17 };
This still results in an empty arr.
Implementation
Implemented create methods according to https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/proposals/csharp-12.0/collection-expressions#create-methods see commit https://github.com/louthy/language-ext/commit/9fff3cc9b6ddfb91fa251b1e6a984e780e288761
There's also tests for this, see https://github.com/louthy/language-ext/commit/45059367ef5e895a86eabddcb3918d08da85bd2e
Performance Considerations for Set<T>
The ReadOnlySpan<T>
passed to the create methods does not implement IEnumerable<T>
and thus cannot be passed to existing constructors of Set<T>
.
I've compared two options:
-
ReadOnlySpan<T>.ToArray()
-
foreach
loop overReadOnlySpan<T>
which performsSet.TryAdd
Benchmarking showed that the loop is faster for 0 or 1 elements. After that, ToArray()
is significantly faster.
Method | N | Mean | Error | StdDev | Rank |
---|---|---|---|---|---|
CreateLoopSpan | 0 | 12.50 ns | 0.278 ns | 0.760 ns | 1 |
CreateCopySpanToArray | 0 | 37.68 ns | 0.866 ns | 2.512 ns | 2 |
CreateLoopSpan | 1 | 57.02 ns | 1.170 ns | 3.042 ns | 3 |
CreateCopySpanToArray | 1 | 65.10 ns | 1.321 ns | 3.570 ns | 4 |
CreateLoopSpan | 2 | 232.58 ns | 4.624 ns | 9.445 ns | 6 |
CreateCopySpanToArray | 2 | 163.08 ns | 3.289 ns | 8.664 ns | 5 |
CreateLoopSpan | 3 | 400.38 ns | 8.034 ns | 21.993 ns | 8 |
CreateCopySpanToArray | 3 | 256.85 ns | 5.145 ns | 13.734 ns | 7 |
Method | N | Mean | Error | StdDev | Rank |
---|---|---|---|---|---|
CreateLoopSpan | 0 | 12.69 ns | 0.279 ns | 0.618 ns | 1 |
CreateCopySpanToArray | 0 | 37.90 ns | 0.751 ns | 1.741 ns | 2 |
CreateLoopSpan | 3 | 371.38 ns | 7.354 ns | 15.023 ns | 4 |
CreateCopySpanToArray | 3 | 267.78 ns | 5.362 ns | 15.210 ns | 3 |
CreateLoopSpan | 7 | 1,880.21 ns | 36.439 ns | 77.655 ns | 6 |
CreateCopySpanToArray | 7 | 1,048.89 ns | 20.921 ns | 51.319 ns | 5 |
CreateLoopSpan | 50 | 32,824.62 ns | 651.136 ns | 1,522.010 ns | 8 |
CreateCopySpanToArray | 50 | 17,175.47 ns | 343.452 ns | 880.400 ns | 7 |
Method | N | Mean | Error | StdDev | Rank |
---|---|---|---|---|---|
CreateLoopSpan | 100 | 89.48 us | 1.758 us | 2.347 us | 2 |
CreateCopySpanToArray | 100 | 42.16 us | 0.836 us | 2.004 us | 1 |
CreateLoopSpan | 1000 | 1,529.97 us | 30.239 us | 58.260 us | 4 |
CreateCopySpanToArray | 1000 | 768.68 us | 14.868 us | 18.260 us | 3 |
CreateLoopSpan | 10000 | 23,371.89 us | 461.683 us | 867.153 us | 6 |
CreateCopySpanToArray | 10000 | 11,363.98 us | 209.157 us | 436.588 us | 5 |
CreateLoopSpan | 100000 | 391,907.57 us | 7,751.993 us | 10,348.688 us | 8 |
CreateCopySpanToArray | 100000 | 168,161.03 us | 3,542.300 us | 10,276.854 us | 7 |
Implementation for ReadOnlySpan<T>.ToArray()
public static Set<T> create<T>(
ReadOnlySpan<T> items) =>
new Set<T>(items.ToArray());
Implementation with foreach
loop
public static Set<T> create<T>(
ReadOnlySpan<T> items)
{
var set = empty<T>();
foreach (var item in items)
{
set = set.TryAdd(item);
}
return set;
}
Implementation of Benchmark
See https://github.com/louthy/language-ext/pull/1323#issuecomment-2092119979
Benchmark
using System;
using BenchmarkDotNet.Attributes;
namespace LanguageExt.Benchmarks
{
[RPlotExporter, RankColumn]
[GenericTypeArguments(typeof(int))]
[GenericTypeArguments(typeof(string))]
public class SetBenchmarks<T>
{
[Params(0, 1, 2, 3, 10, 100, 1000, 10000, 100000)]
public int N;
T[] values;
[GlobalSetup]
public void Setup()
{
values = ValuesGenerator.Default.GenerateUniqueValues<T>(N);
}
[Benchmark]
public Set<T> CreateLoopSpan()
{
var span = new ReadOnlySpan<T>(values);
return Set.create(span);
}
[Benchmark]
public Set<T> CreateCopySpanToArray()
{
var span = new ReadOnlySpan<T>(values);
var array = span.ToArray();
return new Set<T>(array);
}
}
}
Note that here I changed Set.create(span)
implementation to be:
[Pure]
public static Set<T> create<T>(
ReadOnlySpan<T> items)
{
var set = empty<T>();
foreach (var item in items)
{
set = set.TryAdd(item);
}
}
@louthy I'd be very happy if you could create a v4 release with this change. I really like what you're doing with v5 but unfortunately we still have a dependency on .net framework in our project - we're working hard at getting rid of it, but it will take another ~2 years :-( Including this in a v4 Release would go a long way in convincing my boss that it's worth for us to keep up our monthly donations to you (like I said, we will not benefit from v5 for a long time).
Thank you!
I'm really sorry, but I don't have the bandwidth to maintain v4
and v5
(and the nightmare future merge if I keep changing the v4
branch) -- so changes will be for serious bugs without workarounds only.
language-ext has eaten several months of my time recently, which I really should have been putting into building my new startup project (I have meetings lined up with VCs in September, so I'm pushing pretty hard on my project and the stability of v5
right now). I obviously appreciate the donation, but if I need to forego it to make some progress, then I'll have to do that I'm afraid.
This is an MIT licensed project though, so you're well within your rights to fork it and maintain your own version until you're ready to come over to v5
. If you need help with the build process then please let me know, but you should just be able to run the following to produce the nuget artefacts:
dotnet restore
dotnet pack LanguageExt.Core -c Release -o ../../artifacts/bin
dotnet pack LanguageExt.Transformers -c Release -o ../../artifacts/bin
dotnet pack LanguageExt.FSharp -c Release -o ../../artifacts/bin
dotnet pack LanguageExt.Parsec -c Release -o ../../artifacts/bin
dotnet pack LanguageExt.Rx -c Release -o ../../artifacts/bin
dotnet pack LanguageExt.Sys -c Release -o ../../artifacts/bin
dotnet pack LanguageExt.SysX -c Release -o ../../artifacts/bin
(I've removed
LanguagExt.CodeGen
, because that needs some manual intervention, but also is stand-alone and can remain on the current version)
Sorry I can't be more help.
@louthy I do understand and appreciate very much that you've given quick and clear feedback on the matter. ❤️ Also thanks for the build instructions, we may consider forking.
Good luck + success with your new startup!