2016-03-07 51 views
8

对于Froto项目(Google Protobuf in F#),我试图更新从'a ref对象到传递值byref<'a>的性能的反序列化代码。在F#中,是否可以将引用传递给可变默认值作为参数?

但是,下面的代码失败在hydrator &element field行:

type Field = TypeA | TypeB | Etc 

let hydrateRepeated 
     (hydrator:byref<'a> -> Field -> unit) 
     (result:byref<'a list>) 
     (field:Field) = 
    let mutable element = Unchecked.defaultof<'a> 
    hydrator &element field 
    result <- element :: result 

错误FS0421:变量“元素”的地址不能在这一点上

使用有什么我能做到让代码在不改变hydrator参数签名的情况下工作吗?

我很清楚我可以使用hydrator:'a ref -> Field -> unit并让事情发挥作用。但是,目标是支持反序列化到record类型,而无需在每次记录反序列化时在堆上创建一堆ref对象。

请注意,以下代码是完全合法的,并且与上面的hydrator函数声明具有相同的签名,所以我不清楚问题是什么。

let assign (result:byref<'a>) (x:'a) = 
    result <- x 

let thisWorks() = 
    let mutable v = Unchecked.defaultof<int> 
    assign &v 5 
    printfn "%A" v 
+0

我认为这是一个编译器bug,甚至允许'hydrator'的签名。你不能有一个'byref <_>'作为F#函数类型的一部分,因为在CLR级别上需要类似于'FSharpFunc ,...>和'byref's的东西是不合法的类型参数。 – kvb

+0

你应该做的是创建一个新的委托类型,它需要一个byref参数并使用它(类似于'byrefAction <'a,'b> =委托的byref <'a> *'b - >单位'然后'hydrator:byrefAction <'a,Field>')。那么就没有必要诉诸'ref'。 – kvb

+0

关于编译器的错误行为,请参阅https://github.com/Microsoft/visualfsharp/issues/819以获取有关相关问题的集合。 – kvb

回答

6

我会尝试澄清我在评论中说的话。你是对的,你的assign的定义是完全正确的,它出现有签名byref<'a> -> 'a -> unit。但是,如果你看一下得到的组件,你会发现它在.NET表象层面编制的方法是:

Void assign[a](a ByRef, a) 

(即,它是采用两个参数并没有返回的方法任何东西,而不是一个函数值,它接受一个参数并返回一个函数,该函数接受下一个参数并返回unit类型的值 - 编译器使用一些额外的元数据来确定实际声明该方法的方式。

不涉及byref的函数定义也是如此。例如,假设你有如下的定义:

let someFunc (x:int) (y:string) =() 

那么编译器实际创建与签名

Void someFunc(Int32, System.String) 

编译器是足够聪明,当你尝试做正确的事的方法使用类似someFunc这样的函数作为第一个类的值 - 如果您在没有应用任何参数的上下文中使用它,编译器将生成子类型int -> string -> unit(在.NET表示级别为FSharpFunc<int, FSharpFunc<string, unit>>),并且所有内容无缝工作。但是,如果您尝试使用assign做同样的事情,它将不起作用(或者不应该起作用,但是有几个编译器错误可能会使它看起来像某些变体在真的不起作用时 - 您可能不会收到编译器错误,但您可能会得到一个格式错误的输出程序集) - 对于.NET类型实例使用byref类型作为泛型类型参数是不合法的,因此FSharpFunc<int byref, FSharpFunc<int, unit>>不是有效的.NET类型。当存在byref参数时,F#代表函数值的基本方式不起作用。

因此,解决方法是使用方法采用byref参数创建自己的类型,然后创建具有所需行为的子类型/实例,有点像手动执行编译器在非byref情况下自动执行的操作。你可以用命名的类型为此

type MyByrefFunc2<'a,'b> = 
    abstract Invoke : 'a byref * 'b -> unit 

let assign = { 
    new MyByrefFunc2<_,_> with 
     member this.Invoke(result, x) = 
      result <- x } 

或委托类型

type MyByrefDelegate2<'a,'b> = delegate of 'a byref * 'b -> unit 

let assign = MyByrefDelegate2(fun result x -> result <- x) 

请注意,调用像Invoke对委托或名义类型的方法时,不会创建任何实际的元组,所以你不应该不要担心任何额外的开销(这是一个.NET方法,它接受两个参数并且被编译器视为这样)。虚拟方法调用或委托调用的代价是,但在大多数情况下,当以一流的方式使用函数值时也存在类似的成本。一般来说,如果你担心表现,那么你应该设定一个目标和措施,而不是过早地优化。

相关问题