Discussion thread for RFC FS-1043 (Extension methods can solve trait constraints) #435
Replies: 13 comments
-
Something I noticed during testing... First, that this allows extension operators on arrays like this:
Second, F# only currently supports extension instance methods on tuple types, and those only via C#-extensions. So this is not allowed: type System.ValueTuple<'T,'U> with
static member inline (+)(struct(a,b), struct(c,d)) = struct(a + c, b + d)
type System.Tuple<'T,'U> with
static member inline (+)((a,b), (c,d)) = (a + c, b + d)
let v1 = (1,2) + (3,4)
let v2 = (struct (1,2)) + (struct (3,4) ) Third, for the array example - and for any other similar generic extension operator - the (constrained) generic type parameter of the operator must not be the same as the enclosing, (which is unconstrained as with all F# extension members). So this: type ``[]``<'T> with
static member inline (+)(a:'T1[], b: 'T2[]) = Array.map2 (+) a b and not this type ``[]``<'T> with
static member inline (+)(a:'T[], b: 'U[]) = Array.map2 (+) a b |
Beta Was this translation helpful? Give feedback.
-
That's currently a restriction for normal static methods as well. |
Beta Was this translation helpful? Give feedback.
-
I think that's a good thing? Or do you suggest this should not be allowed? Unless it causes issues with existing code, this looks like a good extra 'nice to have' :) |
Beta Was this translation helpful? Give feedback.
-
type int with
member x.Show() = printfn "%A" x Will RFC FS-1043 also allow solving constraints for non-static methods like the above? |
Beta Was this translation helpful? Give feedback.
-
It would be nice if this proposal could also take constants into account. The use case is simple, suppose you need to write numeric code that depends on something like Another use case is where you want your code to depend on a constant field like Allowing resolution of constant fields seems a relatively easy addition, considering that normal fields can already be resolved. But I may be wrong. Apologies if this is out of scope. |
Beta Was this translation helpful? Give feedback.
-
I'm examining some consequences of the part of this RFC "Weak Resolution no longer forces overload resolution...". In general this seems a great improvement. However, I have found one case where, for the complicated SRTP code such as found in FSharpPlus, existing code no longer compiles. ExampleHere is a standalone repro reduced substantially, and where many types are made more explicit: module Lib
let inline CallApplyMethod (input1: ^I1, input2: ^I2, mthd : ^M) : ^R =
((^M or ^I1 or ^I2 or ^R) : (static member ApplyMethod : ^I1 * ^I2 * ^M -> ^R) input1, input2, mthd)
let inline CallMapMethod (mapping: ^F, source: ^I, _output: ^R, mthd: ^M) =
((^M or ^I or ^R) : (static member MapMethod : (^I * ^F) * ^M -> ^R) (source, mapping), mthd)
type Apply =
static member inline ApplyMethod (f: ^AF, x: ^AX, _mthd:Apply) : ^AOut = ((^AF or ^AX) : (static member Apply : ^AF * ^AX -> ^AOut) f, x)
type Map =
static member inline MapMethod ((x: ^FT, f: 'T->'U), _mthd: Map) : ^R = (^FT : (static member Map : ^FT * ('T -> 'U) -> ^R) x, f)
let inline InvokeApply (f: ^AF) (x: ^AX) : ^AOut = CallApplyMethod(f, x, Unchecked.defaultof<Apply>)
let inline InvokeMap (mapping: 'T->'U) (source: ^FT) : ^FU = CallMapMethod (mapping, source, Unchecked.defaultof< ^FU >, Unchecked.defaultof<Map>)
[<Sealed>]
type ZipList<'s>() =
static member Map (_xs: ZipList<'a>, _f: 'a->'b) : ZipList<'b> = failwith ""
static member Apply (_fs: ZipList<'a->'b>, _xs: ZipList<'a>) : ZipList<'b> = failwith "" The following code fails to compile with this RFC activated: let inline AddZipLists (x: ZipList<'a>, y: ZipList<'a>) : ZipList<'a> =
InvokeApply (InvokeMap (+) x) y ExplanationThe characteristics are
WorkaroundsThere are numerous workarounds:
let inline (+) (x: ZipList<'a>, y: ZipList<'a>) : ZipList<'a> =
let f = InvokeMap (+) x
InvokeApply f y This works because using
let inline (+) (x: ZipList<'a>, y: ZipList<'a>) : ZipList<'a> =
InvokeApply (InvokeMap ((+): 'a -> 'a -> 'a) x) y This works because the type annotation means the
let inline CallMapMethod (mapping: ^F, source: ^I, _output: ^R, mthd: ^M) =
((^M or ^I) : (static member MapMethod : (^I * ^F) * ^M -> ^R) (source, mapping), mthd) instead of let inline CallMapMethod (mapping: ^F, source: ^I, _output: ^R, mthd: ^M) =
((^M or ^I or ^R) : (static member MapMethod : (^I * ^F) * ^M -> ^R) (source, mapping), mthd) With this change the code compiles. This is the only example I've found of this in FSharpPlus. However I guess there may be client code of FSharpPlus that hits this problems. In general I suppose it may result whenever we have
I could certainly see this happening in client code of FSharpPlus code. I don't think there is any simple adjustment to the RFC to avoid this problem, though I suspect the RFC allows the systematic removal of the use of return types as resolvers Recommendation
Slightly Simpler ExampleFor completeness here's a vastly simpler example let inline InvokeMap (mapping: ^F) (source: ^I) : ^R =
((^I or ^R) : (static member Map : ^I * ^F -> ^R) source, mapping)
// A similated collection
type Coll<'T>() =
// A similated 'Map' witness
static member Map (source: Coll<'a>, mapping: 'a->'b) : Coll<'b> = new Coll<'b>() Now consider this generic inlince code: let inline MapTwice (x: Coll<'a>) (v: 'a) : Coll<'a> =
InvokeMap ((+) v) (InvokeMap ((+) v) x) Here the trait call for |
Beta Was this translation helpful? Give feedback.
-
From Slack with @gusty: @gusty says
@dsyme says:
And then this side comment from @dsyme:
|
Beta Was this translation helpful? Give feedback.
-
I added the notes about this issue in the compatibility sectiuon of the RFC |
Beta Was this translation helpful? Give feedback.
-
I also added testing to the implementation PR |
Beta Was this translation helpful? Give feedback.
-
Is F#+ even a proponent of instance member overloads? i.e. isn't this somewhat of a strawman? In any case I hope we can all agree that OO style method chaining shouldn't need return type annotations, I would for instance not mind it one bit if |
Beta Was this translation helpful? Give feedback.
-
Yes some F#+ operators like Yes it's not about instance members specifically - though AFAIK there is no technical difference between static and instance here |
Beta Was this translation helpful? Give feedback.
-
Technically no, I can't think of any differences myself either, but as you stated yourself |
Beta Was this translation helpful? Give feedback.
-
@dsyme Notice that the workarounds you wrote above for the Now, if I use the |
Beta Was this translation helpful? Give feedback.
-
Discussion thread for F# RFC FS-1043 - Extension members should be available to solve operator trait constraints
Beta Was this translation helpful? Give feedback.
All reactions