App2015
App2015

Reputation: 993

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

Upvotes: 3

Views: 1066

Answers (3)

dumetrulo
dumetrulo

Reputation: 1991

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

Upvotes: 3

David Raab
David Raab

Reputation: 4488

There are multiple ways how to solve this problem. Besides the already shown solution, i would use lambda functions or a new datatsructure, to solve this problem.

The idea with a lambda is. Instead of a value you expect a function as an argument that returns the needed value. As an example:

let printInformation registration owner wheels obj =
  let reg    = registration obj
  let owner  = owner obj
  let wheels = wheels obj
  printfn "Registration: %s Owner: %s Wheels: %d" reg owner wheels

let car = {Registration="CAR"; Owner="Me"; Wheels=4; customAttribute1="A"; customAttribute2="B"}
let truck = {Registration="TRUCK"; Owner="You"; Wheels=6; customField5="A"; customField6="B"}


printInformation
  (fun (obj:Car) -> obj.Registration)
  (fun obj -> obj.Owner)
  (fun obj -> obj.Wheels)
  car

printInformation
  (fun (obj:Truck) -> obj.Registration)
  (fun obj -> obj.Owner)
  (fun obj -> obj.Wheels)
  truck

But the whole idea is that you create such a function once for each type, and use partial application.

let printCar = 
  printInformation
    (fun (obj:Car) -> obj.Registration)
    (fun obj -> obj.Owner)
    (fun obj -> obj.Wheels)

let printTruck =
  printInformation
    (fun (obj:Truck) -> obj.Registration)
    (fun obj -> obj.Owner)
    (fun obj -> obj.Wheels)

printCar car
printTruck truck

Based on this you can create a dispatch function, if you wish

let print obj =
  match box obj with
  | :? Car   as c -> printCar c
  | :? Truck as t -> printTruck t
  | _              -> failwith "Not Car or Truck"

print car
print truck
print "FooBar"

Now the first ttwo works, but you lose type-safety. The third one compiles, but creates a runtime exception.

Working with lambdas is good enough if you only have 1-3 values, but if you need more values, then it can become cumbersome. Another idea would be to create your own data-type for your function, and provide conversation functions instead.

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

let printInfo (info:Information) =
  printfn "Registration: %s Owner: %s Wheels: %d" info.Registration info.Owner info.Wheels

The advantage. Now you relay on data instead of types. You can basically print any type as long you can create an Information record out of it. So its just a matter of providing a single transformation function for each type.

let carToInformation (car:Car) : Information = 
  {Registration=car.Registration; Owner=car.Owner; Wheels=car.Wheels}

let truckToInformation (truck:Truck) : Information = 
  {Registration=truck.Registration; Owner=truck.Owner; Wheels=truck.Wheels}

printInfo (carToInformation car)
printInfo (truckToInformation truck)

Sure, you can once again create a dispatch function based on this idea. Its up to you, but my personal approach i would create an Information type and use explicit conversation instead.

In my opinion it is the easiest to understand, and also the most useful one, as you can easily print any type this way as long you somehow can provide the needed data. And your different types don't need to share a common interface with pre-defined fields that have some special logic in it.

Upvotes: 1

s952163
s952163

Reputation: 6324

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.

Upvotes: 2

Related Questions