Scala Language
패턴 매칭
수색…
통사론
- 선택기 일치 partialFunction
- 선택기 일치 (대소 문자 바꾸기 목록) // 이것은 위의 가장 일반적인 형식입니다
매개 변수
매개 변수 | 세부 |
---|---|
선택자 | 값이 패턴 일치 된 표현식입니다. |
대안들 | 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>
)를 처리하는 것이 가장 좋은 함수 프로그래밍 연습입니다. 케이스 클래스 를 매치하면 케이스가없는 경우 컴파일러가 경고를 생성하는 데 도움이 될 수 있습니다. 봉인 된 특성을 확장하는 사용자 정의 유형에서도 마찬가지입니다. 일치가 총합이면 기본 사례가 필요하지 않을 수 있습니다.
또한 인라인으로 정의되지 않은 값과 일치시킬 수도 있습니다. 이것들은 대문자로 된 이름을 사용하거나 backticks를 묶어 얻어지는 안정된 식별자 여야합니다.
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"
안정적인 식별자로 패턴 매칭
표준 패턴 일치에서 사용되는 식별자는 둘러싼 범위의 식별자를 무시합니다. 때로는 엔 클로징 스코프의 변수를 매치 할 필요가있다.
다음 예제 함수는 문자와 튜플 목록을 가져 와서 새로운 튜플 목록을 반환합니다. 문자가 튜플 중 하나의 첫 번째 요소로 존재하면 두 번째 요소가 증가합니다. 목록에 아직없는 경우 새 튜플이 만들어집니다.
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) => ...
스칼라는 눈금이 표시된 모든 변수를 안정적인 식별자로 해석합니다. 동일한 방법으로 대문자로 시작하는 변수도 해석합니다.
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( ...)
및 +:
에 제약 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 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
를 사용하는 것과 기능면에서 동일합니다 :
def g(x: Option[Int]) = x.fold(doSomethingIfNone)(doSomething)
def h(x: Option[Int]) = x.map(doSomething).getOrElse(doSomethingIfNone)
패턴 매칭 밀폐 된 특성
타입이 봉인 된 형질 인 객체를 패턴 매칭 할 때, 스칼라는 컴파일 타임에 모든 경우가 '철저하게 일치'하는지 검사 할 것입니다 :
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
문에서 컴파일러 경고가 throw됩니다. 이렇게하면 철저한 리팩토링이 쉬워집니다. 컴파일러는 개발자에게 업데이트해야하는 모든 코드를 알려줍니다.
정규식과의 패턴 일치
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
도 추출되지만이 예제에서는 아무 것도 수행되지 않습니다. String 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
이 방식으로 전환 기능이없는 고전적인 '스위치'문과 유사합니다. 그러나 문제의 유형에서 패턴 일치 및 '추출'값을 지정할 수도 있습니다. 예를 들면 :
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
경우 밑줄 ( _
)을 사용하여 언 바운드 변수와 일치시킵니다. 즉 값 (이 경우 Hadas
및 27
)은 이름에 바인딩되어 있지 않으므로 해당 경우 처리기에서 사용할 수 없습니다. 이는 값이 무엇인지 걱정하지 않고 '모든'값을 일치시키기 위해 유용한 속기입니다.
tableswitch 또는 lookupswitch로 컴파일 된 패턴 매칭
@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"
스칼라 문서 (2.8+) - @switch
:
일치 식에 적용 할 주석입니다. 이 플래그가 있으면 컴파일러는 일치 항목이 테이블 스위치 또는 조회 스위치로 컴파일되었는지 확인하고 대신 일련의 조건식으로 컴파일하면 오류가 발생합니다.
Java 사양 :
- tableswitch : "색인으로 점프 테이블에 액세스하고 점프"
- lookupswitch : "키 매치 및 점프에 의한 점프 테이블 액세스"
한 번에 여러 패턴 매치
|
동일한 결과를 산출하기 위해 여러 입력에 대해 단일 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") } }
첫 번째 경우는 특정 문자열과 일치시켜 해당 가격을 얻는 방법을 보여줍니다. 두 번째 경우는 if와 tuple 추출 을 사용하여 튜플의 요소와 일치시킵니다.