Haskell'deki korumalara karşı eğer-öyleyse-ise ve vakalar


105

Bir listenin n'inci öğesini bulan üç işlevim var:

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)

Kanımca ilk işlev en iyi uygulama çünkü en kısa olanıdır. Ancak diğer iki uygulama hakkında onları tercih edilebilir kılacak herhangi bir şey var mı? Ve uzantı olarak, korumaları, eğer-ise-değilse ifadelerini ve vakaları kullanmayı nasıl seçersiniz?


5
casekullandıysanız iç içe geçmiş ifadelerinizi daraltabilirsinizcase compare a 0 of LT -> ... | EQ -> ... | GT -> ...
rampion

5
@rampion: you meancase compare a 1 of ...
newacct

Yanıtlar:


122

Teknik açıdan, üç versiyonun tümü eşdeğerdir.

Bununla birlikte, stillerle ilgili temel kuralım, eğer onu İngilizcemiş |gibi okuyabiliyorsanız ( "ne zaman", | otherwise"aksi" ve ="olduğu" veya "olduğu" olarak okuyun), muhtemelen bir şeyler yapıyorsunuzdur. sağ.

if..then..elseEğer varsa içindir bir ikili koşulu veya yapmak gerekir tek bir karar. İç içe if..then..elseifadeler Haskell'de çok nadirdir ve bunun yerine hemen hemen her zaman korumalar kullanılmalıdır.

let absOfN =
  if n < 0 -- Single binary expression
  then -n
  else  n

Her if..then..elseifade, bir işlevin en üst seviyesindeyse bir koruma ile değiştirilebilir ve daha fazla durum ekleyebileceğinizden bu genellikle tercih edilmelidir:

abs n
  | n < 0     = -n
  | otherwise =  n

case..ofEğer varsa için birden fazla kod yollarını ve her kod yolu tarafından yönlendirilir yapının bir değer, örneğin, ile desen eşleştirme. Çok nadiren eşleştirilecek Trueve False.

case mapping of
  Constant v -> const v
  Function f -> map f

Muhafızlar case..ofifadeleri tamamlar , yani bir değere bağlı olarak karmaşık kararlar vermeniz gerekiyorsa, önce girdinizin yapısına bağlı olarak kararlar verin ve ardından yapıdaki değerler üzerinde kararlar verin.

handle  ExitSuccess = return ()
handle (ExitFailure code)
  | code < 0  = putStrLn . ("internal error " ++) . show . abs $ code
  | otherwise = putStrLn . ("user error " ++)     . show       $ code

BTW. Bir stil ipucu olarak, her zaman sonra bir yeni satır yapmak =veya bir önceki |sonrasında ise malzeme =/ |uzun bir satır veya kullanımları başka bir nedenden dolayı daha fazla çizgiler için çok geçerli:

-- NO!
nthElement (x:xs) a | a <= 0 = Nothing
                    | a == 1 = Just x
                    | a > 1 = nthElement xs (a-1)

-- Much more compact! Look at those spaces we didn't waste!
nthElement (x:xs) a
  | a <= 0    = Nothing
  | a == 1    = Just x
  | otherwise = nthElement xs (a-1)

1
"Çok nadiren eşleşiyorsun Trueve False" bunu yapacağın herhangi bir fırsat var mı? Sonuçta, bu tür bir karar her zaman bir ifile ve gardiyanlarla da yapılabilir.
leftaroundabout

2
Egcase (foo, bar, baz) of (True, False, False) -> ...
dflemstr

@dflemstr MonadPlus gerektiren korumalar gibi daha ince farklılıklar yok mu ve eğer-then-else gerektirmezken bir monad örneğini döndürür mü? Ama emin değilim.
J Fritsch

2
@JFritsch: guardfonksiyon gerektirir MonadPlus, ancak burada bahsettiğimiz şey, | test =cümleciklerde olduğu gibi, birbiriyle ilişkili olmayan korumalar .
Ben Millwood

Stil ipucu için teşekkürler, şimdi şüpheyle doğrulandı.
eigenfield

22

Bunun açıkça özyinelemeli işlevler için stil hakkında bir soru olduğunu biliyorum, ancak en iyi stilin bunun yerine mevcut özyinelemeli işlevleri yeniden kullanmanın bir yolunu bulmak olduğunu söyleyebilirim.

nthElement xs n = guard (n > 0) >> listToMaybe (drop (n-1) xs)

3

Bu sadece bir sipariş meselesi ama bence çok okunabilir ve gardiyanlarla aynı yapıya sahip.

nthElement :: [a] -> Int -> Maybe a 
nthElement [] a = Nothing
nthElement (x:xs) a = if a  < 1 then Nothing else
                      if a == 1 then Just x
                      else nthElement xs (a-1)

Sonuncusu gerekmiyor ve başka olasılıklar olmadığından, herhangi bir şeyi gözden kaçırmanız durumunda işlevler de "son çare durumuna" sahip olmalıdır.


4
İç içe geçmiş if ifadeleri, vaka korumalarını kullanabildiğiniz zaman bir anti-modeldir.
user76284
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.