-
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
Allow static constraints to be named and reused #1089
Comments
This is just approximating type classes. |
Actually, you can use an active pattern for this: // constraints.fs...
let inline (|HasName|) x = (^a : (member Name: string) x)
// somewhere-else.fs...
let inline printName (HasName name) = printfn $"{name}"
type Person1 = { Name: string; Age: int }
type Person2 = { Name: string; Age: int; IsFunny: bool }
type Name(name) =
member _.Name = name
let p1 = { Name = "Phillip"; Age = 30 }
let p2 = { Name = "Phillip"; Age = 30; IsFunny = false }
let nm = Name "Phillip"
M.printName p1
M.printName p2
M.printName nm |
@cartermp Except that active patterns don't work for type parameters for types, only local functions. |
I don't think that matters here. My comment is about an existing solution to the problem identified, not about providing an exact way to do what the original issue is looking to accomplish. |
I think that type classes would be a much larger feature since it allows you to add implementations for existing types. |
When dismissing a general proposal by claiming the specific example in the proposal can be solved by active patterns, one should offer the active pattern solution to the example. Let LOCs fall where they will. I like this idea, and I like the idea of making SRTPs more accessible through normalizing and documenting the feature. |
I don't think this discussion should be separated from static interface methods and generic maths which is currently being previewed in .Net. Only if that project fails does this need to be considered. SRTPs are only a workaround feature after all. I suggest contributing to the discussion on generic maths dotnet/designs#205 . Your |
I don't really view them as a workaround, more as a pragmatic solution that doesn't give everyone what the want
Will .NET generic math succeed? I'm not really sure. I've no real problem with the feature getting added and it will work fine from F#. However I personally fundamentally dislike like programmers using hierarchical math classifications linking to abstract algebra (fields etc.), despite the joys of my Year II abstract algebra class. I prefer structural, inferred constraints emerging from implementations - and I'm sure I'm not the only one. I strongly suspect there will be relatively little generic math code written in C# (I mean there will be a lot - it's a huge ecosystem and will be proportional to the ecosystem size), and what gets written will not necessarily get used, and what gets used will not necessarily be trusted reputationally w.r.t. performance. That said, I'll probably be glad when it's there and would use it in, say, DiffSharp.
Not really, for the two reasons above. |
FIW this is actually a duplicate of the declined #456 |
Three thoughts
type Has<'T when 'T : equality
and 'T : comparison
and 'T : (static member get_Zero : Unit -> 'T)
and 'T : (static member (+) : 'T * 'T -> 'T)
and 'T : (static member (-) : 'T * 'T -> 'T)
and 'T : (static member (*) : 'T * 'T -> 'T)
and 'T : (static member (/) : 'T * 'T -> 'T)> = 'T
type Point<'T when 'T: Has<'T> > =
{
X : 'T
Y : 'T
}
|
I think this proposal will naturally be realized in some or another form when static abstract members land officially. I do not think that overloading SRTP with functionality is a good way forward. |
@En3Tho Note that the proposal above has nothing fundamentally to do with SRTP - it's allowing collections of constraints of any kind to be named (including F# equality and comparison constraints). It's is reasonable to give names to adhoc collections of constraints in F# code, and it's orthogonal to anything else being considered. |
Yeah, but while I think using "shapes" or collections of constraints is perfectly good in inline functions, using them with types is a different matter - I fear it might bring even more interop related uncertainty. How such Point type will be consumed by C#, for example? If there won't be a way, then we might just end up with 2 totally different syntaxes when static abstracts land, one that is .Net oriented and one that is F# only. Maybe I'm too fixed on those, but in examples the main requirement are statics in generic constraints. |
That's orthogonal to this proposal, which is simply to allow collections of constraints to be named. SRTP constraints can already be declared on F# type parameters (but only invoked in F# inline code). The inlined methods are in theory usable from C# via the "witness-passing" entry points but in practice no one does this.
This is inevitable. F# is going to end up with both nominal/hierarchical/.NET-compatible constraints (for all code) via "static abstract" and structural/adhoc/F#-only constraints (for inlined code) via SRTP. That's just how it is. |
I see. Thanks. Well, my fear is that in general simplifying SRTP usage (e.g. reducing boilerplate, repetitive code etc. (https://github.com/fsharp/fslang-design/blob/main/RFCs/FS-1024-simplify-call-syntax-for-statically-resolved-member-constraints.md)) can lead to a larger use of SRTP than "indented", introducing an F# only split. I and hope mainstream famous F# libraries won't abuse it too much. On the other hand usually, SRTP is used by those who know what they are dealing with. And I personally thought sometimes that "why couldn't there be SRTP shapes/interfaces?" |
I was wondering what the thinking is behind this syntax: type Has<'T when 'T : equality
and 'T : comparison
and 'T : (static member get_Zero : Unit -> 'T)
and 'T : (static member (+) : 'T * 'T -> 'T)
and 'T : (static member (-) : 'T * 'T -> 'T)
and 'T : (static member (*) : 'T * 'T -> 'T)
and 'T : (static member (/) : 'T * 'T -> 'T)> = 'T Normally |
If interfaces with static abstract members are a thing, we could use them to describe a set of constraints. So if instead of type IHas<'T when 'T : equality and 'T : comparison> =
static abstract Zero : 'T
static abstract (+) : 'T * 'T -> 'T
static abstract (-) : 'T * 'T -> 'T
static abstract (*) : 'T * 'T -> 'T
static abstract (/) : 'T * 'T -> 'T
let inline f<'T when 'T ~ IHas<'T>> (x: 'T) (y: 'T) =
if x > y then x - y else 'T.Zero There could also be an attribute or something on the interface declaration to indicate that this is only intended as a constraint, and shouldn't be compiled into an actual .NET interface. But this attribute wouldn't necessarily be mandatory to be able to use |
@njlr, not sure if this answers your question, but it is based on type abbreviation syntax: https://docs.microsoft.com/en-us/dotnet/fsharp/language-reference/type-abbreviations What is nice about them, compared to C# using alias is that they actually carry as abbreviations in places you open the module or namespace, in C# you define those in each place where @Tarmil, this looks like really good way to encode SRTP constraints by lifting IWSAM to define those. |
The new syntax works a treat: type Numeric<'t when 't : equality
and 't : comparison
and 't : (static member get_Zero : Unit -> 't)
and 't : (static member ( + ) : 't * 't -> 't)
and 't : (static member ( - ) : 't * 't -> 't)
and 't : (static member ( * ) : 't * 't -> 't)
and 't : (static member ( / ) : 't * 't -> 't)> = 't
type Point<'t when Numeric<'t>> =
{
X : 't
Y : 't
}
type Circle<'t when Numeric<'t>> =
{
Center : Point<'t>
Radius : 't
}
type Rectangle<'t when Numeric<'t>> =
{
Center : Point<'t>
HalfSize : Point<'t>
} |
I propose we allow constraints to be named for easier reuse.
For example, consider this code:
This won't compile because the
't
intype Circle
andtype Rectangle
must have the constraints ofPoint
:However, across many types this becomes quite repetitive.
Also, if a new constraint were to be added (e.g.
't : (static member get_One : Unit -> 't)
) the code must be changed in many places.I propose we add a way to name a group of constraints so that they can be reused:
The existing way of approaching this problem in F#... is manually write out the constraints every time
Considering F# already allows type aliases, I think constraint aliases fits with the philosophy of the language.
My example concerns numeric types, but I'm sure this would be applicable to other scenarios too.
Pros and Cons
The advantages of making this adjustment to F# are...
The disadvantages of making this adjustment to F# are ...
Extra information
Estimated cost (XS, S, M, L, XL, XXL): M?
Related suggestions:
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: