2012-02-19 37 views
85

tôi có ba chức năng mà tìm ra nguyên tố thứ n của một danh sách:Guards vs if-then-else vs trường hợp trong Haskell

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) 

Theo tôi, chức năng đầu tiên là việc thực hiện tốt nhất bởi vì nó được ngắn gọn nhất. Nhưng có bất kỳ điều gì về hai cách triển khai khác sẽ khiến chúng trở nên thích hợp hơn không? Và bằng cách mở rộng, làm thế nào bạn sẽ lựa chọn giữa sử dụng bảo vệ, nếu-sau đó khác báo cáo, và các trường hợp?

+5

bạn có thể thu gọn câu lệnh 'case' lồng nhau nếu bạn sử dụng' case so sánh 0 của LT -> ... | EQ -> ... | GT -> ... ' – rampion

+5

@ rampion: bạn có nghĩa là 'trường hợp so sánh 1 của ...' – newacct

Trả lời

97

Từ quan điểm kỹ thuật, cả ba phiên bản đều tương đương nhau.

Điều đó đang được nói, quy tắc chung của tôi về phong cách là nếu bạn có thể đọc nó như thể đó là tiếng Anh (đọc | là "when", | otherwise là "else" và = là "is" hoặc "be") , có lẽ bạn đang làm điều gì đó đúng.

if..then..else là khi bạn có một điều kiện nhị phân hoặc một quyết định duy nhất bạn cần thực hiện. Nested if..then..else -expressions là rất không phổ biến trong Haskell, và bảo vệ hầu như luôn luôn nên được sử dụng để thay thế.

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

Mỗi biểu if..then..else thể được thay thế bởi một người bảo vệ nếu nó là ở cấp cao nhất của một hàm, và điều này thường nên được ưa thích, vì bạn có thể bổ sung thêm trường hợp dễ dàng hơn sau đó:

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

case..of là khi bạn có nhiều đường dẫn mã và mọi đường dẫn mã được hướng dẫn bởi cấu trúc của giá trị, tức là thông qua kết hợp mẫu. Bạn rất hiếm khi kết hợp trên TrueFalse.

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

vệ bổ sung cho case..of biểu thức, có nghĩa là nếu bạn cần phải đưa ra quyết định phức tạp tùy thuộc vào một giá trị, đầu tiên đưa ra quyết định tùy thuộc vào cấu trúc của đầu vào của bạn, và sau đó đưa ra quyết định về các giá trị trong kết cấu.

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

BTW. Như một mẹo phong cách, luôn luôn làm cho một dòng mới sau một = hoặc trước một | nếu những thứ sau =/| là quá dài đối với một dòng, hoặc sử dụng nhiều đường vì một lý do khác:

-- 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

"Bạn rất hiếm khi phù hợp trên' True' và 'False'" là có bất kỳ dịp nào bạn sẽ làm điều đó? Xét cho cùng, loại quyết định này có thể được thực hiện với một 'if', và với những người bảo vệ nữa. – leftaroundabout

+0

Ví dụ: 'trường hợp (foo, bar, baz) của (True, False, False) -> ...' – dflemstr

+0

@dflemstr Không có bất kỳ sự khác biệt tinh tế nào, ví dụ: bảo vệ yêu cầu MonadPlus và trả lại một bản sao của đơn nguyên trong khi nếu-thì-người khác thì không? Nhưng tôi không chắc chắn. –

21

tôi biết đây là câu hỏi về phong cách cho các hàm đệ quy một cách rõ ràng, nhưng tôi cho rằng kiểu tốt nhất là tìm cách sử dụng lại các hàm đệ quy hiện có để thay thế.

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

Đây chỉ là vấn đề đặt hàng nhưng tôi nghĩ rằng nó rất dễ đọc và có cấu trúc giống như bảo vệ.

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) 

Điều cuối cùng không cần và nếu không có khả năng khác, các chức năng cũng phải có "trường hợp cuối cùng" trong trường hợp bạn bỏ sót bất kỳ điều gì.

+0

Các câu lệnh if lồng nhau là một mẫu chống khi bạn có thể sử dụng các nhân viên bảo vệ trường hợp. – user76284

Các vấn đề liên quan