Po pierwsze, to nie mogą być bezpośrednio związane z pytaniem, ale może chcesz rearrage logikę w tej funkcji.
Zamiast:
"Patrzę dla klienta, który odpowiada FName, nazwisko i Emai; w przypadku jego braku, szukam tylko fname + e-mail, a potem po prostu e-mail, a następnie utworzyć gości"
może lepiej postępować tak:
„patrzę na email pasującym Jeżeli dostaję wiele odpowiedników, patrzę na pasującym fname, a jeśli jest wielokrotnością znowu patrzę na pasującym L-NAME” .
To nie tylko umożliwi lepszą strukturę kodu, ale zmusi cię do radzenia sobie z możliwymi problemami w logice.
Na przykład, jeśli masz wiele pasujących wiadomości e-mail, ale żadna z nich nie ma poprawnej nazwy? Obecnie wystarczy wybrać pierwszą sekwencję, która może być lub nie jest tym, czego potrzebujesz, w zależności od tego, w jaki sposób zamawiana jest Data.Customers(), , jeśli jest zamówiona,.
Teraz, jeśli wiadomości e-mail muszą być unikatowe, nie będzie to problemem - ale gdyby tak było, równie dobrze można pominąć sprawdzanie pierwszych/ostatnich nazw!
(Waham się o tym wspomnieć, ale może również nieco przyspieszyć twój kod, ponieważ nie musisz niepotrzebnie sprawdzać rekordów więcej niż jeden raz dla tych samych pól, ani nie sprawdzaj dodatkowych pól, gdy wystarczy tylko wiadomość e-mail.)
A teraz pytanie do ciebie - problem nie jest w użyciu Option
, problem polega na tym, że wykonujesz zasadniczo tę samą operację trzy razy! ("Znajdź pasujące wyniki, a jeśli nie, znajdź rezerwę"). Refaktoryzacja funkcji w sposób rekurencyjny wyeliminuje brzydką strukturę przekątną, i pozwala na trywialne rozszerzenie funkcji w przyszłości, aby sprawdzić dodatkowe pola.
Niektóre inne drobne sugestie dotyczące kodzie:
- Ponieważ tylko ty kiedykolwiek powołać się
validFoo
funkcji pomocniczych z tymi samymi argumentami dla Foo
można upiec je w definicji funkcji odchudzić się w kodzie.
- W przypadku rozróżniania wielkości znaków niewrażliwe na wielkość jest typowe, ale nieco nieoptymalne, ponieważ w rzeczywistości tworzy nowe małe kopie każdego ciągu. Właściwym sposobem jest użycie
String.Equals(a, b, StringComparison.CurrentCultureIgnoreCase)
. W 99% jest to nieistotna mikrooptymalizacja, ale jeśli masz ogromną bazę klientów i wykonujesz wiele wyszukiwań dla klientów, jest to funkcja, która może mieć znaczenie!
- Jeśli to możliwe, chciałbym zmodyfikować funkcję
createGuest
tak, że zwraca cały customer
obiektu, a jedynie przyjąć .id
jako ostatniej linii tej funkcji - albo jeszcze lepiej, zwracają customer
z tej funkcji, jak również i oferować oddzielny jednolinijkowy findCustomerId = findCustomer >> (fun c -> c.id)
dla łatwości użycia.
Z tym wszystkim mamy następujące.Dla przykładu, założę, że w przypadku wielu równorzędnych dopasowań, będziesz potrzebować ostatniego ostatniego lub najnowszego. Ale możesz również rzucić wyjątek, posortować według pola daty lub cokolwiek innego.
let findCustomerId fname lname email =
let (==) (a:string) (b:string) = String.Equals(a, b, StringComparison.CurrentCultureIgnoreCase)
let validFName = fun (cus:customer) -> fname == cus.firstname
let validLName = fun (cus:customer) -> lname == cus.lastname
let validEmail = fun (cus:customer) -> email == cus.email
let allCustomers = Data.Customers()
let pickBetweenEquallyValid = Seq.last
let rec check customers predicates fallback =
match predicates with
| [] -> fallback
| pred :: otherPreds ->
let matchingCustomers = customers |> Seq.filter pred
match Seq.length matchingCustomers with
| 0 -> fallback
| 1 -> (Seq.head matchingCustomers).id
| _ -> check matchingCustomers otherPreds (pickBetweenEquallyValid matchingCustomers).id
check allCustomers [validEmail; validFName; validLName] (createGuest())
Ostatnia sprawa: te brzydkie (i często O (n)) Seq.foo
wyrażenia wszędzie są konieczne, ponieważ nie wiem jaki rodzaj sekwencji Data.Customers
powraca, a ogólna Seq
klasa nie jest bardzo przyjazny dla dopasowywanie do wzorca.
Jeśli, na przykład, Data.Customers
zwraca tablicę, a następnie czytelność byłyby znacznie się poprawiła:
let pickBetweenEquallyValid results = results.[results.Length - 1]
let rec check customers predicates fallback =
match predicates with
| [] -> fallback
| pred :: otherPreds ->
let matchingCustomers = customers |> Array.filter pred
match matchingCustomers with
| [||] -> fallback
| [| uniqueMatch |] -> uniqueMatch.id
| _ -> check matchingCustomers otherPreds (pickBetweenEquallyValid matchingCustomers).id
check allCustomers [validEmail; validFName; validLName] (createGuest())
To podejście jest bardzo czytelne i łatwe do naśladowania, dziękuję! –
Mniej ogólne, ale ogólnie lepsze podejście niż moje. +1 To powiedziawszy, '| Niektóre x -> Niektóre x' są potencjalnie marnotrawstwem - jeśli kompilator nie zoptymalizuje tego poprawnie, może to spowodować dodatkową alokację. '| Niektóre _ jako x -> x' byłyby tak jasne i potencjalnie bardziej wydajne. – ildjarn
Zaid, dla graficznego obrazu zasad, które za tym stoją, spójrz na programowanie zorientowane na kolej, http://fsharpforfunandprofit.com/posts/recipe-part2/ –