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.
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
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
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