fslang-suggestions icon indicating copy to clipboard operation
fslang-suggestions copied to clipboard

Support for C# collection expressions in F# lists and sets

Open IsaacSchemm opened this issue 11 months ago • 7 comments

I propose we add support for C# collection expressions to F# lists and sets, so that a collection of this type can be initialized from C# code with a collection expression.

The use case for F# lists and sets in C# code, as I see it, is to extend the immutability and value equality of C# records to those with collections of equatable items (strings, integers, other records) as properties/fields. By combining a record with an immutable collection type that (like the record) implements value equality, a complex type can be constructed that is fully immutable and supports "deep" equality checks all the way down, as is often done in F#. This becomes very useful when writing unit tests that need to define and check all fields and sub-fields on objects,. It also opens up use cases for these complex types to be used in a HashSet or as keys in a Dictionary.

C# collection expressions don't have "automatic" support for immutable collections (the compiler looks for an Add method), but support can be added to a collection type by adding a CollectionBuilderAttribute to the type and implementing a static creation method.

This code can be used today in C#:

record Poem {
    public string Name { get; init; } = "Untitled";
    public string Content { get; init; } = "";
    public FSharpSet<string> Tags { get; init; } = SetModule.Empty<string>();
}

Poem originalPoem = repository.GetPoem(id);
Poem newPoem = originalPoem with { Tags = SetModule.FromArray(["sonnet", "moon", "sky"]) };
if (originalPoem != newPoem) {
    repository.UpdatePoem(id, newPoem);
}

With this change, the code could become:

record Poem {
    public string Name { get; init; } = "Untitled";
    public string Content { get; init; } = "";
    public FSharpSet<string> Tags { get; init; } = [];
}

Poem originalPoem = repository.GetPoem(id);
Poem newPoem = originalPoem with { Tags = ["sonnet", "moon", "sky"] };
if (originalPoem != newPoem) {
    repository.UpdatePoem(id, newPoem);
}

The existing ways of approaching this problem are to:

  • use a non-F# collection type such as the ones in the namespace System.Collections.Immutable; these types do not implement value equality; so another approach must be used to compare two objects
  • initialize another collection type (with or without collection expressions) and convert to an F# list using ListModule.OfSeq or ListModule.OfArray
  • implement a custom collection type that supports value equality (with object.Equals and with IEquatable<T>) and collection expressions, as well as whatever other functions / features are needed

Pros and Cons

The advantages of making this adjustment to F# are:

  • Better interoperability with C# code that uses F#-defined types
  • Making the F# list type more useful for C# developers

The disadvantages of making this adjustment to F# are:

  • Code must be added to the F# library (and maintained) to support a C# feature that does not (currently) exist in F#

Extra information

Estimated cost (XS, S, M, L, XL, XXL): XS

Related suggestions:

  • #1086
  • #969

Affidavit (please submit!)

Please tick these items by placing a cross in the box:

  • [x] This is not a question (e.g. like one you might ask on StackOverflow) and I have searched StackOverflow for discussions of this issue
  • [x] This is a language change and not purely a tooling change (e.g. compiler bug, editor support, warning/error messages, new warning, non-breaking optimisation) belonging to the compiler and tooling repository
  • [x] This is not something which has obviously "already been decided" in previous versions of F#. If you're questioning a fundamental design decision that has obviously already been taken (e.g. "Make F# untyped") then please don't submit it
  • [x] I have searched both open and closed suggestions on this site and believe this is not a duplicate

Please tick all that apply:

  • [x] This is not a breaking change to the F# language design
  • [x] I or my company would be willing to help implement and/or test this

IsaacSchemm avatar Mar 20 '24 01:03 IsaacSchemm

Do you mean F# should define these methods for C# consumption? Should this relate to https://github.com/fsharp/fslang-suggestions/issues/969 for usage in F# too?

Happypig375 avatar Mar 20 '24 09:03 Happypig375

Marked as approved in principle!

dsyme avatar Mar 20 '24 11:03 dsyme

@Happypig375 I'll add #969 as related - it looks like if F# adds the ability to consume CollectionBuilderAttribute, it would be tracked as part of that issue. Right now this would just be for C# consumption.

IsaacSchemm avatar Mar 20 '24 13:03 IsaacSchemm