Guards vs. if-then-else vs. Haskell의 케이스


104

목록의 n 번째 요소를 찾는 세 가지 함수가 있습니다.

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)

제 생각에는 첫 번째 기능이 가장 간결하기 때문에 최상의 구현입니다. 그러나 다른 두 가지 구현에 대해 선호하는 것이 있습니까? 그리고 더 나아가 가드, if-then-else 문 및 케이스 사용 중에서 어떻게 선택 하시겠습니까?


5
case사용한 경우 중첩 된 문을 접을 수 있습니다.case compare a 0 of LT -> ... | EQ -> ... | GT -> ...
— rampion

5
@rampion : 당신이 의미case compare a 1 of ...
— newacct

답변:


121

기술적 관점에서 세 가지 버전은 모두 동일합니다.

즉, 스타일에 대한 제 경험 법칙은 마치 영어처럼 읽을 수 있다면 ( |"언제", | otherwise"그렇지 ="또는 "있음"으로 읽음 ) 뭔가를하고있을 것입니다. 권리.

if..then..else당신이있을 때위한 하나 개의 바이너리 조건 , 또는 당신이해야 할 하나의 결정을. 중첩 if..then..else표현식은 Haskell에서 매우 드물며 가드는 거의 항상 대신 사용해야합니다.

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

if..then..else함수의 최상위 수준에있는 경우 모든 표현식을 가드로 대체 할 수 있으며, 더 많은 경우를 더 쉽게 추가 할 수 있으므로 일반적으로 선호됩니다.

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

case..of만약이 때위한 다중 코드 경로 , 모든 코드 경로에 의해 유도되는 구조 값의 예를 통해 패턴 매칭. True및에서 거의 일치하지 않습니다 False.

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

가드는 case..of표현식을 보완 합니다. 즉, 값에 따라 복잡한 결정을 내려야하는 경우 먼저 입력의 구조에 따라 결정을 내린 다음 구조의 값에 대한 결정을 내립니다.

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

BTW. 스타일 팁으로 / 뒤의 내용 이 한 줄에 너무 길거나 다른 이유로 더 많은 줄을 사용 =하는 |경우 항상 a 뒤 또는 a 앞에 개행을 만드십시오 .=|

-- 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
"당신은 매우 드물게에 일치하지 True하고 False"당신이 할 수있는 모든에서 어떤 행사가 있습니까? 결국, 의사 결정의이 종류는 수 항상 와 함께 할 if, 또한 가드와.
— leftaroundabout

2
예case (foo, bar, baz) of (True, False, False) -> ...
— dflemstr

@dflemstr 더 이상 미묘한 차이가 없습니까? 예를 들어 가드가 MonadPlus를 필요로하고 if-then-else는 그렇지 않은 동안 모나드의 인스턴스를 반환합니까? 하지만 확실하지 않습니다.
— J Fritsch

2
@JFritsch :이 guard함수 는를 필요 MonadPlus로하지만 여기서 우리가 말하는 | test =것은 관련이없는 절 에서 가드 입니다.
— Ben Millwood

스타일 팁에 감사드립니다. 이제 의심으로 확인되었습니다.
— truthadjustr

22

이것이 명시 적으로 재귀 함수에 대한 스타일에 대한 질문이라는 것을 알고 있지만 가장 좋은 스타일은 기존 재귀 함수를 대신 재사용하는 방법을 찾는 것입니다.

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

2

이것은 주문의 문제이지만 매우 읽기 쉽고 경비원과 동일한 구조를 가지고 있다고 생각합니다.

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)

마지막 다른 것은 필요하지 않으며 다른 가능성이 없기 때문에 함수도 놓친 경우 "마지막 수단"이 있어야합니다.


4
중첩 된 if 문은 케이스 가드를 사용할 수있는 경우 안티 패턴입니다.
— user76284
당사 사이트를 사용함과 동시에 당사의 쿠키 정책과 개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.