Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

polymorphism with types for common fields

Is this question solvable through functional idiomatic approach, could generics or discriminated unions be the answer?

Is it possible to have polymorphism with passing different types to a function while the function is consuming some common fields.

Idea is to be able to call and reuse the function with different types and use the common attributes/fields.

type Car = {
    Registration: string
    Owner: string
    Wheels: int
    customAttribute1: string
    customAttribute2: string
}

type Truck = {
   Registration: string
   Owner: string
   Wheels: int
   customField5: string
   customField6: string
}


let SomeComplexMethod (v: Car) = 
    Console.WriteLine("Registration" + v.Registration + "Owner:" + v.Owner + "Wheels" + v.Wheels
    // some complex functionality

Use

SomeComplexMethod(car)
SomeComplexMethod(truck)

Edit

Following the answer. Is it possible to specify the type of the incoming v since JSON serializer asks for the associated type. If Car was supplied as input, Car will be output, If truck as input truck will be output.

let inline someComplexFun v  =
    let owner =  (^v: (member Owner: string)(v)) 
    let registration = (^v: (member Registration: string)(v))
    // process input

    use response = request.GetResponse() :?> HttpWebResponse
    use reader = new StreamReader(response.GetResponseStream())
    use memoryStream = new MemoryStream(Encoding.UTF8.GetBytes(reader.ReadToEnd()))
    (new DataContractJsonSerializer(typeof<Car>)).ReadObject(memoryStream) :?> Car

if truck was the input v

(new DataContractJsonSerializer(typeof<Truck>)).ReadObject(memoryStream) :?> Truck
like image 363
App2015 Avatar asked Nov 19 '25 20:11

App2015


2 Answers

This looks like the classical use case for object inheritance, or perhaps an interface. The difference is that an interface provides only methods (including properties, as they are methods under the hood), while a base object (abstract or concrete) can also provide fields.

In your case an interface might be appropriate:

type IVehicle =
    abstract Registration : string
    abstract Owner : string
    abstract Wheels : int

type Car (_r, _o, _w) =
    member customAttribute1 : string
    member customAttribute2 : string
    interface IVehicle with
        member Registration = _r
        member Owner = _o
        member Wheels = _w

type Truck (_r, _o, _w) =
    member customField5 : string
    member customField6 : string
    interface IVehicle with
        member Registration = _r
        member Owner = _o
        member Wheels = _w

let someComplexMethod (v : IVehicle) =
    stdout.WriteLine "Registration: " + v.Registration +
                     "\nOwner: " + v.Owner +
                     "\nWheels: " + v.Wheels

EDIT: You can do it without OOP by using a discriminated union, and several record types:

type VehicleBase =
    { Registration : string
      Owner : string
      Wheels : int }

type CarAttributes =
    { customAttribute1 : string
      customAttribute2 : string }

type TruckAttributes =
    { customField5 : string
      customField6 : string }

type Vehicle =
    | Car of VehicleBase * CarAttributes
    | Truck of VehicleBase * TruckAttributes

let processVehicle v =
    stdout.WriteLine ("Registration: " + v.Registration +
                      "\nOwner: " + v.Owner +
                      "\nWheels: " + v.Wheels)

let someComplexMethod = function
    | Car (v, _) -> processVehicle v
    | Truck (v, _) -> processVehicle v
like image 86
dumetrulo Avatar answered Nov 21 '25 08:11

dumetrulo


What you want is usually called structural (or duck) typing. It can be done via interfaces and object expressions in F# (the accepted way) or via SRTPs. The link @CaringDev provided gives you a quick rundown, but obviously you can find many more examples. Please read this and this. For your specific example it will depend how much control you have over the original types.

It's easy to define another type that includes the fields that you might want. Then your function (I found it interesting btw, that you so much want to go for a functional solution but named it a method...) should just take ANY type that has the required fields/properties. Generics won't work for you in this case, as you need to constrain to a subset of types. But once you have such a function, which uses statically resolved type parameters (SRTPs) you're good to go:

type Car = {
    Registration: string
    Owner: string
    Wheels: int
    customAttribute1: string
    customAttribute2: string
}

type Truck = {
   Registration: string
   Owner: string
   Wheels: int
   customField5: string
   customField6: string
}

type Bike = {
    Owner: string
    Color: string
}

type Vehicle = {
    Registration: string
    Owner: string
}

let inline someComplexFun v  =
   let owner =  (^v: (member Owner: string)(v)) 
   let registration = (^v: (member Registration: string)(v))
   {Registration = registration; Owner = owner}

let car = {Car.Registration = "xyz"; Owner = "xyz"; Wheels = 3; customAttribute1= "xyz"; customAttribute2 = "xyz"}
let truck = {Truck.Registration = "abc"; Owner = "abc"; Wheels = 12; customField5 = "abc"; customField6 = "abc"}
let bike = {Owner = "hell's angels"; Color = "black"}
someComplexFun car //val it : Vehicle = {Registration = "xyz";
                   //Owner = "xyz";}
someComplexFun truck //val it : Vehicle = {Registration = "abc";
                     //Owner = "abc";}
someComplexFun bike //error FS0001:

The Vehicle type is defined but it could be anything. Then someConplexFun is defined that can take any type, that has Owner and Registration. It has to be inline and its type signature is:

val inline someComplexFun :
v: ^v -> Vehicle
when ^v : (member get_Owner : ^v -> string) and
^v : (member get_Registration : ^v -> string)

You can pass any type that has Owner and Registration fields, and it will return a Vehicle but of course you can just print it out or return a tuple, etc. For the Bike type, since it doesn't have Registration this function will fail.

like image 41
s952163 Avatar answered Nov 21 '25 10:11

s952163



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!