Các trường hợp bảo vệ so với if-then-else so với Haskell


104

Tôi có ba hàm tìm phần tử thứ n của danh sách:

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à thực hiện tốt nhất vì nó ngắn gọn nhất. Nhưng có điều gì về hai cách triển khai kia khiến chúng trở nên thích hợp hơn không? Và mở rộng, bạn sẽ chọn như thế nào giữa việc sử dụng bảo vệ, câu lệnh if-then-else và các trường hợp?


5
bạn có thể thu gọn các casecâu lệnh lồng vào nhau của mình nếu bạn đã sử dụngcase compare a 0 of LT -> ... | EQ -> ... | GT -> ...
rapter 19/02/12

5
@raosystem: ý bạn làcase compare a 1 of ...
newacct

Câu trả lời:


121

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 ngón tay cái của tôi cho các phong cách là nếu bạn có thể đọc nó như thể nó là tiếng Anh (đọc |là "khi nào", | otherwisenhư "nếu không" và =như "là" hoặc "được"), có thể bạn đang làm gì đó đúng.

if..then..elselà 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. Các if..then..elsebiểu thị- lồng nhau rất không phổ biến trong Haskell và thay vào đó hầu như luôn luôn phải sử dụng các biện pháp bảo vệ.

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

Mọi if..then..elsebiểu thức có thể được thay thế bằng một bảo vệ nếu nó ở cấp cao nhất của một hàm và điều này thường được ưu tiên hơn, vì bạn có thể thêm nhiều trường hợp dễ dàng hơn sau đó:

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

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

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

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

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 tạo kiểu, hãy luôn tạo một dòng mới sau một =hoặc trước một |nếu nội dung sau =/ |quá dài cho một dòng hoặc sử dụng nhiều dòng hơn vì một số 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 kết hợp TrueFalse" có khi nào bạn muốn làm điều đó không? Rốt cuộc, loại quyết định này luôn có thể được thực hiện với an if, và với cả lính canh.
bùng binh

2
Ví dụcase (foo, bar, baz) of (True, False, False) -> ...
dflemstr

@dflemstr Không có bất kỳ sự khác biệt nhỏ nào hơn, ví dụ như lính canh yêu cầu MonadPlus và trả về một bản sao của monad trong khi if-then-else thì không? Nhưng tôi không chắc.
J Fritsch

2
@JFritsch: guardchức năng yêu cầu MonadPlus, nhưng những gì chúng ta đang nói ở đây là lính canh như trong | test =các mệnh đề, không liên quan.
Ben Millwood

Cảm ơn vì mẹo phong cách, bây giờ nó đã được xác nhận bởi sự nghi ngờ.
truthadjustr

22

Tôi biết đây là câu hỏi về kiểu cho các hàm đệ quy rõ ràng, nhưng tôi đề xuất 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)

2

Đây chỉ là một 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ư những người 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)

Cái khác cuối cùng không cần và nếu vì không có khả năng nào khác, các hàm cũng nên có "trường hợp cuối cùng" trong trường hợp bạn bỏ lỡ bất cứ điều gì.


4
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 trình bảo vệ trường hợp.
user76284
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.