多态与公共字段的类型

时间:2018-03-28 05:37:44

标签: f# polymorphism

这个问题是否可通过功能惯用方法解决,泛型受歧视的联盟可以作为答案吗?

当函数消耗一些常见字段时,是否可能将多态性传递给函数。

想法是能够调用和重用不同类型的函数并使用公共属性/字段。

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

使用

SomeComplexMethod(car)
SomeComplexMethod(truck)

修改

回答。是否可以指定传入v的类型,因为JSON序列化程序会询问相关类型。如果提供汽车作为输入,将输出汽车,如果输出卡车作为输入卡车。

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

如果卡车是输入v

(new DataContractJsonSerializer(typeof<Truck>)).ReadObject(memoryStream) :?> Truck

3 个答案:

答案 0 :(得分:3)

这看起来像是对象继承的经典用例,或者可能是一个接口。不同之处在于接口仅提供方法(包括属性,因为它们是引擎盖下的方法),而基础对象(抽象或具体)也可以提供字段。

在您的情况下,接口可能是合适的:

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

编辑:您可以在没有OOP的情况下使用受歧视的联合和几种记录类型来执行此操作:

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

答案 1 :(得分:2)

您想要的通常称为结构(或鸭)打字。它可以通过F#中的接口和对象表达式(可接受的方式)或通过SRTP来完成。 @CaringDev提供的链接为您提供了快速简介,但显然您可以找到更多示例。请阅读thisthis。对于您的具体示例,它将取决于您对原始类型的控制程度。

很容易定义包含您可能需要的字段的其他类型。然后你的函数(我发现它很有趣btw,你非常想要一个功能性的解决方案,但命名为一个方法......)应该采取任何具有所需字段/属性的类型。在这种情况下,泛型不会为您工作,因为您需要约束类型的子集。但是一旦你有了这样一个使用静态解析类型参数(SRTP)的功能,你就可以了:

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:

Vehicle类型已定义,但可以是任何类型。然后定义了someConplexFun,可以采用任何类型,OwnerRegistration。它必须是inline,其类型签名是:

  

val inline someComplexFun:
    v:^ v - &gt;车辆
      当^ v :(成员get_Owner:^ v - &gt;字符串)和
            ^ v :(成员get_Registration:^ v - &gt;字符串)

您可以传递包含OwnerRegistration字段的任何类型,它会返回Vehicle但当然您可以将其打印出来或返回元组等。 Bike类型,因为它没有Registration此功能将失败。

答案 2 :(得分:1)

有多种方法可以解决这个问题。除了已经显示的解决方案,我将使用lambda函数或新的datatsructure来解决这个问题。

lambda的想法是。您希望函数作为返回所需值的参数而不是值。举个例子:

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

但是整个想法是你为每种类型创建一次这样的函数,并使用部分应用程序。

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

基于此,如果您愿意,可以创建一个调度函数

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"

现在第一个工作,但你失去了类型安全。第三个编译,但会创建一个运行时异常。

如果你只有1-3个值,那么使用lambdas就足够了,但是如果你需要更多的值,那么它会变得很麻烦。另一个想法是为您的函数创建自己的数据类型,并提供对话函数。

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

优点。现在你转发数据而不是类型。您可以基本上打印任何类型,只要您可以从中创建信息记录。因此,只需为每种类型提供单一转换功能。

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)

当然,您可以再次根据这个想法创建一个调度功能。这取决于你,但我的个人方法是创建一个信息类型并使用显式对话。

在我看来,这是最容易理解的,也是最有用的,因为只要你能以某种方式提供所需的数据,就可以轻松地以这种方式打印任何类型。并且您的不同类型不需要与具有某些特殊逻辑的预定义字段共享公共接口。