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)