多态与公共字段的类型

问题描述 投票:3回答:3

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

当函数消耗一些公共字段时,是否可能具有将不同类型传递给函数的多态性。

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

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
f# polymorphism
3个回答
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 - >车辆 当^ v :(成员get_Owner:^ v - >字符串)和 ^ v :(成员get_Registration:^ v - >字符串)

你可以传递任何有OwnerRegistration字段的类型,它会返回一个Vehicle但当然你可以打印出来或返回一个元组等。对于Bike类型,因为它没有Registration这个函数会失败。


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
投票

有多种方法可以解决这个问题。除了已经显示的解决方案,我将使用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)

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

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

© www.soinside.com 2019 - 2024. All rights reserved.