ハスケルの警備員vs. if-then-else vs.事件


104

リストの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ステートメント、ケースのいずれを使用するかを選択しますか?


5
あなたは、ネストされた折りたたむことができcase、あなたが使用した場合の文をcase compare a 0 of LT -> ... | EQ -> ... | GT -> ...
ホタルブクロ

5
@rampion:あなたが意味case compare a 1 of ...
newacct

回答:


121

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

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

if..then..elseバイナリ条件1つある場合、または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式を補完します。つまり、値に応じて複雑な決定を行う必要がある場合は、最初に入力の構造に応じて決定し、次に構造内の値を決定します。

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

ところで。スタイルのヒントとして、/の後の行が1行に対して長すぎる場合、またはその他の理由で行数を増やす場合は、常に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 =で説明しているのは、関連のない句のようなガードです。
ベンミルウッド、2012年

スタイルのヒントをありがとう、今では疑いによって確認されました。
TruthAdjuster

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)

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


4
ケースガードを使用できる場合、ネストされたifステートメントはアンチパターンです。
user76284 2017年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.