web-dev-qa-db-ja.com

Haskellのガード対if-then-else対ケース

リストのn番目の要素を見つける3つの関数があります。

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)

私の意見では、最初の関数は最も簡潔であるため、最適な実装です。しかし、他の2つの実装について、それらを好ましいものにするものはありますか?また、拡張機能により、ガード、if-then-elseステートメント、およびケースの使用をどのように選択しますか?

97
Jason Tu

技術的な観点から、3つのバージョンはすべて同等です。

そうは言っても、スタイルに関する私の経験則は、英語のように読むことができる場合(|を "when"として、| otherwiseを "otherwise"として、=として「is」または「be」)、あなたはおそらく正しいことをしているでしょう。

if..then..elseは、1つの2つの条件がある場合、または1つの決定が必要な場合に使用します。ネストされた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は、複数のコードパスがあり、すべてのコードパスが構造によって導かれる場合に使用します値の、つまりパターンマッチング経由。 TrueFalseで一致することはほとんどありません。

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

ガードはcase..of式を補完します。つまり、値に応じて複雑な決定を行う必要がある場合、firstは構造に応じて決定を行います。入力、およびthenは、構造体の値を決定します。

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

BTW。スタイルのヒントとして、=の後または|の後のものが=の後にある場合は、常に|の前に改行を入れます1行には長すぎる、または他の何らかの理由でより多くの行を使用する:

-- 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)
114
dflemstr

これは明示的に再帰的な関数のスタイルに関する質問であることは知っていますが、代わりに既存の再帰関数を再利用する方法を見つけることをお勧めします。

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

これは単なる順序付けの問題ですが、非常に読みやすく、ガードと同じ構造を持っていると思います。

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)

最後の要素は必要ありません。他に可能性がないため、何かを見逃した場合に備えて、関数にも「最後の手段」が必要です。

1
Cristian Garcia