Mam trzy funkcje, które znajdują n-ty element listy:
nthElement :: [a] -> Int -> Maybe a
nthElement [] a = Nothing
nthElement (x:xs) a | a <= 0 = Nothing
| a == 1 = Just x
| a > 1 = nthElement xs (a-1)
nthElementIf :: [a] -> Int -> Maybe a
nthElementIf [] a = Nothing
nthElementIf (x:xs) a = if a <= 1
then if a <= 0
then Nothing
else Just x -- a == 1
else nthElementIf xs (a-1)
nthElementCases :: [a] -> Int -> Maybe a
nthElementCases [] a = Nothing
nthElementCases (x:xs) a = case a <= 0 of
True -> Nothing
False -> case a == 1 of
True -> Just x
False -> nthElementCases xs (a-1)
Moim zdaniem pierwsza funkcja jest najlepszą implementacją, ponieważ jest najbardziej zwięzła. Ale czy jest coś w pozostałych dwóch implementacjach, co sprawiłoby, że byłyby lepsze? A co za tym idzie, jak wybrałbyś między używaniem strażników, stwierdzeniami if-to-else i przypadkami?
haskell
if-statement
case
nukleartyd
źródło
źródło
case
instrukcje, jeśli użyłeścase compare a 0 of LT -> ... | EQ -> ... | GT -> ...
case compare a 1 of ...
Odpowiedzi:
Z technicznego punktu widzenia wszystkie trzy wersje są równoważne.
Biorąc to pod uwagę, moją praktyczną zasadą dotyczącą stylów jest to, że jeśli możesz czytać to tak, jakby był angielski (czytaj
|
„kiedy”,| otherwise
jako „inaczej” i=
jako „jest” lub „być”), prawdopodobnie robisz coś dobrze.if..then..else
dotyczy sytuacji, gdy masz jeden warunek binarny lub jedną decyzję, którą musisz podjąć. Zagnieżdżoneif..then..else
wyrażenia są bardzo rzadkie w Haskell i prawie zawsze należy używać strażników.Każde
if..then..else
wyrażenie można zastąpić strażnikiem, jeśli znajduje się na najwyższym poziomie funkcji, i generalnie powinno to być preferowane, ponieważ można wtedy łatwiej dodać więcej przypadków:case..of
jest dla sytuacji, gdy masz wiele ścieżek kodu , a każda ścieżka kodu jest prowadzona przez strukturę wartości, tj. poprzez dopasowywanie wzorców. Bardzo rzadko pasujesz naTrue
iFalse
.Strażnicy uzupełniają
case..of
wyrażenia, co oznacza, że jeśli musisz podejmować skomplikowane decyzje w zależności od wartości, najpierw podejmuj decyzje w zależności od struktury danych wejściowych, a następnie podejmuj decyzje dotyczące wartości w strukturze.BTW. Jako wskazówka stylistyczna, zawsze rób nową linię po
=
lub przed a,|
jeśli element po=
/|
jest zbyt długi dla jednej linii lub używa więcej linii z innego powodu:źródło
True
iFalse
” czy jest jakaś okazja, by to zrobić? W końcu tego rodzaju decyzje zawsze można podjąć za pomocąif
, a także ze strażnikami.case (foo, bar, baz) of (True, False, False) -> ...
guard
funkcja wymagaMonadPlus
, ale mówimy tutaj o strażnikach, jak w| test =
klauzulach, które nie są powiązane.Wiem, że jest to kwestia stylu dla funkcji jawnie rekurencyjnych, ale sugerowałbym, że najlepszym stylem jest znalezienie sposobu na ponowne użycie istniejących funkcji rekurencyjnych.
źródło
To tylko kwestia zamówienia, ale myślę, że jest bardzo czytelny i ma taką samą strukturę jak osłony.
To ostatnie nie potrzebuje, a jeśli nie ma innych możliwości, również funkcje powinny mieć „przypadek ostatniej szansy” na wypadek, gdybyś coś przeoczył.
źródło