2016-06-24 14 views
9

Chciałbym w jakiś sposób zdefiniować powiązane rekordy. Na przykład,Zapisz zmiany w F #

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

lub

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 } 

wraz ze sposobem konwersji z jednego do drugiego, bez konieczności pisania tyle boilerplate. Nawet pierwszy przykład w całości byłoby:

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 

Jak dostać się do field10 etc, to staje się odpowiedzialnością.

Obecnie robię to w niebezpieczny (i powolny) sposób za pomocą refleksji.

Dodałem prośbę o rozszerzenie składni with w celu zapisania definicji na uservoice, co wypełniłoby tę potrzebę.

Ale czy istnieje już sposób, aby to zrobić? Może z dostawcami typów?

Odpowiedz

4

Tak, to jest szpara w F # 's inaczej lśniącej zbroi. Nie wydaje mi się, żeby istniało tam uniwersalne rozwiązanie do łatwego dziedziczenia lub przedłużania zapisu. Bez wątpienia jest na nie apetyt - policzyłem kilkanaście zgłoszeń składanych przez użytkowników popierających ulepszenia w tym zakresie - oto kilka wiodących, zachęcamy do głosowania: 1, 2, 3, 4, 5.

Na pewno są rzeczy, które można zrobić, aby obejść problem, i w zależności od scenariusza mogą one dobrze się dla Ciebie udać. Ale ostatecznie - są obejścia i tam coś trzeba poświęcić:

  • Szybkość i bezpieczeństwo typu przy użyciu odbicia,
  • Zwięzłość gdy idziesz bezpieczną drogę typu i mieć pełnoprawnym rekordy z funkcjami wymiany między one,
  • Cała syntaktyczna i semantyczna dobroć, którą zapisy dają ci za darmo, kiedy zdecydujesz się powrócić do zwykłych klas .NET i dziedziczenia.

Dostawcy danych nie będą go ciąć, ponieważ nie są naprawdę dobrym narzędziem do metaprogramowania. Nie tego zaprojektowali. Jeśli spróbujesz ich użyć w ten sposób, będziesz mieć pewne ograniczenia.

Po pierwsze, możesz podać tylko typy na podstawie informacji zewnętrznych. Oznacza to, że możesz mieć dostawcę typów, który będzie pobierał typy z a.Złożenie NET za pomocą refleksji i dostarczenie na tej podstawie niektórych typów pochodnych, nie można "introspekować" do budowanego zespołu. Więc nie ma możliwości wywodzenia się z typu zdefiniowanego wcześniej w tym samym zespole.

Zgaduję, że można obejść to, projektując projekty wokół dostawcy typu, ale to brzmi niezgrabnie. I nawet wtedy nie można zapewnić typów rekordów tak czy inaczej yet, więc najlepiej można zrobić to zwykłe klasy .NET.

Dla bardziej konkretnego przypadku użycia pewnego rodzaju mapowania ORM dla bazy danych - wyobrażam sobie, że można używać dostawców typu dobrze. Po prostu nie jako ogólny obiekt do metaprogramowania.

+0

Bummer, planowałem zrobić to w przyszłym tygodniu, jeśli nikt nie ma istniejącego rozwiązania, ale (zaskakujący) fakt, że dostawcy typu nie obsługują typów rekordów nieco zabija ten pomysł. –

+0

Cóż, jeśli masz dwa tygodnie, zawsze możesz zacząć od tego, że dostawca typu emituje typy rekordów;) – scrwtp

+0

Całkowicie chętny, ale oszczędzaj mi kilka dni: od czego zacząć? –

1

Dlaczego nie uczynisz ich bardziej zagnieżdżonymi, jak na przykład?

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

lub

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

Funkcje konwersji są trywialne:

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

Zastosowanie:

> 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

Idealnie, tak, ale baza danych tego nie lubi i (nawet idealnie) czasami dziwnie jest mieć strukturę zagnieżdżoną, gdy naprawdę reprezentujesz płaską rzecz. –

+0

Czasami jest wiele rzeczy, które sprawiają, że zagnieżdżanie się nie uruchamia. na przykład podczas zagnieżdżania, 'WithId >' różni się od '>'; ale chciałbym 'thing.withId (id) .withTimestamp (ts)' równoważne 'thing.withTimestamp (ts) .withId (id)'. –