64

私はHaskellと関数型プログラミング全般に非常に慣れていません。私の質問はかなり基本的なものです。パターンマッチングとガードの違いは何ですか?

パターンマッチングを利用した機能

check :: [a] -> String
check [] = "Empty"
check (x:xs) = "Contains Elements"

ガードを使用した機能

check_ :: [a] -> String
check_ lst
    | length lst < 1 = "Empty"
    | otherwise = "Contains elements"

私には、パターン マッチングとガードは基本的に同じように見えます。どちらも条件を評価し、true の場合はそれにフックされた式を実行します。私の理解は正しいですか?

この例では、パターン マッチングまたはガードを使用して同じ結果を得ることができます。しかし、ここで何か重要なことを見逃していると何かが教えてくれます。常に一方を他方に置き換えることはできますか?

パターン マッチングがガードよりも優先される例や、その逆の例を誰かが挙げてくれますか?

4

4 に答える 4

65

実は根本的に違うんです!いずれにせよ、少なくとも Haskell では。

ガードはよりシンプルで柔軟です。本質的には一連の if/then 式に変換される特別な構文です。ガードに任意のブール式を入れることができますが、通常の ではできないことは何もしませんif

パターン マッチは、さらにいくつかのことを行います。データを分解する唯一の方法であり、スコープ内で識別子をバインドします。ガードがif式と同等であるのと同じ意味で、パターン マッチングはcase式と同等です。宣言 (最上位またはlet式のようなもの) もパターン一致の形式であり、「通常の」定義は単純なパターン (単一の識別子) と一致します。

また、Haskell で実際に行われる主な方法は、パターン マッチである傾向があります。パターン内のデータを分解しようとすることは、評価を強制する数少ないことの 1 つです。

ところで、トップレベルの宣言で実際にパターン マッチングを行うことができます。

square = (^2)

(one:four:nine:_) = map square [1..]

これは、関連する定義のグループに役立つ場合があります。

GHCは、両方を組み合わせた ViewPatterns 拡張機能も提供します。バインディング コンテキストで任意の関数を使用し、結果に対してパターン マッチを行うことができます。もちろん、これはまだ通常のものの構文糖衣にすぎません。


どちらをどこで使用するかという日常の問題については、大まかなガイドを次に示します。

  • 1 つまたは 2 つのコンストラクターの深さで直接一致できるものには、必ずパターン マッチングを使用してください。複合データ全体はあまり気にしませんが、構造の大部分は気にします。この@構文を使用すると、全体的な構造を変数にバインドしながらパターン マッチングも行うことができますが、1 つのパターンで多くのことを行うと、すぐに見苦しく、読みにくくなります。

  • Int2 つの値を比較してどちらが大きいかを確認するなど、パターンにうまく対応していないプロパティに基づいて選択する必要がある場合は、必ずガードを使用してください。

  • 大規模な構造の奥深くからいくつかのデータのみが必要な場合、特に構造全体を使用する必要がある場合、ガードとアクセサー関数は通常、@とでいっぱいの巨大なパターンよりも読みやすくなり_ます。

  • 異なるパターンで表される値に対して同じことを行う必要があるが、それらを分類するための便利な述語を使用する必要がある場合は、通常、ガード付きの単一のジェネリック パターンを使用する方が読みやすくなります。一連のガードが網羅的でない場合、すべてのガードに失敗したものはすべて、次のパターン (存在する場合) にドロップされることに注意してください。したがって、一般的なパターンをフィルターと組み合わせて例外的なケースをキャッチし、それ以外のすべてに対してパターン マッチングを実行して、関心のある詳細を取得できます。

  • パターンで簡単にチェックできるものには絶対にガードを使用しないでください。空のリストのチェックは典型的な例です。そのためにはパターン マッチを使用します。

  • 一般に、疑わしい場合は、デフォルトでパターン マッチングをそのまま使用してください。通常は、その方が適切です。パターンが本当に見苦しくなったり、複雑になったりしたら、それを書く方法を考えるのをやめてください。ガードを使用する以外に、部分式を個別の関数として抽出したりcase、関数本体内に式を配置して、パターン マッチングの一部をメインの定義から除外したりすることもできます。

于 2010-11-11T16:44:22.003 に答える
10

1 つには、ガード内にブール式を入れることができます。

:

リスト内包表記と同様に、ブール式はパターン ガードと自由に組み合わせることができます。例えば:

f x | [y] <- x
    , y > 3
    , Just z <- h y
    = ...


アップデート

違いについて、Learn You a Haskellからの素晴らしい引用があります。

パターンは、値が何らかの形式に準拠していることを確認して分解する方法ですが、ガードは、値の一部のプロパティ (またはそれらのいくつか) が true か false かをテストする方法です。これは if ステートメントによく似ており、非常によく似ています。問題は、いくつかの条件があり、パターンで非常にうまく機能する場合、ガードははるかに読みやすいということです。

于 2010-11-11T16:37:09.183 に答える
10

私には、パターン マッチングとガードは基本的に同じように見えます。どちらも条件を評価し、true の場合はそれにフックされた式を実行します。私の理解は正しいですか?

そうではありません。最初のパターン マッチングでは、任意の条件を評価することはできません。特定のコンストラクターを使用して値が作成されたかどうかのみを確認できます。

2 番目のパターン マッチングでは、変数をバインドできます。したがって、パターン[]はガードと同等である可能性がありますがnull lst(長さは同等ではないため、長さを使用しません。後で詳しく説明します)、パターンは変数とをバインドするため、パターンはx:xsガードと同等ではありません。これはガードが行います。いいえ。not (null lst)xxs

使用上の注意length: を使用lengthしてリストが空であるかどうかを確認することは非常に悪い習慣です。長さを計算するには、リスト全体を処理する必要があり、O(n)時間がかかりますが、リストが空であるかどうかを確認するだけでもO(1)時間がかかりnullますパターンマッチング。さらに、単純に「長さ」を使用しても、無限リストでは機能しません。

于 2010-11-11T16:43:18.093 に答える
5

他の良い答えに加えて、ガードについて具体的に説明します。ガードは単なる構文糖衣です。よく考えてみると、プログラムには次のような構造が含まれていることがよくあります。

f y = ...
f x =
  if p(x) then A else B

つまり、パターンが一致する場合、その直後に if-then-else 識別が続きます。ガードは、この識別をパターン マッチに直接折り込みます。

f y = ...
f x | p(x) = A
    | otherwise = B

(は標準ライブラリでotherwise定義されています)。Trueこれは、if-then-else チェーンよりも便利であり、場合によってはバリアントごとにコードがはるかに単純になるため、if-then-else 構造よりも簡単に記述できます。

言い換えれば、多くの場合、コードを大幅に簡素化する方法で、別の構造の上に砂糖を重ねたものです。多くの if-then-else チェーンが排除され、コードが読みやすくなります。

于 2010-11-11T16:44:28.437 に答える