我的目的是定义一个具有函数的模块,这些函数可以对符合某些键的假设的所有记录类型进行操作。
为了说明,我们有以下代码:
> type DBRow = { id: string ; createdAt: System.DateTime } ;;
type DBRow =
{id: string;
createdAt: System.DateTime;}
> let logCreationInfo row = printf "Record %s created at %s " row.id (row.createdAt.ToString()) ;;
val logCreationInfo : row:DBRow -> unit
我想更改上面的logCreationInfo
,以便能够对包含id: string
和createdAt: System.DateTime
(以及其他内容)的所有记录进行操作。
来自打字稿的结构打字,我预计这是微不足道的,但我正在探索在F#中有一种更惯用的处理方法。
我有attempted使用接口来处理这个问题,但即使这样可行,因为F#只支持显式接口,这不适用于我自己定义的类型。
答案 0 :(得分:4)
您可以使用statically resolved type constraints。
let inline logCreationInfo (x : ^t) =
printfn "Record %s created at %s"
(^t : (member id : string) (x))
((^t : (member createdAt : System.DateTime) (x)).ToString())
答案 1 :(得分:4)
F#主要使用主格类型 - 这是其运行时环境中的自然选择,因为这是Common Type System规范规定的内容。遵守这套规则允许F#代码与其他.NET语言几乎无缝地互操作。
值得注意的是,这与TypeScript使用结构类型的原因相同。由于该语言建立在动态类型的JavaScript之上,因此根据结构而不是名义类型表达对象关系更为自然 - 这是JS中的一个外国概念。
F#确实有一个"后门"通过已经提到的SRTP进行结构分类,但我建议非常谨慎地使用它。 SRTP已经解析,使用它们的代码由编译器内联,缩短了编译时间,降低了与其他语言和.NET平台的互操作性(简单地说,您不能从其他语言引用该代码或使用反射API,因为它被编译掉了#34;)。
通常还有其他可用解决方案。已经提到了接口,尽管使用的示例有点人为 - 这更简单:
type IDBRow =
abstract Id: string
abstract CreatedAt: System.DateTime
type Person =
{
id: string
name: string
age: int
createdAt: System.DateTime
}
interface IDBRow with
member this.Id = this.id
member this.CreatedAt = this.createdAt
let logCreationInfo (row: #IDBRow) =
printf "Record %s created at %s" row.Id (string row.CreatedAt)
let x = { id = "1"; name = "Bob"; age = 32; createdAt = DateTime.Now }
logCreationInfo x
或者使用合成和泛型类型来捕获DBRow的通用部分:
type DBRow<'data> =
{
id: string
data: 'data
createdAt: System.DateTime
}
type Person =
{
name: string
age: int
}
let logCreationInfo (row: DBRow<_>) =
printf "Record %s created at %s" row.id (string row.createdAt)
let x = { id = "1"; data = { name = "Bob"; age = 32 }; createdAt = DateTime.Now }
logCreationInfo x
答案 2 :(得分:1)
这是一个带接口的版本:
open System
type DBRow1 = {
id: string
createdAt: DateTime
}
type DBRow2 = {
id: string
createdAt: DateTime
address: string
}
/// The types are defined above without an interface
let row1 = {id = "Row1"; createdAt = DateTime.Now}
let row2 = {id = "Row2"; createdAt = DateTime.Now; address = "NYC"}
type IDBRow<'A> =
abstract member Data:(string * DateTime)
// Object expression implements the interface
let Data1 (x:DBRow1) = {
new IDBRow<_> with
member __.Data = (x.id, x.createdAt)
}
let Data2 (x: DBRow2) = {
new IDBRow<_> with
member __.Data = (x.id, x.createdAt)
}
//pass in both the object expression and the record
let getData (ifun: 'a -> IDBRow<'b>) xrec =
(ifun xrec).Data
// You could partially apply the functions: `getData1 = getData Data1`
getData Data1 row1 //("Row1", 2018/02/05 9:24:17)
getData Data2 row2 //("Row2", 2018/02/05 9:24:17)
即使您无法访问原始类型,也可以使用接口(在这种情况下为对象表达式)来粘贴其他成员.Data
。您仍然需要为每种类型组合一个对象表达式,因此SRTP可能是一个更“优雅”的解决方案。