F# 具有公共字段类型的多态性

F# 具有公共字段类型的多态性,f#,polymorphism,F#,Polymorphism,这个问题是否可以通过功能惯用方法解决,泛型法或有区别的联合法是答案吗 当函数使用一些公共字段时,是否可能存在向函数传递不同类型的多态性 其思想是能够调用和重用不同类型的函数,并使用公共属性/字段 type Car = { Registration: string Owner: string Wheels: int customAttribute1: string customAttribute2: string } type Truck = { R

这个问题是否可以通过功能惯用方法解决,泛型法有区别的联合法是答案吗

当函数使用一些公共字段时,是否可能存在向函数传递不同类型的多态性

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

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)

编辑

下面是答案。由于JSON序列化程序要求关联的类型,是否可以指定传入的
v
的类型。若汽车作为输入提供,汽车将被输出,若卡车作为输入,卡车将被输出

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

这看起来像是对象继承的经典用例,或者可能是一个接口。区别在于接口只提供方法(包括属性,因为它们是隐藏的方法),而基础对象(抽象或具体)也可以提供字段

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

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

您需要的通常称为结构(或duck)类型。它可以通过F#(公认的方式)中的接口和对象表达式或通过SRTPs完成。提供的@CaringDev链接为您提供了一个快速的概述,但显然您可以找到更多的示例。请阅读和阅读。对于您的特定示例,它将取决于您对原始类型的控制程度

很容易定义另一种类型,其中包含您可能需要的字段。然后,您的函数(顺便说一句,我发现它很有趣,您非常想使用函数解决方案,但将其命名为方法…)应该只采用具有所需字段/属性的任何类型。在这种情况下,泛型不适用于您,因为您需要约束到类型的子集。但是,一旦有了这样一个使用静态解析类型参数(SRTPs)的函数,就可以开始了:

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:
车辆类型已定义,但可以是任何类型。然后定义了可以采用任何类型的
someConplexFun
,该类型具有
所有者
注册
。它必须是内联的,其类型签名为:

val inline someComplexFun:
v:^v->车辆
当^v:(成员获取所有者:^v->string)和
^v:(成员获取注册:^v->string)


您可以传递任何具有
所有者
注册
字段的类型,它将返回
车辆
,但您当然可以打印出来或返回元组等。对于
自行车
类型,因为它没有
注册
此功能将失败

解决这个问题有多种方法。除了已经展示的解决方案之外,我还将使用lambda函数或新的数据结构来解决这个问题

使用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"
现在,第一个ttwo可以工作,但是您失去了类型安全性。第三个可以编译,但会创建运行时异常

如果您只有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)
当然,您可以再次基于此想法创建分派函数。这取决于您,但我个人的做法是创建一种信息类型,并使用显式对话


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

例如“一些复杂的功能”?也许您正在寻找
接口
?SRTPs可以做到这一点:@CaringDev感谢链接,但我没有得到它,你能给我举个例子吗code@CaringDev只是为了打印-是的,所以它取决于“一些复杂的功能”,多亏了dumetrulo,问题是,这是函数编程世界中惯用的方式吗?因为我在里面看到了更多的OOP?有区别的工会或非专利药如何,只是随便说说。@App2015 F#主要是功能性PL,所以如果OOP方法更适合任务,就使用它。非OOP的方法是使用差别化的联合来区分各种车辆类型,但在这种情况下,它可能比使用OOP更复杂……我喜欢你使用差别化联合的方式,请给我一些时间来测试,并在完成我的具体示例后返回给您。我有一个错误,请您在您的末尾执行,屏幕截图->1。太棒了,太酷了。请通过一些文档链接向我详细介绍这个克拉
^
符号/运算符,这是一种自动生成的动态getter和setter(反射?)+1.引用幕后发生的事情。为什么它必须是内联的?意味着每次调用的代码基本上都是重复的?假设调用了两次
someComplexFun-car
someComplexFun-car
,这意味着
someComplexFun
代码的副本将被替换为callYes,它必须是内联的,.NET类型系统不支持这种类型和类型约束
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)