-
Notifications
You must be signed in to change notification settings - Fork 789
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
[RFC FS-1043] Extension members visible to trait constraints #6805
base: main
Are you sure you want to change the base?
Conversation
I'd love to see this in, but I'm wondering about the rfc status. It appears empty, and points to the previous PR. Is there another location that has a summary of scope and features? |
@abelbraaksma this won't be merged until an RFC is written. The current one has no design. |
Thanks, I'd love to help, but I may not know enough of the internals to write a quality rfc pr |
pre-cleanup related to #6805
9a04217
to
6452dba
Compare
Anything we can do about the RFC? |
@dsyme is it possible that this will be merged for F# 5 after you were able to fix some bugs with the SRTP's? |
A sketch draft of the RFC is complete. It's not actually a very complicated change, but there are some subtle points in the interaction with other elements of logic in the compiler |
A key thing is to continue to improve testing, for
Note I'm not particularly interested in this change supporting the SRTP-laden code that does (largely pointless) over-abstraction of code in the name of "maximal sharing" or simulating category theory - this tends to serve little actual value and makes code highly obscure, and SRTP was never designed for these purposes. Indeed if possible I'd prefer to disallow or deprecate that kind of code. |
I can share the feeling, but at the same time, the type of code you are refering to is coming to existence (even in a coming PR to fsharp core) because F# currently lacks the constructs enabling such code to be written with more adequate idioms than SRTP. see usage of fsharp/src/fsharp/FSharp.Core/tasks.fs Lines 453 to 455 in b7f0199
As for the usefulness of SRTP, it is very useful and critical, here is a single SRTP function that I've defined and is critical in my day work codebase: let inline CreateCommand (createCommand: unit -> SqlCommand) : 'a =
let dummyCommand = createCommand()
let cx = dummyCommand.Connection
let tx = dummyCommand.Transaction
let timeout = dummyCommand.CommandTimeout
let command = (^a : (new : SqlConnection * SqlTransaction * int -> ^a) (cx, tx, timeout)) // SRTP abstracting over
// possibly some more stuff
command (NB: I don't use SRTP constructs casually in my work codebase) This is to work around design choices made in a TP library where I don't have control on constructor and definitely don't want useless boiler plate code to show up at every instanciation of hundreds of disparate types. The normal "legit" ways to handle this with the vanilla OO polymorphism just don't work (especially for type constructors), the usage of reflection comes with lots of issues, and the zero cost abstraction and safety that SRTP enables works strongly to support the great aspects of F# design, even if for SRTP, it is better burried deep down in the implementation of a library. My point is to show that between SRTP enabling critical abstractions (like numeric operators you mentionned or the function I was showing) and what is over abstraction, it really depends the context. Other languages like C++ and Rust have similar features (with worse or better syntax / idioms) that enable compile time monomorphization, and F# having some support for it is a great thing, despite all the bad rep of SRTP syntax. AFAIU, C++ will someday having the concept of concepts, and Rust uses traits, those approach enable better user story in terms of error messages, which in case of C++ template code and F# SRTP, tend to produce error messages that aren't conveying the likely cause of an issue. All that being said, this very PR, AFAIU, is going to enable haskell type class like approach where the instance can be defined outside of the type or the abstraction, so I won't be surprised if this turns out into new idioms developing until there is support for "Extend everything" from C# land, or new and more elaborated type constructs landing in the CLR. |
Yes, I'm aware of complex new SRTP constraints being used to capture the TaskLike pattern in RFC FS-1072 #6811 - this is something I'm not totally happy about and likely wouldn't have authored, but has been brought through the initial desire to base the work on TaskBuilder.fs. Regarding this code:
Could you show some callsites? I presume they need type annotations, e.g.
But how bad is it if you pass the constructor in explicitly?
Anyway, this RFC is not removing the ability to write user-defined SRTP constraints, I'm just trying to scope intended use cases for the whole mechanism if we need to make tradeoffs for compat etc. It would be a separate RFC to make some adjustment (e.g. add a warning for any user-defined SRTP constraint, or add a warning for user-defined two-type-parameter SRTP constraints, or something). I'm far from deciding what the principles for anything like this would be. |
Right now I'm trying to determine when/if this whole RFC is a breaking change.
Still, I'm sure this must be a breaking change. I would actually appreciate help trying to construct a case where it is if anyone has a spare evening :) |
Added notes on the compat concerns here: fsharp/fslang-design#430 |
I started to add some compatibility testing here: f3901d0 |
I would like some contributions of the following if you can help... Please list the best examples (if any) that you know of for these:
I'm after examples of good SRTP code capturing useful patterns, used in a way that makes code simpler end to end (convince me!), if this feature were supported. |
Right, it looks like this let command : MyCommandType = CreateCommand createCommand
let command = CreateCommandWithCtor createCommand (fun (a,b,c) -> new MyCommandType(a,b,c)) In my case I find it would be prohibitive to fallback to write this (or rather discourage the use of function I used as example), the only work around is reflection (not sure that even works with erased provided types I'm using here) which would offer no compile time guarantees as to which type argument is passed. There is also the risk, by doing what is shown in that SRTP-less code, that if I need to call another constructor later on, I have to go over all those lambdas and shuffle things, that is many callsites, instead of just changing that single infrastructure function; also prohibitive. With SRTP approach, I enforce only proper types are constructed and the invariants in my This is for me, compelling use of SRTP, so I don't have to review code which would be missusing or missing critical setters calls on the object and the invariants baked in the function, the syntax to use the function is actually the smallest thing a developer has to write with nothing competing as more convenient which could be faulty / needs more time reviewing. (This is code using http://fsprojects.github.io/FSharp.Data.SqlClient/) Thanks a lot for the clarifications and inviting to contribute valuable SRTP samples! |
Sorry I have no code here - but I will do some blog posts when this becomes available - comparing the different approaches. I have a deeply nested object graph (8 - 10 nested types before the whole thing becomes recursive) that I need to (de)JSONify - however I have usually between 4-5 representations of JSON for the same data structure. The most common approach for this would be to encode each JSON representation and the corresponding types as Json-types . however this would yield an explosion of similar types and the corresponding At the moment I have settled for a solution like the new System.Text.Json from .net core. That is I build a big registry of JsonReaders and JsonWriters with a lookup of The code that exists atm is roughly 7KLOC and I am sure I could strip away 30% if not 50% if I had something akin to TCs. |
IMO the level of breaking change here is worth taking:
|
Would be good to try to fix the merge conflicts before the diff gets too big . |
Just noting that there's some consequential overlap with fsharp/fslang-suggestions#1039 here - since it expands the ability to incidentally share names with properties, causing potential resolution issues. |
This pretty much awaits on C#'s decision on extensions, so we don't do work twice in regards to interop. |
@vzarytovskii just curious, can you briefly explain which decision you're referring to on the c# side? |
When an how will C# implement extensions. So interop will be less painful and we can design around their feature too. |
Otherwise if we implement it now, we may have to do support for their extensions again, and a. Inch of stuff may overlap. Going just to be double the work. |
Thanks! I think my confusion comes from thinking "c# already supports extension methods?" 😄 You seem to be referring to something more advanced on the C# side (which they'll partially take from F# probably...) to which you'll have to align with again. Did I get it right? |
Yeah, it's a new feature - extensions. I am not sure if there will be lots of overlap, but might be a bunch. I guess I should've phrased my initial comment differently: we aren't blocked by C# feature, but whoever will continue working on it should keep in mind C# spec and make a decision/suggestion whether we want to wait for their implementation or not. |
That explains it @vzarytovskii , much appreciated, thanks! |
I think would be good to keep this branch without conflicts. So whoever continues this will have a chance :) |
Agreed with @edgarfgp also I had a look at C# extensions, thanks @vzarytovskii for the link I wasn't aware of that proposal at all, and it looks like it's not overlapping with this because it's about type extensions (as opposed to static member extensions) and it's not about SRTPs, so I don't see where a conflict/overlap might arise. |
Oh it likely will, since we'll need to be supporting SRTPs via new extensions. |
I see what you mean, yes, but it seems to me that once you get SRTPs to work with static members, making them working on type extensions shouldn't be that complicated, specially considering that type extension should also support interfaces, IWSAMs which BTW can be used as solutions for SRTPs, ... just my 2 cents. |
It will be additional housekeeping for something we don't know when we will continue. I.e someone will need to go and explicitly look at it every week (day?, couple of days?), go and merge it with understanding of the change. I would say the usual approach would be easier - re-apply changes on top of main once we work on it. History shows that's the easiest approach since implementation changes oftentimes (like now - we will be needing to take care of iwsams too probably, and later we will need to be supporting extensions in srtps too). |
Continuation of #6286 from a feature branch
RFC https://github.com/fsharp/fslang-design/blob/master/RFCs/FS-1043-extension-members-for-operators-and-srtp-constraints.md
This is work by @TobyShaw and myself to implement RFC FS-1043. This PR brings #3582 up-to-date with master
Design points:
Things to test
--langversion:preview
--langversion:preview
Related bugs to investigate:
Stress test on type system involving many statically resolved type parameters #9382
Internal error: Undefined or unsolved type variable with SRTP chains #5973
Compilation fails depending on runtime path (???) #7384
Program that should not compile, using statically resolved type parameters, gets runtime exception instead #4924
NullReferenceException when calling a virtual Object method on a value type from inline function #8098
Internal error: couldn't remap internal tycon type Hober <> #8523
Weird dynamic invocation error on trait-call #8690
FS0073: internal error: Undefined or unsolved type variable when using records with generic type variables and overloaded operators in inline function #9416
error FS0073: internal error: Undefined or unsolved type variable: ^_?82069 #9633
error FS0073: internal error: Undefined or unsolved type variable: ^_?82069 fsprojects/FSharpPlus#331
Changing the SRTP of an inline operator in the same expression results in compile error. #7917
Return types in shadowing members are not considered in generic constraint resolution to avoid ambiguity. #8794