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.