let rec nth (list, i) =
match list, i with
| x::xs, 0 -> x
| x::xs, i -> nth(xs, i-1)
| [], _ -> ()
Funkcja ta rzeczywiście ma (niepożądanego) podpis Państwo wymienić:
val nth : unit list * int -> unit
Dlaczego? Spójrz na prawą stronę tych trzech reguł. Jeśli nie było to dla ()
, nie można powiedzieć, jaki konkretny rodzaj wartości zwraca twoja funkcja. Ale jak tylko dodasz ostatnią regułę, F # widzi wyrażenie ()
(które ma typ unit
) i od tego może wywnioskować typ zwrotu twojej funkcji; który nie jest już ogólny. Ponieważ jakakolwiek funkcja może mieć tylko jeden stały typ zwracany, wówczas zakłada, że x
, xs
również zawiera typ unit
, co powoduje podpis powyżej.
Jak już zauważyłem w kvb, chcesz czasami zwracać wartość, a na obsadzie pustej listy wejściowej nie chcesz zwracać niczego ... co oznacza, że twoja wartość zwracana powinna być 'a option
(może być również . zapisać jako option<'a>
btw)
let rec nth (list, i) =
match list, i with
| x::xs, 0 -> Some(x)
| x::xs, i -> nth(xs, i-1) // <-- nth already returns an 'a option,
| [], _ -> None // no need to "wrap" it once more
teraz zgłaszane podpis wygląda poprawne:
val nth : 'a list * int -> 'a option
dotyczące danej se pytanie warunkowe, Przyznaję, że nie mogę w pełni odpowiedzieć na to pytanie, ponieważ sam nadal jestem fanką. Jedna wskazówka: jeśli weźmiesz powyższą funkcję w poprawnej formie (ogólna wersja zwróci numer 'a option
), nie możesz pomóc, ale sprawdź wszystkie możliwe wartości zwracane:
Dlaczego? Bo jeśli chcesz dostać się do rzeczywistej wartości powrotnej (o nazwie x
w kodzie tylko pokazanego), trzeba „wyciąg” go za pomocą match
bloku:
let result = nth (someList, someIndex)
match result with
| Some(x) -> ...
| None -> ...
I od reguł powinny zawsze być wyczerpująca (bo kompilator narzeka), automatycznie musisz dodać regułę, która sprawdza, czy istnieje możliwość None
.
Kompilator będzie w rzeczywistości wymuszać, aby również rozważyć, co powinno się zdarzyć w sytuacji "błędu"; nie możesz tego zapomnieć. Masz tylko wybór, jak sobie z tym poradzić!
(Oczywiście, jak tylko zrobić programowania .NET i mieć do czynienia z typami, które mogą być null
, wszystko może wyglądać nieco inaczej, ponieważ null
nie jest rodowitym pojęcie F #).
Dalsze sugestia dla poprawy: Jeśli zmienić sposób, że funkcja nth
akceptuje swoje argumenty, pojawi się możliwość częściowego zastosowania go, co oznacza, że npże można go używać z operatorem rurociągów różnych:
let rec nth i list = // <-- swap order of arguments, don't pass them in
... // as a tuple but as two separate arguments
Teraz można to zrobić:
someList
|> nth someIndex
albo to:
let third = nth 2
someList |> third
Jeśli, z drugiej strony, to jedyna funkcja akceptuje krotkę, która nie zadziała. Zastanówmy się więc, czy naprawdę potrzebny jest parametr krotki: w tym przypadku faktycznie ogranicza on elastyczność, a co więcej, "znaczenie"/treść dwóch parametrów nie sugeruje, że powinny one zawsze być przechowywane i wyświetlane razem. Dlatego odradzam używanie w tym przypadku krotki.
"Ścisłość jest proporcjonalna do liczby programistów, którzy mogą nazwać twój kod" Niesamowite podsumowanie. – TechNeilogy
To może brzmieć odrobinę banalnie, ale nigdy nie można się pomylić z "skalnym" kodem. W końcu nie zawsze można zagwarantować, w jaki sposób Twój kod będzie używany lub przez kogo w przyszłości. – Daniel
@Daniel, To prawda, ale wiąże się to z kosztami związanymi z tą decyzją, zarówno w czasie projektowania, jak i konserwacji. Gdyby każda metoda miała być tą paranoją, skończyłaby się podstawą kodu, która jest pełna standardowego zestawu, a także niezbyt inspirującymi programistami. –