robkuz
robkuz

Reputation: 9914

Extension Methods and Duck Typing

Why do I get an error when trying to call transform on an string?

type Truck = Truck 
type Car = Car

type Vehicle<'a> =
    | TruckWrapper of Truck * 'a
    | CarWrapper of Car * 'a 

type Truck with
    member this.transform (x) = TruckWrapper(this, x)

type Car with
     member this.transform (x) = CarWrapper(this, x)

type System.String with 
    member this.transform (x) =
        match x with
        | "truck" -> TruckWrapper(Truck, this)
        | _ -> CarWrapper(Car, this)

let inline transform value x = (^T : (member transform : 'a -> Vehicle<'b>) (value, x))

let a = transform Truck 1
let b = transform Car (1, 2)
let c = transform "truck" 0

this will yield the following error

let c = transform "truck" 0
------------------^^^^^^^

stdin(77,19): error FS0001: The type 'string' does not support the operator 'transform'

whereas

let d = "vehicle".transform("truck") 

works pretty fine

Upvotes: 1

Views: 136

Answers (2)

kaefer
kaefer

Reputation: 5741

I'm not quite sure if the alternative Gustavo alludes to would work with your original code, since the String extension method has a different signature from your other transform methods, member transform : x:string -> Vehicle<System.String> vs. member transform : x:'a -> Vehicle<'a>` otherwise.

If they have all the same type, then it's something along the lines of:

type IntermediateVehicle = IntermediateVehicle with
    static member ($) (IntermediateVehicle, x : Truck) =
        fun value -> x.transform value
    static member ($) (IntermediateVehicle, x : Car) =
        fun value ->  x.transform value
    static member ($) (IntermediateVehicle, x : string) =
        fun value -> x.transform value

let inline transform value x = (IntermediateVehicle $ value) x

let a = transform Truck 1
let b = transform Car (1, 2)
let c = transform "truck" 0
// val a : Vehicle<int> = TruckWrapper (Truck,1)
// val b : Vehicle<int * int> = CarWrapper (Car,(1, 2))
// val c : Vehicle<int> = TruckWrapper (Truck,0)

Upvotes: 2

Tarmil
Tarmil

Reputation: 11362

Unfortunately, extension members are not usable from member constraints. This could be implemented in the compiler, but I doubt it will be any time soon — as far as I can tell, member constraints are a low-priority feature for the F# team.

Edit: for your own types: what happens is that when you define a type extension in the same module as the type itself, then it is compiled into a normal method on your type. If you move the extension to another module, then it is truly compiled as an extension and you will see the same behavior as with System.String.

Upvotes: 3

Related Questions