2016-03-07 19 views
8

Per il progetto Froto (Google Protobuf in F #), sto tentando di aggiornare il codice di deserializzazione dall'utilizzo degli oggetti 'a ref ai valori di passaggio byref<'a>, per le prestazioni.In F #, è possibile passare un riferimento a un valore predefinito mutabile come parametro?

Tuttavia, il codice qui sotto non riesce sulla linea 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 

errore FS0421: L'indirizzo del 'elemento' variabile non può essere utilizzato a questo punto

C'è qualcosa Posso fare per far funzionare questo codice senza modificare la firma del parametro hydrator?

Sono consapevole che potrei usare hydrator:'a ref -> Field -> unit e far funzionare le cose. Tuttavia, l'obiettivo è supportare la deserializzazione nei tipi record senza la necessità di creare un gruppo di oggetti ref nell'heap ogni volta che un record viene deserializzato.

Si noti che il seguente codice è perfettamente legale e ha la stessa firma della dichiarazione di funzione hydrator, sopra, quindi non sono chiaro su quale sia il problema.

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

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

Penso che sia un errore del compilatore che la firma per "hydrator" sia anche consentita. Non è possibile avere un 'byref <_>' come parte di un tipo di funzione F #, poiché a livello di CLR che richiederebbe qualcosa simile a un 'FSharpFunc , ...>' e 'byref's non sono argomenti di tipo validi . – kvb

+0

Quello che dovresti fare è creare un nuovo tipo di delegato che usi un argomento di riferimento e utilizzarlo (qualcosa come 'tipo byrefAction <'a,'b> = delegato di byref <'a> * 'b -> unit' e poi' hydrator: byrefAction <'a,Field> '). Quindi non è necessario ricorrere a 'ref'. – kvb

+0

Per quanto riguarda il comportamento errato del compilatore, consultare https://github.com/Microsoft/visualfsharp/issues/819 per una raccolta di problemi in qualche modo correlati. – kvb

risposta

6

Proverò a chiarire quello che stavo dicendo nei miei commenti. Hai ragione che la tua definizione di assign è perfettamente a posto e che appare per avere la firma byref<'a> -> 'a -> unit. Tuttavia, se si guarda al l'assembly risultante, troverete che il modo in cui è stato compilato a livello di rappresentazione NET è:

Void assign[a](a ByRef, a) 

(vale a dire, si tratta di un metodo che accetta due argomenti e non restituisce qualsiasi cosa, non un valore di funzione che accetta un argomento e restituisce una funzione che accetta l'argomento successivo e restituisce un valore di tipo unit - il compilatore utilizza alcuni metadati aggiuntivi per determinare come il metodo è stato effettivamente dichiarato).

Lo stesso vale per le definizioni di funzione che non coinvolgono byref. Per esempio, si supponga che hai la seguente definizione:

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

Poi il compilatore crea effettivamente un metodo con la firma

Void someFunc(Int32, System.String) 

Il compilatore è abbastanza intelligente per fare la cosa giusta quando si tenta di usa una funzione come someFunc come valore di prima classe - se la usi in un contesto in cui non è applicata a nessun argomento, il compilatore genererà un sottotipo di int -> string -> unit (che è FSharpFunc<int, FSharpFunc<string, unit>> a livello di rappresentazione .NET) e tutto funziona perfettamente.

Tuttavia, se si tenta di fare la stessa cosa con assign, non funzionerà (o non dovrebbe funzionare, ma ci sono diversi bug del compilatore che potrebbero far sembrare che certe variazioni funzionino quando in realtà non lo fanno - potresti non ottenere un errore del compilatore ma potresti ottenere un assembly di output che è malformato invece) - non è legale per le istanze di tipo .NET utilizzare i tipi byref come argomenti di tipo generico, quindi FSharpFunc<int byref, FSharpFunc<int, unit>> non è un tipo .NET valido. Il modo fondamentale in cui F # rappresenta i valori delle funzioni non funziona quando ci sono argomenti byref.

Quindi la soluzione alternativa consiste nel creare il proprio tipo con un metodo che utilizza un argomento byref e quindi creare sottotipi/istanze con il comportamento desiderato, un po 'come fare manualmente ciò che il compilatore esegue automaticamente nel caso non-byref. Si potrebbe fare questo con un tipo di nome

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

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

o con un tipo delegato

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

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

Si noti che quando si chiama metodi come Invoke il delegato o di tipo nominale, viene creato alcun tuple vero e proprio, in modo da shouldn essere preoccupato per qualsiasi overhead extra lì (è un metodo .NET che accetta due argomenti e viene trattato come tale dal compilatore). Esiste il costo di una chiamata al metodo virtuale o di una chiamata delegata, ma nella maggior parte dei casi esistono costi simili quando si utilizzano i valori delle funzioni anche in un modo di prima classe. E in generale, se sei preoccupato per le prestazioni, dovresti impostare un target e misurare contro di esso piuttosto che cercare di ottimizzare prematuramente.