-
Notifications
You must be signed in to change notification settings - Fork 21
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
"Most concrete" tiebreaker for generic overloads #905
Comments
I too have used this workaround in several of my projects, e.g. Felicity, FSharp.JsonApi, Feliz.MaterialUI, and Cvdm.ErrorHandling (now merged into FsToolkit.ErrorHandling), among several others. Having tie breakers as described here would make it notably easier to design flexible APIs. |
Yes this seems reasonable. |
This has bitten me many times, mostly in Interop scenarios. I had to make my own ValueTask extensions (like mimicking Task's FromResult) and there are other types, coming from C# that have different overloads in constructors or methods and while calling one or two is not that much of a problem (in case of ValueTask you can just pick a right argument name, but at the same time not every constructor or method is like this, sometimes they share parameter name because why not, it's C#...) but when you have to build a more complex tree or a graph of objects it becomes a huge pain and code becomes quite unsightly or requires spending time writing workarounds just to build the thing... CEs sometime are really hurt by this too. |
Recently was trying code like this, and it tends to always resolve to let castToOption (x: obj) =
match x with
| :? option<obj> as x1 -> ... // Do stuff with x1
| other -> failwith "Invalid type" |
Another example of teams adding APIs that causes a break in F# code: microsoft/testfx#2381 |
Yeah, it's everywhere unfortunately. Faced it in bcl when upgraded sdk. We will eventually do it, but it will have to be approached very carefully, especially around considerations for "better" overloads (such as span based), since we are unlikely to change those once we implement them. It will need a very thorough RFC, I might write it once we done with November release. |
@vzarytovskii Would be amazing to have. This will reduce a huge number of hacky workarounds (like using module import order) and simplify using generics. |
For future me:
|
F# today has tiebreakers like intrinsic methods over extension methods and most derived over least derived type, yet it fails to address one - in my opinion - very common source of ambiguity errors.
That is the case where there are two overloads, like so
See: sharplab
or like so (which I find more of an anti pattern than the one above but it does come up now and then)
I propose to add the 'obvious' tiebreaker of "most concrete"; meaning if a value is passed matching the shape of multiple generic methods choose the most concrete overload in the applicable set, in this case for
Some [1]
that would beOption<List<'t>>
. When there are multiple arguments this should probably function just as the "most derived" tiebreaker does in the face of multiple matching overloads but the best decision here isn't entirely clear to me.Some examples where workarounds for this are being employed:
query {}
CE https://github.com/dotnet/fsharp/blob/fead0aac540485683f694524eadad79983ec28d9/src/fsharp/FSharp.Core/Query.fs#L210-L221One very common case of ambiguity errors in my own code is in the
ValueTask<'T>
constructors, where you must pass either aresult: 'T
or atask: Task<'T>
, in this case disambiguation is (luckily) possible by naming the parameter but it's a frequent source of frustration.There likely exists a large group of people that tried overloads like these but could not get things to work, at that point they either abandon it, or discover SRTPs which is arguably a heavy mechanism for these simple cases.
I expect there exists many more cases like these 'in the wild' and I encourage all readers to post them below. Additionally if you have tried to define generic overloads and ran into ambiguity errors please speak up as well.
With a large part of functional programming being about functors and generalization, generic overloads are not uncommon and you quickly bump into this shortcoming. I believe it's time overload resolution can properly deal with them.
Pros and Cons
Less hacks but additional complexity in overload mechanics.
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:
Please tick all that apply:
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.
The text was updated successfully, but these errors were encountered: