这个问题是通过功能惯用法解决的吗,泛型或歧视联盟可以作为答案吗?
当函数消耗一些公共字段时,是否可能具有将不同类型传递给函数的多态性。
想法是能够调用和重用不同类型的函数并使用公共属性/字段。
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#中的接口和对象表达式(可接受的方式)或通过SRTP来完成。 @CaringDev提供的链接为您提供了快速简介,但显然您可以找到更多示例。请阅读this和this。对于您的具体示例,它将取决于您对原始类型的控制程度。
可以轻松定义包含您可能需要的字段的其他类型。然后你的函数(我发现它很有趣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
可以采取任何类型,有Owner
和Registration
。它必须是inline
,其类型签名是:
val inline someComplexFun: v:^ v - >车辆 当^ v :(成员get_Owner:^ v - >字符串)和 ^ v :(成员get_Registration:^ v - >字符串)
你可以传递任何有Owner
和Registration
字段的类型,它会返回一个Vehicle
但当然你可以打印出来或返回一个元组等。对于Bike
类型,因为它没有Registration
这个函数会失败。
这看起来像对象继承的经典用例,或者可能是一个接口。不同之处在于接口仅提供方法(包括属性,因为它们是引擎盖下的方法),而基础对象(抽象或具体)也可以提供字段。
在您的情况下,接口可能是合适的:
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
有多种方法可以解决这个问题。除了已经显示的解决方案,我将使用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)
当然,您可以再次根据这个想法创建一个调度功能。这取决于你,但我的个人方法是创建一个信息类型并使用显式对话。
在我看来,它是最容易理解的,也是最有用的,因为只要你能以某种方式提供所需的数据,就可以轻松地以这种方式打印任何类型。并且您的不同类型不需要与具有某些特殊逻辑的预定义字段共享公共接口。