2016-06-24 20 views
9

Vorrei un modo per definire i record correlati. Ad esempio,Registra variazioni in F #

type Thing  = { field1: string; field2: float } 
type ThingRecord = { field1: string; field2: float; id: int; created: DateTime } 

o

type UserProfile = { username: string; name: string; address: string } 
type NewUserReq = { username: string; name: string; address: string; password: string } 
type UserRecord = { username: string; name: string; address: string; encryptedPwd: string; salt: string } 

insieme con un modo per convertire da una all'altra, senza bisogno di scrivere tanto boilerplate. Anche il primo esempio in piena sarebbe:

type Thing = 
    { field1: string 
    field2: float } 
    with 
    member this.toThingRecord(id, created) = 
     { field1 = this.field1 
     field2 = this.field2 
     id = id 
     created = created } : ThingRecord 
and ThingRecord = 
    { field1: string 
    field2: float 
    id: int 
    created: DateTime } 
    with 
    member this.toThing() = 
     { field1 = this.field1 
     field2 = this.field2 } : Thing 

Come si arriva fino a field10 ecc, si arriva a essere una responsabilità.

Attualmente lo faccio in modo non sicuro (e lento) utilizzando il riflesso.

Ho aggiunto una richiesta per la sintassi with da estendere alle definizioni di record su uservoice, che soddisferebbe questa necessità.

Ma c'è forse un modo tipicamente valido per farlo già? Forse con i fornitori di tipi?

risposta

4

Sì, è uno spiraglio in armatura altrimenti lucido s 'F #. Non credo che ci sia una soluzione universale lì per ereditare o estendere facilmente un record. Senza dubbio c'è un appetito per uno - Ho contato più di una dozzina di documenti d'iscrizione sostenendo miglioramenti in questo senso - ecco alcuni dei principali, sentiti libero di votare: 1, 2, 3, 4, 5.

Certo, ci sono cose che puoi fare per aggirare il problema e, a seconda del tuo scenario, potrebbero funzionare al meglio per te. Ma alla fine - sono soluzioni alternative e c'è qualcosa che devi sacrificare:

  • velocità e la sicurezza di tipo quando si utilizza la riflessione,
  • brevità quando si va il tipo modo sicuro e avere annotazioni a pieno titolo con le funzioni di conversione tra loro,
  • Tutta la bontà sintattica e semantica che i record ti danno gratuitamente quando decidi di ricorrere alle classi .NET e all'ereditarietà.

I provider di tipi non lo interromperanno perché non sono davvero uno strumento utile per la metaprogrammazione. Non è quello per cui sono stati progettati. Se provi ad usarli in questo modo, sei destinato a trovare qualche limite.

Per uno, è possibile fornire solo tipi basati su informazioni esterne. Ciò significa che mentre si potrebbe avere un provider di tipi che inserirà tipi da a.Assemblaggio NET tramite riflessione e fornire alcuni tipi derivati ​​basati su questo, non è possibile "introspettarsi" nell'assembly che si sta creando. Quindi nessun modo di derivare da un tipo definito in precedenza nello stesso assembly.

Immagino che si possa aggirare il problema strutturando i progetti attorno al provider di tipi, ma suona sgraziato. E anche allora, non è possibile fornire i tipi di record in ogni caso yet, quindi meglio si può fare sono semplici classi .NET.

Per un caso d'uso più specifico di fornire un qualche tipo di mappatura ORM per un database - immagino che potreste usare i provider di tipi semplicemente bene. Non come una struttura generica per la metaprogrammazione.

+0

Bummer, stavo pensando di farlo la prossima settimana se nessuno avesse una soluzione esistente, ma il fatto (sorprendente) che i provider di tipi non supportano i tipi di record in qualche modo uccide quell'idea. –

+0

Bene, se hai due settimane, puoi sempre iniziare facendo in modo che il provider di tipi emetta i tipi di record;) – scrwtp

+0

Interamente disponibile, ma salvami un paio di giorni: dove dovrei iniziare? –

1

Perché non li fai più nidificati, come il seguente?

type Thing  = { Field1: string; Field2: float } 
type ThingRecord = { Thing : Thing; Id: int; Created: DateTime } 

o

type UserProfile = { Username: string; Name: string; Address: string } 
type NewUserReq = { UserProfile: UserProfile; Password: string } 
type UserRecord = { UserProfile: UserProfile; EncryptedPwd: string; Salt: string } 

funzioni di conversione sono banali:

let toThingRecord id created thing = { Thing = thing; Id = id; Created = created } 
let toThing thingRecord = thingRecord.Thing 

Usage:

> let tr = { Field1 = "Foo"; Field2 = 42. } |> toThingRecord 1337 (DateTime (2016, 6, 24));; 

val tr : ThingRecord = {Thing = {Field1 = "Foo"; 
           Field2 = 42.0;}; 
         Id = 1337; 
         Created = 24.06.2016 00:00:00;} 
> tr |> toThing;; 
val it : Thing = {Field1 = "Foo"; 
        Field2 = 42.0;} 
+0

Idealmente, sì, ma al database non piace, e (anche idealmente) a volte sembra strano avere una struttura nidificata quando si sta veramente rappresentando una cosa piatta. –

+0

E a volte ci sono cose trasversali che rendono il nesting un non-starter. per esempio. durante l'annidamento, 'WithId >' sarebbe diverso da '>'; ma vorrei 'thing.withId (id) .withTimestamp (ts)' equivalente a 'thing.withTimestamp (ts) .withId (id)'. –