Skip to content
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

Fix Support for Collection Expressions #1323

Conversation

BrunoJuchli
Copy link
Contributor

@BrunoJuchli BrunoJuchli commented May 3, 2024

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 9fff3cc

There's also tests for this, see 4505936

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 over ReadOnlySpan<T> which performs Set.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 #1323 (comment)

@BrunoJuchli
Copy link
Contributor Author

BrunoJuchli commented May 3, 2024

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);
    }
}

@BrunoJuchli BrunoJuchli changed the title Add Support for Collection Expression (fixes #1018 and obsoletes #868) Add Support for Collection Expression May 3, 2024
@BrunoJuchli BrunoJuchli changed the title Add Support for Collection Expression Fix Support for Collection Expressions May 3, 2024
@BrunoJuchli
Copy link
Contributor Author

@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!

@BrunoJuchli BrunoJuchli marked this pull request as ready for review May 3, 2024 05:19
@louthy
Copy link
Owner

louthy commented May 3, 2024

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.

@BrunoJuchli
Copy link
Contributor Author

@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!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants