Scala Language
パターンマッチング
サーチ…
構文
- セレクタマッチpartialFunction
- セレクタの一致(大文字/小文字のリスト)//これは上記の中で最も一般的な形式です
パラメーター
パラメータ | 詳細 |
---|---|
セレクタ | 値がパターンマッチングされている式。 |
代替案 | case とcase 代替リスト。 |
シンプルなパターンマッチ
次の例は、入力を複数の値と照合する方法を示しています。
def f(x: Int): String = x match {
case 1 => "One"
case 2 => "Two"
case _ => "Unknown!"
}
f(2) // "Two"
f(3) // "Unknown!"
注: _
は、 フォールスルーまたはデフォルトの場合ですが、必須ではありません。
def g(x: Int): String = x match {
case 1 => "One"
case 2 => "Two"
}
g(1) // "One"
g(3) // throws a MatchError
例外を投げるのを避けるために、デフォルトのケース( case _ => <do something>
)を処理するのがここでは最高の関数プログラミングの練習です。 caseクラスが一致していれば、コンパイラが警告を生成するのに役立つことに注意してください。密封された特性を拡張するユーザー定義のタイプの場合も同じです。マッチが合計である場合、デフォルトのケースは必要ないかもしれません
また、インラインで定義されていない値と照合することもできます。これらは、 安定した識別子でなければなりません。これは、大文字の名前を使用するか、バッククォートを囲むかのいずれかによって取得されます。
One
two
他の場所で定義されているか、関数のパラメータとして渡されている:
val One: Int = 1
val two: Int = 2
それらは次の方法で照合することができます:
def g(x: Int): String = x match {
case One => "One"
case `two` => "Two"
}
例えばJavaのような他のプログラミング言語とは異なり、転倒はありません。 caseブロックが入力と一致すると、それが実行され、一致が完了します。したがって、最も特殊なケースは最後のケースブロックでなければなりません。
def f(x: Int): String = x match {
case _ => "Default"
case 1 => "One"
}
f(5) // "Default"
f(1) // "Default"
安定した識別子によるパターンマッチング
標準的なパターンマッチングでは、使用される識別子は、囲みスコープ内の識別子をシャドウします。場合によっては、囲みスコープの変数を照合する必要があります。
次の関数例は、文字とタプルのリストを取り、タプルの新しいリストを返します。文字がタプルの1つの最初の要素として存在する場合、2番目の要素がインクリメントされます。リストにまだ存在しない場合は、新しいタプルが作成されます。
def tabulate(char: Char, tab: List[(Char, Int)]): List[(Char, Int)] = tab match {
case Nil => List((char, 1))
case (`char`, count) :: tail => (char, count + 1) :: tail
case head :: tail => head :: tabulate(char, tail)
}
上の例は、メソッドの入力char
がパターンマッチで '安定'に保たれているパターンマッチングを示しています。つまり、 tabulate('x', ...)
を呼び出すと、最初のcase文は次のように解釈されます。
case('x', count) => ...
Scalaは、ティックマークで区切られた変数を安定した識別子として解釈します。同じように大文字で始まる変数も解釈します。
Seqのパターンマッチング
コレクション内の要素の正確な数を確認するには
def f(ints: Seq[Int]): String = ints match {
case Seq() =>
"The Seq is empty !"
case Seq(first) =>
s"The seq has exactly one element : $first"
case Seq(first, second) =>
s"The seq has exactly two elements : $first, $second"
case s @ Seq(_, _, _) =>
s"s is a Seq of length three and looks like ${s}" // Note individual elements are not bound to their own names.
case s: Seq[Int] if s.length == 4 =>
s"s is a Seq of Ints of exactly length 4" // Again, individual elements are not bound to their own names.
case _ =>
"No match was found!"
}
最初の要素を抽出し、残りをコレクションとして保持するには:
def f(ints: Seq[Int]): String = ints match {
case Seq(first, second, tail @ _*) =>
s"The seq has at least two elements : $first, $second. The rest of the Seq is $tail"
case Seq(first, tail @ _*) =>
s"The seq has at least one element : $first. The rest of the Seq is $tail"
// alternative syntax
// here of course this one will never match since it checks
// for the same thing as the one above
case first +: tail =>
s"The seq has at least one element : $first. The rest of the Seq is $tail"
case _ =>
"The seq didn't match any of the above, so it must be empty"
}
一般に、配列を構築するために使用することができる任意の形態を用いて、既存の配列とのパターンマッチングを行うことができる。
Nil
と::
を使用している間、シーケンスにパターンマッチングすると機能しますが、 List
変換して予期しない結果が生じることに注意してください。これを避けるには、 Seq( ...)
と+:
に自分自身を制限します。
WrappedArray
、 Vector
などでは::
を使用することはできません。
scala> def f(ints:Seq[Int]) = ints match {
| case h :: t => h
| case _ => "No match"
| }
f: (ints: Seq[Int])Any
scala> f(Array(1,2))
res0: Any = No match
そして+:
scala> def g(ints:Seq[Int]) = ints match {
| case h+:t => h
| case _ => "No match"
| }
g: (ints: Seq[Int])Any
scala> g(Array(1,2).toSeq)
res4: Any = 1
ガード(式の場合)
case文をif式と組み合わせて、パターンマッチング時に余分なロジックを提供することができます。
def checkSign(x: Int): String = {
x match {
case a if a < 0 => s"$a is a negative number"
case b if b > 0 => s"$b is a positive number"
case c => s"$c neither positive nor negative"
}
}
あなたのガードが非徹底的なマッチを作らないようにすることが重要です(コンパイラはしばしばこれをキャッチしません):
def f(x: Option[Int]) = x match {
case Some(i) if i % 2 == 0 => doSomething(i)
case None => doSomethingIfNone
}
奇数にMatchError
がスローされます。すべてのケースについて説明するか、ワイルドカードの大文字と小文字を区別するケースを使用する必要があります。
def f(x: Option[Int]) = x match {
case Some(i) if i % 2 == 0 => doSomething(i)
case _ => doSomethingIfNoneOrOdd
}
ケースクラスによるパターンマッチング
すべてのcaseクラスは、パターンマッチングの際にcaseクラスのメンバを取得するために使用できるextractorを定義します。
case class Student(name: String, email: String)
def matchStudent1(student: Student): String = student match {
case Student(name, email) => s"$name has the following email: $email" // extract name and email
}
パターンマッチングのすべての通常のルールが適用されます。ガードと定数式を使用してマッチングを制御できます。
def matchStudent2(student: Student): String = student match {
case Student("Paul", _) => "Matched Paul" // Only match students named Paul, ignore email
case Student(name, _) if name == "Paul" => "Matched Paul" // Use a guard to match students named Paul, ignore email
case s if s.name == "Paul" => "Matched Paul" // Don't use extractor; use a guard to match students named Paul, ignore email
case Student("Joe", email) => s"Joe has email $email" // Match students named Joe, capture their email
case Student(name, email) if name == "Joe" => s"Joe has email $email" // use a guard to match students named Joe, capture their email
case Student(name, email) => s"$name has email $email." // Match all students, capture name and email
}
オプションでのマッチング
Optionタイプでマッチングしている場合:
def f(x: Option[Int]) = x match {
case Some(i) => doSomething(i)
case None => doSomethingIfNone
}
これはfold
、 map
/ getOrElse
を使うことと機能的に同等getOrElse
:
def g(x: Option[Int]) = x.fold(doSomethingIfNone)(doSomething)
def h(x: Option[Int]) = x.map(doSomething).getOrElse(doSomethingIfNone)
パターンマッチング密閉形質
型が密封された特性であるオブジェクトをパターンマッチングすると、Scalaはコンパイル時にすべてのケースが「徹底的に一致している」ことをチェックします:
sealed trait Shape
case class Square(height: Int, width: Int) extends Shape
case class Circle(radius: Int) extends Shape
case object Point extends Shape
def matchShape(shape: Shape): String = shape match {
case Square(height, width) => "It's a square"
case Circle(radius) => "It's a circle"
//no case for Point because it would cause a compiler warning.
}
Shape
新しいcase class
が後で追加されると、 Shape
すべてのmatch
文がコンパイラの警告をスローし始めます。これにより、完全なリファクタリングが容易になります。コンパイラは、更新が必要なすべてのコードを開発者に警告します。
Regexとのパターンマッチング
val emailRegex: Regex = "(.+)@(.+)\\.(.+)".r
"[email protected]" match {
case emailRegex(userName, domain, topDomain) => println(s"Hi $userName from $domain")
case _ => println(s"This is not a valid email.")
}
この例では、正規表現は提供された電子メールアドレスに一致するように試みます。そうであれば、 userName
とdomain
が抽出されて出力されます。 topDomain
も抽出されますが、この例では何も行われません。文字str
.r
呼び出しは、 new Regex(str)
と同じです。 r
関数は、 暗黙の変換によって使用できます。
パターンバインダー(@)
@
記号は、パターンマッチング中に変数を名前にバインドします。バインドされた変数は、一致したオブジェクト全体または一致したオブジェクトの一部のいずれかです。
sealed trait Shape
case class Rectangle(height: Int, width: Int) extends Shape
case class Circle(radius: Int) extends Shape
case object Point extends Shape
(Circle(5): Shape) match {
case Rectangle(h, w) => s"rectangle, $h x $w."
case Circle(r) if r > 9 => s"large circle"
case c @ Circle(_) => s"small circle: ${c.radius}" // Whole matched object is bound to c
case Point => "point"
}
> res0: String = small circle: 5
バインドされた識別子は、条件付きフィルタで使用できます。従って:
case Circle(r) if r > 9 => s"large circle"
次のように書くことができます:
case c @ Circle(_) if c.radius > 9 => s"large circle"
名前は、一致するパターンの一部にのみバインドできます。
Seq(Some(1), Some(2), None) match {
// Only the first element of the matched sequence is bound to the name 'c'
case Seq(c @ Some(1), _*) => head
case _ => None
}
> res0: Option[Int] = Some(1)
パターンマッチングタイプ
isInstanceOf[B]
ではなく、パターンマッチングを使用してインスタンスのタイプをチェックすることもできます。
val anyRef: AnyRef = ""
anyRef match {
case _: Number => "It is a number"
case _: String => "It is a string"
case _: CharSequence => "It is a char sequence"
}
//> res0: String = It is a string
ケースの順序は重要です。
anyRef match {
case _: Number => "It is a number"
case _: CharSequence => "It is a char sequence"
case _: String => "It is a string"
}
//> res1: String = It is a char sequence
このように、フォールスルー機能を持たない古典的な 'switch'ステートメントに似ています。しかし、問題のタイプからパターンマッチと '抽出'値を組み合わせることもできます。例えば:
case class Foo(s: String)
case class Bar(s: String)
case class Woo(s: String, i: Int)
def matcher(g: Any):String = {
g match {
case Bar(s) => s + " is classy!"
case Foo(_) => "Someone is wicked smart!"
case Woo(s, _) => s + " is adventerous!"
case _ => "What are we talking about?"
}
}
print(matcher(Foo("Diana"))) // prints 'Diana is classy!'
print(matcher(Bar("Hadas"))) // prints 'Someone is wicked smart!'
print(matcher(Woo("Beth", 27))) // prints 'Beth is adventerous!'
print(matcher(Option("Katie"))) // prints 'What are we talking about?'
Foo
とWoo
場合、アンダースコア( _
)を使用して 'unbound variableと一致させる'ことに注意してください。つまり、値(この場合はHadas
と27
)は名前にバインドされていないため、その場合のハンドラでは使用できません。これは、その値が何であるかを気にせずに「任意の」値に一致させるために便利な省略形です。
テーブルスイッチまたはルックアップスイッチとしてコンパイルされたパターンマッチング
@switch
アノテーションは、バイトコードレベルでmatch
ステートメントを単一のtableswitch
命令に置き換えることができることをコンパイラに指示します。これは、実行時に不必要な比較や変数の負荷を取り除くことができるマイナーな最適化です。
@switch
アノテーションは、リテラル定数とfinal val
識別子との一致に対してのみ機能します。パターンマッチをtableswitch
/ lookupswitch
としてコンパイルできない場合、コンパイラは警告を発します。
import annotation.switch
def suffix(i: Int) = (i: @switch) match {
case 1 => "st"
case 2 => "nd"
case 3 => "rd"
case _ => "th"
}
結果は通常のパターンマッチと同じです:
scala> suffix(2)
res1: String = "2nd"
scala> suffix(4)
res2: String = "4th"
Scalaドキュメント (2.8以降)から@switch
:
一致式に適用される注釈。存在する場合、コンパイラは、マッチがテーブルスイッチまたはルックアップスイッチにコンパイルされていることを確認し、代わりに一連の条件式にコンパイルするとエラーを発行します。
Java仕様から:
- テーブルスイッチ :「インデックスとジャンプによるジャンプテーブルへのアクセス」
- lookupswitch : "キーマッチとジャンプによるジャンプテーブルへのアクセス"
一度に複数のパターンを一致させる
|
同じ結果を得るために複数の入力に対して1つのcase文を一致させるために使うことができます:
def f(str: String): String = str match {
case "foo" | "bar" => "Matched!"
case _ => "No match."
}
f("foo") // res0: String = Matched!
f("bar") // res1: String = Matched!
f("fubar") // res2: String = No match.
この方法で値をマッチングするとうまくいくが、次のタイプのマッチングによって問題が発生することに注意してください。
sealed class FooBar
case class Foo(s: String) extends FooBar
case class Bar(s: String) extends FooBar
val d = Foo("Diana")
val h = Bar("Hadas")
// This matcher WILL NOT work.
def matcher(g: FooBar):String = {
g match {
case Foo(s) | Bar(s) => print(s) // Won't work: s cannot be resolved
case Foo(_) | Bar(_) => _ // Won't work: _ is an unbound placeholder
case _ => "Could not match"
}
}
後者の場合( _
)、バインドされていない変数の値を必要とせず、他の何かを実行したい場合は、上手くいく:
def matcher(g: FooBar):String = {
g match {
case Foo(_) | Bar(_) => "Is either Foo or Bar." // Works fine
case _ => "Could not match"
}
}
それ以外の場合は、あなたのケースを分割して残します:
def matcher(g: FooBar):String = {
g match {
case Foo(s) => s
case Bar(s) => s
case _ => "Could not match"
}
}
タプルのパターンマッチング
以下のタプルのList
を与えられます:
val pastries = List(("Chocolate Cupcake", 2.50),
("Vanilla Cupcake", 2.25),
("Plain Muffin", 3.25))
パターンマッチングを使用すると、各要素を別々に処理できます。
pastries foreach { pastry => pastry match { case ("Plain Muffin", price) => println(s"Buying muffin for $price") case p if p._1 contains "Cupcake" => println(s"Buying cupcake for ${p._2}") case _ => println("We don't sell that pastry") } }
最初のケースでは、特定の文字列と照合して対応する価格を取得する方法を示しています。 2番目のケースでは、 タプルの要素と一致する ifおよびtuple抽出の使用を示しています。