2012-10-22 4 views
7

Sto cercando di implementare la digitazione anatra in F # e ho notato che si può avere un member constraint in F# generics come segue:F # tipo generico vincoli e anatra digitando

type ListEntryViewModel<'T when 'T : (member Name : string)>(model:'T) = 
    inherit ViewModelBase() 

    member this.Name with get() = model.Name 

Tuttavia, il codice di cui sopra non viene compilato quando ho prova a fare riferimento alla proprietà. Viene visualizzato un errore del compilatore:

This code is not sufficiently generic. The type variable ^T when ^T : (member get_Name : ^T -> string) could not be generalized because it would escape its scope.

È possibile implementare la digitazione anatra tramite un vincolo generico?

+0

Si noti che questo non è realmente "digitazione anatra", ma piuttosto una digitazione strutturale (secondaria). – Eyvind

risposta

19

C'è stata una domanda simile recentemente dove member constraints were used in the type declaration.

Non sono sicuro di come correggere il campione per farlo compilare, ma non sarei sorpreso se ciò non fosse possibile. I vincoli dei membri sono progettati per essere utilizzati con parametri di tipo risolti staticamente e specialmente con le funzioni o membri inline e non penso che sia un codice F # idiomatico per usarli con i parametri di tipo di una classe.

penso che una soluzione più idiomatica al tuo esempio potrebbe essere quello di definire un'interfaccia:

type INamed = 
    abstract Name : string 

type ListEntryViewModel<'T when 'T :> INamed>(model:'T) = 
    member this.Name = model.Name 

(In realtà, il ListEntryViewModel probabilmente non ha bisogno di un parametro di tipo e possono solo prendere INamed come parametro del costruttore , ma ci può essere qualche beneficio per iscritto in questo modo.)

Ora, è comunque possibile utilizzare la tipizzazione anatra e utilizzare ListEntryViewModel sulle cose che hanno Name proprietà, ma non implementano l'interfaccia INamed! Questo può essere fatto scrivendo una funzione inline che restituisce INamed e usa i vincoli membro statiche per catturare l'Name proprietà esistente:

let inline namedModel< ^T when ^T : (member Name : string)> (model:^T)= 
    { new INamed with 
     member x.Name = 
     (^T : (member Name : string) model) } 

È quindi possibile creare la tua vista del modello scrivendo ListEntryViewModel(namedModel someObj) dove someObj non deve implementare l'interfaccia , ma necessita solo della proprietà Name.

Preferirei questo stile, perché prendendo un'interfaccia, è possibile documentare meglio ciò che si richiede dal modello. Se hai altri oggetti che non si adattano allo schema, puoi adattarli, ma se stai scrivendo un modello, implementare un'interfaccia è un buon modo per assicurarsi che esponga tutte le funzionalità richieste.

5

Per rendere il vostro lavoro codice originale:

type ListEntryViewModel< ^T when ^T : (member Name : string)>(model:^T) = 
    inherit ViewModelBase() 

    member inline this.Name with get() = (^T : (member Name : string) model) 

quindi bisogna marcare il membro come "inline" e ripetere il vincolo nella funzione membro.

Sono d'accordo con Tomas sul fatto che un approccio basato su interfaccia di solito è preferito in F #.

6

Is it possible to implement duck typing via a generic constraint?

No. Fatta eccezione per alcuni casi speciali, F # implementa solo la digitazione nominale in cui la digitazione anatra non è possibile. Come hanno spiegato le altre risposte, la "soluzione" idiomatica è quella di riadattare un'interfaccia su tutte le classi che si desidera abbiano aderito a tale interfaccia, ma, naturalmente, ciò è poco pratico nella maggior parte dei casi in cui si desidera la digitazione anatra.

Si noti che questa limitazione in F # viene ereditata da .NET.Se vuoi vedere una soluzione più pratica simile alla digitazione, controlla le varianti e gli oggetti polimorfici strutturalmente digitati di OCaml.

+0

+1 per un consiglio pratico. – missingfaktor