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

Add overload for creating Set/Map that includes an IComparer<'T> #1137

Open
5 tasks done
matthewcrews opened this issue May 1, 2022 · 8 comments
Open
5 tasks done

Comments

@matthewcrews
Copy link

matthewcrews commented May 1, 2022

Add overload for creating Set/Map that includes an IComparer<'T>

I propose we add an overload for the creation of Set<'T> and Map<'K, 'V> which takes an IComparer<'T> to override the default comparison behavior of these two collections. Where I believe this would add the most value is in the case of structs that currently get boxed during comparison leading to performance degradation.

The existing way of approaching this problem in F# is to use the built-in comparison functionality in the F# language.

You can see from the benchmarks in this repo. The Struct and CustomStruct versions are allocating more memory than the default Record.

Method Mean Error StdDev Gen 0 Allocated
Record 2.464 us 0.0479 us 0.0774 us 0.4044 3 KB
StructRecord 2.508 us 0.0500 us 0.0748 us 0.7248 6 KB
CustomStructRecord 3.798 us 0.0758 us 0.0811 us 0.8926 7 KB

If this proposal does not address the GC/performance problem then I suggest we discard it since it is the main concern.

Pros and Cons

The advantages of making this adjustment to F# are that we reduce the GC associated with boxing structs unnecessarily.

The disadvantages of making this adjustment to F# are added complexity of the F# Core library and therefore more code to maintain.

Extra information

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

Related suggestions: (put links to related suggestions here)

Affidavit (please submit!)

Please tick this by placing a cross in the box:

  • This is not a question (e.g. like one you might ask on stackoverflow) and I have searched stackoverflow for discussions of this issue
  • I have searched both open and closed suggestions on this site and believe this is not a duplicate
  • 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.

Please tick all that apply:

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

For Readers

If you would like to see this issue implemented, please click the 👍 emoji on this issue. These counts are used to generally order the suggestions by engagement.

@charlesroddie
Copy link

charlesroddie commented May 2, 2022

F# has had a lot of trouble doing equality and comparison cleanly. PRs have come and gone including dotnet/fsharp#5112, dotnet/fsharp#6175.

If dotnet/fsharp#6175 gets resurrected it may be the best approach to the performance issue.

Fixing the type issue (moving to generic IComparable<T> instead of IComparable) is this suggestion: #816 .

This suggestion here is a workaround that uses an IComparer<'T>, not because a different comparison logic is needed, but to avoid the poor performance of F# comparison when creating sets. However it is too small. We would need similar workarounds for all uses of comparison in F#. Better to address the problem entirely and then no workarounds are needed.

@matthewcrews
Copy link
Author

I'm completely fine with closing this one and resurrecting another. I created this per Don's request on Twitter.

If something could be done, it would be greatly appreciated.

It looks like dotnet/fsharp#6175 is the best approach for this problem. I would be immensely grateful if it was brought back.

@cartermp
Copy link
Member

cartermp commented May 2, 2022

That one is the best overall approach, yeah. Maybe @TIHan could chime in on it a bit, but my understanding is that it is very tricky work with a high risk of breaking changes, which is why it was put on ice

@chkn
Copy link

chkn commented May 3, 2022

This suggestion here is a workaround that uses an IComparer<'T>, not because a different comparison logic is needed, but to avoid the poor performance of F# comparison when creating sets.

Well what if different comparison logic is needed? I had this case recently when trying to use System.Type as a key in a Map. Furthermore, this approach seems much simpler and safer than dotnet/fsharp#6175, but also doesn't preclude that change from landing one day.

@Tarmil
Copy link

Tarmil commented May 3, 2022

Here's a problem though: how do you handle, for example, Set.union s1 s2 when s1 and s2 use different comparers?

@charlesroddie
Copy link

@cartermp my understanding is that it is very tricky work with a high risk of breaking changes

I am very enthusiastic about the ability of flags to allow F# to move forwards with less concern for breaking changes. Reflection-free string functions will live behind a flag or attribute, and in dotnet/fsharp#6175 the dangerous stuff lives behind --optimize:equality.

We may need an RFC to specify exactly what flags we need. Something like --equalitycomparison [classic|optimized|generic], where classic preserves existing behaviour, optimized (--optimize:equality as defined in the PR) and generic uses IEquatable<'T>.Equals('T) always.

@BashkaMen
Copy link

BashkaMen commented Jan 20, 2023

Here's a problem though: how do you handle, for example, Set.union s1 s2 when s1 and s2 use different comparers?

we get a new type
Set<Record, CustomRecordComparer> != Set<Record>

@Tarmil
Copy link

Tarmil commented Jan 20, 2023

@BashkaMen We'd have to duplicate the entire Set module though.

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

No branches or pull requests

7 participants