Outsider's Dev Story

Stay Hungry. Stay Foolish. Don't Be Satisfied.
RetroTech 팟캐스트 44BITS 팟캐스트

Scala의 패턴매칭(Pattern Matching)과 정규표현식


def example(a: Int) {
    a match {
        case 1 => print("One")
        case 2 => print("Two")
        case _ => print("Other")
    }
}

패턴매칭은 match를 이용하며 match는 Any타입에서 동작합니다. 파라미터로 받은 a를 각 case별로 패턴매칭을 하고 매칭되면 표현식을 실행합니다. match는 Any타입에서 동작하지만 match왼쪽에 있는 타겟객체의 타입으로 매칭타입이 제한됩니다. case문에 매치되는 것이 없으면 MatchError 예외가 발생하며  5번줄의 case _ => ... 문을 통해서 와일드카드 매칭을 할 수 잇습니다.

match에는 튜플이나 List도 사용할 수 있으며 리스트의 경우에는 case List("aa", bb") 처럼 사용하지만 case List("aa", _* ) 처럼 _* 심볼을 사용해서 리스트의 나머지 부분에 대한 와일드 카드로 사용할 수 있습니다. 여기서 case List("aa", t @ _* ) => print( t ) 처럼 _* 심볼앞에 변수명을 @로 연결하면 실행문에서 참조할 수 있습니다.


def example2(a: Any) {
    a match {
        case target:Int if (target > 10) => print("over ten")
        case 2 => print("Two")
        case target : String => println(a + " is String" )
        case _ => printf("Others")
    }
}

3번줄 처럼 매칭에 if문을 이용하여 평가를 할 수 있으며 이를 Guard라고 합니다. 매칭되는 case문은 선언된 순서대로 Top-Down으로 매칭을 시도합니다.



case문에서 패턴 변수들과 상수들

def example(source: String) {
    source match {
        case aa => println("")
        case AA => println("")
    }
}

case문에서 스칼라에 관례에 따라 소문자로 시작하면 패턴변수로, 대문자로 시작하면 상수로 취급하기 때문에 3번줄에서는  source가 aa에 할당되고 4번줄은 상수로 취급하고 비교합니다. aa를 할당할 용도가 아니라면 this.aa와 같은 ObjectName.filedName같은 형식의 범위를 명시적으로 적어주어서 해결할 수 있습니다.




case 클래스를 사용한 매칭
case클래스는 case문에서 패턴매칭에 사용하기 위한 패턴 매처(pattern matcher) 클래스입니다.


abstract case class Parent()
case class Ex1(a: Int) extends Parent
case class Ex2(a: Int) extends Parent
case class Ex3(a: Int) extends Parent

class Test {
    def method(target: Parent) {
        target match {
            case Ex1(b) if (b > 10) => println("Case 1")
            case Ex1(b) if (b == 5) => println("Case 2")
            case Ex2(3) => println("Case 3")
            case Ex2(b) => println("Case 4")
        }
    }
}

val t = new Test
t.method(Ex1(5)) // Case 2
t.method(Ex1(100)) // Case 1
t.method(Ex2(20)) // Case 4
t.method(Ex2(3)) // Case 3

case 키워드를 사용하여 클래스를 case클래스로 선언하여 패턴매칭에 사용할 수 있습니다. case클래스 매칭은 파라미터가 없다고 하더라도 case문에서 괄호를 반드시 사용하여야 하며 괄호가 없으면 컴패니언 오브젝트가 실행됩니다. case클래스를 사용할 경우 모든 case클래스가 매칭을 시도하는지 컴파일러가 알수 없기 때문에 sealed abstract case class Parent() 로 선언하면 현재 선언된 case클래스만 존재한다고 컴파일러에 알려주어 위 코드처럼 Ex3를 매칭하지 않으면 warning: match is not exhaustive! 오류가 발생합니다.




Extractor를 사용한 매칭
Extractor를 사용하면 input값에서 매칭할 부분을 추출하여  임의의 패턴을 매칭할 수 있습니다. extractor는 매칭하려는 값을 받아들이는 unapply()라는 메서드를 가지고 있으며 매치가 시도될때 자동적으로 unapply()메서드를 호출하면서 input을 파라미터로 전달합니다.


object Test {
    def unapply(a: Int): Boolean = {
        if (a < 10) { true }
        else { false }
    }
}

object Test2 {
    def unapply(a: Int): Option[(Int, String)] = {
        if (a > 30) { Some(a/10, "from Test2") }
        else { None }
    }
}

class Example {
    def method(target: Int) {
        target match {
            case Test() => println("matched to Test")
            case Test2(n @ Test(), m) => println("result: " + n + " " + m)
            case 120 => println("match to 120")
        }
    }
}

val t = new Example
t.method(5) // match to Test
t.method(40) // 4 from Test2
t.method(120) // match to 120

19번 라인의 n, m은 Test2로 전달되는 파라미터가 아닌(파라미터는 target) extractor인 Test2로부터 리턴받을 아큐먼트들입니다. 돌려받는 아규먼트에 다른 extractor를 @로 연결하면 해당값에 대한 패턴매칭을 추가로 적용할 수 있습니다.


object Domain {
    def unapplySeq(whole: String): Option[Seq[String]] = {
        Some(whole.split("\\.").reverse)
    }
}

"www.NAVER.com" match {
    case Domain(_, first@UpperCase(), "www") =>printf(" %s !!", first)
    case _ => "?"
}

// output
// NAVER !!

unapply메서드는 시그니처때문에 한 클래스 내에서 여러개를 구현할수 없습니다. unapplySeq()는 가변적인 인자객수에 대응할 수 있으며 case문에서 _* 가 사용가능합니다.




정규표현식
Scala는 scala.util.matching패키지에 있는 클래스들로 정규표현식을 지원하고 있으며 정규표현식을 생성했을 때 이 팩키지의 Regex클래스의 인스턴스로 동작하게 되며 String에 r()메서드를 호출해서 Regex의 인스턴스를 얻을수 있습니다.


val regex = "[a-zA-Z0-9]{5,8}".r

println( (regex findFirstIn "my nick is Outsider").mkString("") )
println( (regex findAllIn "JSON is a lightweight data-interchange format").mkString(", ") )
println( regex replaceFirstIn("my nick is nickname", "Outsider") )
// Outsider
// lightwei, intercha, format
// my nick is Outsider

또한 정규표현식은 extractor이기도 하기 때문에 match를 하는 case문에서 그대로 사용할 수 있습니다. 매칭된 그룹으로 이루어진 튜플을 리턴받습니다.
2010/08/05 04:08 2010/08/05 04:08