I want to extend some system types and later use them via inlining
type System.String with
member this.foo n = this + "!" + n
type System.Boolean with
member this.foo n = sprintf "%A!%A" this n
Now I call these extension methods
let x = "foo".foo "bar"
let y = true.foo "bar"
which gives me this
- val x : System.String = "foobar"
- val y : string = "true!"bar""
All fine and dandy - but now I want to wrap the call to .foo
into an inline function
let inline foo n v = (^T : (member foo : ^N -> ^S) v, n)
let z = foo "bar" "baz"
Only now I get a compiler error telling me that
> The type 'string' does not support the operator 'foo':
well ... it does!
Can somebody explain whats going on?
Extension methods are not taken into account in static member constraints (possible duplicate of this) and this is a general problem when you want to implement generic code using member constraints and make it work also with already defined or primitive types.
See the user voice request, also the workarounds mentioned here and Don Syme's explanation of why it's complicated to implement it in the F# compiler.
If you follow the links there you will see currently the way to workaround it basically involves creating an intermediate type and overloads for all the known types and a generic one for the extensions.
This is a very basic example of how to workaround it:
type Foo = Foo with
static member ($) (Foo, this:int) = fun (n:int) -> this + n
static member ($) (Foo, this:string) = fun n -> this + "!" + n
static member ($) (Foo, this:bool) = fun n -> sprintf "%A!%A" this n
let inline foo this n = (Foo $ this) n
//Now you can create your own types with its implementation of ($) Foo.
type MyType() =
static member ($) (Foo, this) =
fun n -> printfn "You called foo on MyType with n = %A" n; MyType()
let x = foo "hello" "world"
let y = foo true "world"
let z = foo (MyType()) "world"
You can enhance it by adding an explicit generic overload for new types:
// define the extensions
type System.String with
member this.foo n = this + "!" + n
type System.Boolean with
member this.foo n = sprintf "%A!%A" this n
// Once finished with the extensions put them in a class
// where the first overload should be the generic version.
type Foo = Foo with
static member inline ($) (Foo, this) = fun n -> (^T : (member foo : ^N -> ^S) this, n)
static member ($) (Foo, this:string) = fun n -> this.foo n
static member ($) (Foo, this:bool) = fun n -> this.foo n
// Add other overloads
static member ($) (Foo, this:int) = fun n -> this + n
let inline foo this n = (Foo $ this) n
//later you can define any type with foo
type MyType() =
member this.foo n = printfn "You called foo on MyType with n = %A" n; MyType()
// and everything will work
let x = foo "hello" "world"
let y = foo true "world"
let z = foo (MyType()) "world"
You can further refine it by writing the static constraints by hand and using a member instead of an operator (see an example here),
At the end of the day you will end up with something like this generic append function from FsControl.
Statically resolved type constraints do not support extension methods. It's just not a feature of F#.
If you would like F# to gain support for higher-kinded polymorphism, you can vote for it on user voice.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With