私はHaskellと関数型プログラミング全般に非常に慣れていません。私の質問はかなり基本的です。パターンマッチングとガードの違いは何ですか?
パターンマッチングを使用した関数
check :: [a] -> String
check [] = "Empty"
check (x:xs) = "Contains Elements"
ガードを使用する関数
check_ :: [a] -> String
check_ lst
| length lst < 1 = "Empty"
| otherwise = "Contains elements"
私には、パターンマッチングとガードは基本的に同じように見えます。どちらも条件を評価し、trueの場合、それに接続された式を実行します。私の理解は正しいですか?
この例では、パターンマッチングまたはガードを使用して、同じ結果を得ることができます。しかし、何かが私にここで重要な何かを見逃していることを教えてくれます。常に一方を他方に置き換えることはできますか?
誰かがパターンマッチングがガードよりも好まれる例を教えてもらえますか?
実際、それらは根本的にかなり異なります!とにかく、少なくともHaskellでは。
ガードはよりシンプルで柔軟性が高く、基本的には一連のif/then式に変換される特別な構文です。ガードには任意のブール式を入れることができますが、通常のif
ではできなかったことはできません。
パターンマッチはさらにいくつかのことを行います:それらはデータを分解する唯一の方法であり、それらの中にバインド識別子をスコープ。ガードがif
式と同等であるのと同じ意味で、パターンマッチングはcase
式と同等です。宣言(最上位、またはlet
式のようなもの)もパターンマッチの形式であり、「通常の」定義は単純なパターン、つまり単一の識別子と一致します。
パターンマッチは、Haskellで実際に発生する主な方法である傾向もあります。パターンでデータを分解しようとする試みは、評価を強制する数少ないものの1つです。
ちなみに、実際にはトップレベルの宣言でパターンマッチングを行うことができます。
square = (^2)
(one:four:nine:_) = map square [1..]
これは、関連する定義のグループに役立つことがあります。
GHCも ViewPatterns拡張機能を提供します 両方を組み合わせた種類です。バインディングコンテキストで任意の関数を使用して、結果に対してパターンマッチングを実行できます。もちろん、これは通常のものの単なる構文糖です。
Whereを使用する日常の問題については、いくつかの大まかなガイドがあります。
1つまたは2つのコンストラクターの深さに直接一致する可能性のあるものには、必ずパターンマッチングを使用してください。この場合、複合データ全体については特に気にせず、構造のほとんどを気にします。 @
構文を使用すると、構造全体を変数にバインドし、その上でパターンマッチングを行うことができますが、1つのパターンでその多くを実行すると、醜く、すぐに読み取ることができなくなります。
パターンにきちんと対応しないいくつかのプロパティに基づいて選択をする必要がある場合は、確実にガードを使用してください。 2つのInt
値を比較して、どちらが大きいかを確認します。
大規模な構造の奥深くにあるいくつかのデータのみが必要な場合、特に構造全体を使用する必要がある場合、通常、ガードとアクセサー関数は、@
と_
でいっぱいの巨大なパターンよりも読みやすくなります。
異なるパターンで表される値に対して同じことを行う必要があるが、それらを分類するための便利な述語を使用する場合、ガード付きの単一の汎用パターンを使用するほうが通常は読みやすくなります。ガードのセットがすべてではない場合、すべてのガードが失敗すると、次のパターン(ある場合)にドロップダウンすることに注意してください。したがって、一般的なパターンをいくつかのフィルターと組み合わせて例外的なケースをキャッチし、他のすべてに対してパターンマッチングを実行して、気になる詳細を取得できます。
パターンで簡単にチェックできるものには、ガードを使用しないでください。空のリストのチェックは古典的な例です。そのためにパターンマッチを使用します。
一般に、疑わしい場合は、デフォルトでパターンマッチングを使用することをお勧めします。パターンが非常に醜く、または複雑になり始めたら、それを止めて、他にどのようにそれを書くことができるかを検討してください。ガードを使用する以外に、他のオプションには、部分式を個別の関数として抽出することや、パターンマッチングの一部をプッシュしてメイン定義から外すために、関数本体内にcase
式を配置することが含まれます。
私には、パターンマッチングとガードは基本的に同じように見えます。どちらも条件を評価し、trueの場合、それに接続された式を実行します。私の理解は正しいですか?
結構です。最初のパターンマッチングでは、任意の条件を評価できません。指定されたコンストラクタを使用して値が作成されたかどうかのみを確認できます。
2番目のパターンマッチングでは、変数をバインドできます。したがって、パターン_[]
_はガード_null lst
_と同等である可能性があります(同等ではないため、長さを使用しません-詳細は後で説明します)、パターン_x:xs
_は同等ではありませんパターンが変数x
とxs
をバインドしますが、ガードはバインドしないため、ガードnot (null lst)
に追加します。
length
の使用に関する注意:リストが空かどうかを確認するためにlength
を使用することは非常に悪い習慣です。長さを計算するには、リスト全体を通過する必要があるため、O(n)
時間、リストが空であるかどうかを確認するだけでは、O(1)
時間、null
またはパターンマッチングが必要です。さらに「長さ」を使用するだけでは、無限リストでは機能しません。
1つは、ブール式をガード内に置くことができます。
例 :
リスト内包と同様に、ブール式はパターンガード間で自由に組み合わせることができます。例えば:
f x | [y] <- x , y > 3 , Just z <- h y = ...
違いについて Learn You a Haskell からの素晴らしい引用があります:
パターンは、値が何らかの形式に準拠していることを確認し、それを分解する方法ですが、ガードは、値のプロパティ(またはそのいくつか)がtrueかfalseかをテストする方法です。これはifステートメントによく似ており、非常によく似ています。問題は、いくつかの条件があり、パターンと非常にうまく機能する場合、ガードはより読みやすくなることです。
他の良い答えに加えて、私はガードについて具体的に説明しようとします。ガードは単なる構文上の砂糖です。あなたがそれについて考えるならば、あなたはあなたのプログラムで以下の構造をしばしば持っているでしょう:
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チェーンが削除され、コードが読みやすくなります。