1. 概要
パターンマッチングはScala言語の強力な機能です。より簡潔で読みやすいコードを可能にすると同時に、複雑なパターンに対して要素をマッチングする機能を提供します。
このチュートリアルでは、一般的なパターンマッチングの使用方法と、それを活用する方法について説明します。
2. パターンマッチング
Javaのswitchステートメントで使用できる「完全一致」とは対照的に、パターンマッチングでは、正確な値ではなくパターンを一致させることができます。
たとえばJavaでは、 input.equals(caseClause)が false を返す場合、次の case句を直接評価します。ただし、これは常に機能する方法ではありません。 Scalaで。
パターンマッチングがどのように機能するかをさらに詳しく見ていきましょう。
2.1. 構文
match 式は、複数の部分で構成されています。
- パターンを一致させるために使用する値は、候補と呼ばれます。
- キーワードmatch
- ケースキーワード、パターン、矢印記号、およびパターンが一致したときに実行するコードで構成される複数のケース句
- 他のパターンが一致しない場合のデフォルト句。 デフォルト句は、アンダースコア文字(_)で構成され、ケース句の最後のであるため、認識できます。
これらの部分を説明するための簡単な例を見てみましょう。
def patternMatching(candidate: String): Int = {
candidate match {
case "One" => 1
case "Two" => 2
case _ => -1
}
}
3. 一致式のパターン
3.1. ケースクラス
ケースクラスは、継承の力を使用してパターンマッチングを実行するのに役立ちます。 ケースクラスは、共通の抽象クラスを拡張します。 次に、match式は、各ケースクラスによって表現された各パターンに対して抽象クラスの参照を評価します。
クラスを書くことから始めましょう:
abstract class Animal
case class Mammal(name: String, fromSea: Boolean) extends Animal
case class Bird(name: String) extends Animal
case class Fish(name: String) extends Animal
次に、パターンマッチングをケースクラスに適用する方法を見てみましょう。
def caseClassesPatternMatching(animal: Animal): String = {
animal match {
case Mammal(name, fromSea) => s"I'm a $name, a kind of mammal. Am I from the sea? $fromSea"
case Bird(name) => s"I'm a $name, a kind of bird"
case _ => "I'm an unknown animal"
}
}
この種のパターンマッチングの別名はコンストラクターマッチングです。これは、この場合、コンストラクターを使用してマッチングを可能にすることを意味します。
たとえば、 Mammal パターンが、上記で定義したケースクラスのコンストラクターと正確に一致することがわかります。
3.2. 定数
ほとんどの言語と同様に、Scalaは定数を使用して数値またはブール値を定義します。 パターンは定数で構成できます。
一致式で定数を使用する方法を見てみましょう。
def constantsPatternMatching(constant: Any): String = {
constant match {
case 0 => "I'm equal to zero"
case 4.5d => "I'm a double"
case false => "I'm the contrary of true"
case _ => s"I'm unknown and equal to $constant"
}
}
3.3. シーケンス
配列、リスト、およびベクトルは要素で構成されます。 これらのシーケンスとその要素は、パターンを形成するためにも使用されます。
さらに、通常、ワイルドカードを使用してパターンの動的な部分を表現します。
- 単一の要素に一致させるために、アンダースコアワイルドカードを使用します _ 。 これは、アンダースコア文字も使用するデフォルトの句と混同しないでください。 エイリアスを使用して要素を表すこともできます
- 一方、未知の数の要素(0、1、またはそれ以上)に一致させるために、スターワイルドカードを使用します*
パターンとして使用した場合のシーケンスの外観を見てみましょう。
def sequencesPatternMatching(sequence: Any): String = {
sequence match {
case List(singleElement) => s"I'm a list with one element: $singleElement"
case List(_, _*) => s"I'm a list with one or multiple elements: sequence"
case Vector(1, 2, _*) => s"I'm a vector: $sequence"
case _ => s"I'm an unrecognized sequence. My value: $sequence"
}
}
最初のcase句では、エイリアス– singleElement –を使用して、Listの単一要素を定義しました。
他のcase句では、アンダースコア文字を使用して値を無視しているだけです。デフォルトのcase句で使用されるほか、一致で特定の値が無視される場合にもアンダースコア文字を使用できます。表現。
3.4. タプル
タプルは、限られた数のサブオブジェクトを含むオブジェクトです。 それらは、限られたサイズの混合要素のコレクションとして想像することができます。
次に、パターンマッチングでタプルを使用する方法の例を見てみましょう。
def tuplesPatternMatching(tuple: Any): String = {
tuple match {
case (first, second) => s"I'm a tuple with two elements: $first & $second"
case (first, second, third) => s"I'm a tuple with three elements: $first & $second & $third"
case _ => s"Unrecognized pattern. My value: $tuple"
}
}
この例では、タプルパターンで定義された名前を使用して要素を抽出しました。 タプルの最初の要素を使用する場合は、タプルパターンでローカル変数を定義します。 上記の例では、これらの変数を first 、 second 、およびthirdとして認識できます。
3.5. 型付きパターン
Scalaは型付き言語です。つまり、各オブジェクトには変更できない静的型があります。 たとえば、 Boolean オブジェクトには、ブール式のみを含めることができます。
以下に示すように、Scalaを使用すると、オブジェクトを型パターンと簡単に照合できます。
def typedPatternMatching(any: Any): String = {
any match {
case string: String => s"I'm a string. My value: $string"
case integer: Int => s"I'm an integer. My value: $integer"
case _ => s"I'm from an unknown type. My value: $any"
}
}
3.6. 正規表現パターン
文字列を操作するときに正規表現がどれほど役立つかはすでにわかっています。 Scalaには朗報があります— 一致式でオブジェクトを一致させるときに正規表現を使用することもできます:
def regexPatterns(toMatch: String): String = {
val numeric = """([0-9]+)""".r
val alphabetic = """([a-zA-Z]+)""".r
val alphanumeric = """([a-zA-Z0-9]+)""".r
toMatch match {
case numeric(value) => s"I'm a numeric with value $value"
case alphabetic(value) => s"I'm an alphabetic with value $value"
case alphanumeric(value) => s"I'm an alphanumeric with value $value"
case _ => s"I contain other characters than alphanumerics. My value $toMatch"
}
}
3.7. オプション: いくつかとなし
Scalaのような関数型言語では、オプションは値を含むか含まない構造です。 ScalaのOptionは、JavaのOptionalクラスと簡単に比較できます。
オプションオブジェクトを使用してパターンマッチングが可能です。 この場合、2つの可能なcase句があります。
- いくつか —タイプの値を含む T
- なし—何も含まれていません
一致式でそれらをどのように使用するかを見てみましょう。
def optionsPatternMatching(option: Option[String]): String = {
option match {
case Some(value) => s"I'm not an empty option. Value $value"
case None => "I'm an empty option"
}
}
3.8. 可変バインディング
@ 記号を使用して、変数を完全一致または部分一致の結果にバインドすることもできます。
def binderPatternMatching(animal: Any): String = {
animal match {
case m@Mammal(_, true) => s"${m.name} is a mammal from sea"
case Mammal(name, fromSea) => s"${name} is a mammal, fromSea:${fromSea}"
case _ => "unknown animal"
}
}
この場合、変数 m は、fromSeaがtrueであるMammalオブジェクトに割り当てられます。
同様に、一致したオブジェクトの一部を変数にバインドできます。 値Lionのみを変数nameに割り当てたいとしましょう。
def binderPatternWithPartMatch(animal: Any): String = {
animal match {
case Mammal(name @ "Lion", _) => s"$name is a mammal"
case _ => "unknown"
}
}
4. パターンガード
パターンマッチングがいかに強力であるかはすでに見てきました。 さまざまな方法でパターンを構築できます。 ただし、case句内のコードを実行するためのパターンマッチングに加えて、特定の条件が満たされていることを確認したい場合があります。
パターンガードを使用して、この動作を実現できます。 パターンガードは、case句と同じレベルで一緒に使用されるブール式です。
パターンガードを使用することがどれほど便利か見てみましょう。
def patternGuards(toMatch: Any, maxLength: Int): String = {
toMatch match {
case list: List[Any] if (list.size <= maxLength) => "List is of acceptable size"
case list: List[Any] => "List has not an acceptable size"
case string: String if (string.length <= maxLength) => "String is of acceptable size"
case string: String => "String has not an acceptable size"
case _ => "Input is neither a List nor a String"
}
}
コードスニペットの例では、StringオブジェクトとListオブジェクトの両方にそれぞれ2つのパターンがあります。 それぞれの違いは、パターンの1つがcase句に入る前にオブジェクトの長さもチェックするという事実にあります。
たとえば、 List に5つのオブジェクトが含まれているが、最大長が6の場合、最初のcase句に入り、戻ります。 一方、最大長が4の場合、2番目の句のコードが実行されます。
5. 封印されたクラス
パターンマッチングと一緒に封印されたクラスを使用したい場合があります。 封印されたクラスは、それを拡張するすべての単一クラスを認識するスーパークラスです。この動作は、封印されたクラスとそのすべてのサブクラスを表現するために同じ単一のファイルを使用して可能です。
この機能は、一致式にデフォルトの動作がないようにする場合に特に便利です。
封印されたクラスとその子クラスを作成することから始めましょう。
sealed abstract class CardSuit
case class Spike() extends CardSuit
case class Diamond() extends CardSuit
case class Heart() extends CardSuit
case class Club() extends CardSuit
その後、これらのクラスでパターンマッチングを使用しましょう。
def sealedClass(cardSuit: CardSuit): String = {
cardSuit match {
case Spike() => "Card is spike"
case Club() => "Card is club"
case Heart() => "Card is heart"
case Diamond() => "Card is diamond"
}
}
ここでは、サブクラスごとにケースがあるため、デフォルトのcase句の使用は必須ではありません。
6. 抽出器
抽出オブジェクトは、と呼ばれるメソッドを含むオブジェクトです。 適用しない。 このメソッドは、パターンとの照合が成功したときに実行されます。
これを例で使用してみましょう。 フルネームを含むPersonがあり、 Person がパターンと照合される場合、フルネームを使用する代わりに、イニシャルが必要であるとします。
この要件を実装するときにエクストラクタがどのように役立つかを見てみましょう。
まず、Personオブジェクトを作成します。
object Person {
def apply(fullName: String) = fullName
def unapply(fullName: String): Option[String] = {
if (!fullName.isEmpty)
Some(fullName.replaceAll("(?<=\\w)(\\w+)", "."))
else
None
}
}
Person オブジェクトが存在するので、 match 式で使用し、unapplyメソッドの結果を利用できるようになります。
def extractors(person: Any): String = {
person match {
case Person(initials) => s"My initials are $initials"
case _ => "Could not extract initials"
}
}
その人の名前がジョンスミス、 この場合、返される弦だろう ‘ 私のイニシャルはJです。 S。 ‘。
7. その他の使用法
7.1. クロージャ
クロージャーもパターンマッチングを使用できます。
クロージャーパターンマッチングが実際にどのように見えるかを見てみましょう。
def closuresPatternMatching(list: List[Any]): List[Any] = {
list.collect { case i: Int if (i < 10) => i }
}
呼び出されると、このコードは次のようになります。
- Intではない要素を除外します
- 10未満のIntを除外します
- リストから残りの要素を新しいリストに収集します
7.2. キャッチブロック
また、パターンマッチングを使用して、try-catchブロックでスローされた例外を処理することもできます。
これを実際に見てみましょう:
def catchBlocksPatternMatching(exception: Exception): String = {
try {
throw exception
} catch {
case ex: IllegalArgumentException => "It's an IllegalArgumentException"
case ex: RuntimeException => "It's a RuntimeException"
case _ => "It's an unknown kind of exception"
}
}
8. 結論
このチュートリアルでは、Scalaの強力なパターンマッチングをさまざまな方法で使用する方法を発見しました。
いつものように、このチュートリアルで使用されるすべてのソースコードは、GitHubのにあります。