具有特定属性的记录类型的F#类型约束

时间:2016-09-01 14:56:26

标签: generics f# type-constraints

我正在尝试创建一个泛型函数,该函数需要它的类型参数,它是一个记录类型,并且它具有特定的属性。以下是生成相关编译器错误的示例:

let foo<'a> (a : 'a) =
    a' = { a with bar = "baz" }
    a'

编译此内容我收到一条错误,指出The record label bar is not defined

我尝试添加以下类型约束:

let foo<'a when 'a : (member Id : string)> =
    // ...

但也没有编译,抱怨This code is not sufficiently generic. The type variable ^a when ^a : (member get_Int : ^a -> string) could not be generalized because it would escape its scope.

有没有办法指定一个允许我正确执行此操作的类型约束?

3 个答案:

答案 0 :(得分:5)

我建议先阅读托马斯的回答。在可能的情况下,通常应避免使用静态解析的类型约束。它们是F#编译器的一个特性而不是.NET,因此它们在某种程度上限制了代码的可重用性。也就是说,它们非常强大,并允许您在编译时强加有用的约束。

使用它们的语法也不是非常令人愉快,但如果你仍然没有被阻止,你可以这样做:

type Test = {Bar : string}

let inline foo (a : ^a) =
    "foo " + ((^a) : (member Bar : string) (a))

let foobar = foo {Bar = "bar"} // prints "foo bar"

但请注意,您实际上无法将类型限制为记录,只是具有Bar类型成员string的内容。所以这也将解决:

type Test2(str : string) = member this.Bar = str

let foobar2 = foo (Test2("bar")) // prints "foo bar"

答案 1 :(得分:3)

我认为没有办法使用静态成员约束来指定它 - 静态成员约束是相当受限制的,它们主要是除了其他更常用的技术之外还可用的抽象机制。

如果我试图解决这样的问题,我可能会考虑使用接口(这并不总是最好的方法,但不知道更多关于你的具体情况,这可能是一个合理的默认方法):< / p>

type ISetA<'T> = 
  abstract WithA : string -> 'T

type MyRecord = 
  { A : string }
  interface ISetA<MyRecord> with
    member x.WithA(a) = { x with A = a }

实现记录时,您需要添加一个接口实现(因此,与只使用静态成员约束可以完成此操作相比,您需要做更多的工作)。但是,你也明确地说这是类型的预期用途......

使用方法也比使用静态约束时更简单:

let setA (setA:ISetA<_>) = 
  setA.WithA "Hello"

setA { A = "Test" }

答案 2 :(得分:2)

我不确定你的目标是什么。

如果您想要读取通用记录的属性,可以参阅TheInnerLight的工作示例。

现在,如果你想编写一个克隆许多类型记录的函数,那么你应该改变你的设计。

如果是这样,您可以按照Tomas建议的方法。除此之外,还有另一种选择:使用嵌套的通用记录。

type Test<'a> = {Bar : string; Rest : 'a}

type A = {PropA : string}
type B = {PropB : int}

let a = {Bar = "bar"; Rest  = {PropA = "propA" }}


let foo a = {a with Bar = "foo " + a.Bar}

let foobar = foo {Bar = "bar"; Rest = {PropA = "propA"}} 
// val foobar : Test<A> = {Bar = "foo bar"; Rest = {PropA = "propA";};}

let foobar' = foo {Bar = "bar"; Rest = {PropB = 0}}
// val foobar' : Test<B> = {Bar = "foo bar"; Rest = {PropB = 0;};}

最后一点,现在有了这个设计,你可以像FyodorSoikin最初建议的那样转向镜头。