我正在尝试创建一个泛型函数,该函数需要它的类型参数,它是一个记录类型,并且它具有特定的属性。以下是生成相关编译器错误的示例:
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.
有没有办法指定一个允许我正确执行此操作的类型约束?
答案 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最初建议的那样转向镜头。