-
[스칼라] 익스트랙터(extractor)스칼라 2022. 3. 2. 18:00
Programming in scala 4th edition 26장
1. 익스트랙터
익스트랙터는 unapply 메서드를 갖고 있는 객체이다. 패턴 매치 시 익스트랙터 객체를 참조하는 패턴을 만나면 unapply메서드가 호출된다.
// selectorString match { case EMail(user, domain) => ... } // 위는 아래의 호출을 일으킴 // EMail(selectorString) object Email { def apply(user: String, domain: String) = user + "@" + domain def unapply(str: String): Option[(String, String)] = { val parts = str split "@" if(parts.length == 2) Some(parts(0), parts(1)) else None } } val x: Any = "bees1114@naver.com" x match { case Email(user, domain) => println(user, domain) case _ => println("default") }
패턴 매치 식은 x의 타입을 확인해서 Email.unapply의 인자 타입과 비교한다. unapply에 x를 대입할 수 있으면 해당 라인을 실행한다.
2. 변수가 없거나 1개만 있는 패턴
변수가 1개만 있는 패턴: 변수 2개를 반환할 때와 마찬가지로 Some으로 감싸 리턴하면 됨
변수가 하나도 없는 패턴: 익스트랙터 패턴이 아무 변수도 바인딩하지 않는 경우도 있음. 이런경우 Boolean을 리턴함
object Twice { def apply(s: String): String = s + s def unapply(s: String): Option[String] = { val length = s.length / 2 val half = s.substring(0, length) if (half == s.substring(length)) Some(half) else None } } object UpperCase { def unapply(s: String): Boolean = s.toUpperCase == s } def userTwiceUpper(s: String) = s match { case Email(Twice(x @ UpperCase()), domain) => "match: " + x + " in domain " + domain case _ => "no match" } userTwiceUpper("ABCDABCD@gmail.com") // match: ABCD in domain gmail.com
위의 예제에서 userTwiceUpper에서 "ABCDABCD@gmail.com"이 들어가면 x @ UpperCase()에 의해서 UpperCase를 만족하는 s를 x에 맵핑시킨다. 이후 Twice.unapply, Email.unapply가 적용되어 x와 domain에 유저와 도메인이 할당된다.
3. 가변 인자 익스트랙터
unapply는 Option[T]의 타입을 리턴하게 되어있다. 가변 길이를 가진 인자를 반환하는 unapply를 정의하는 방법도 있다. unapplySeq를 정의해서 Option[Seq[T]] 타입을 리턴하면 된다.
object Domain { def apply(parts: String*): String = parts.reverse.mkString(".") def unapplySeq(whole: String): Option[Seq[String]] = Some(whole.split("\\.").reverse) } "net" match { case Domain("org", "acm") => println("acm.org") case Domain("com", "sun", "java") => println("java.sun.com") case Domain("net", _*) => println("a .net domain") } // "a .net domain" "acm.org" match { case Domain("org", "acm") => println("acm.org") case Domain("com", "sun", "java") => println("java.sun.com") case Domain("net", _*) => println("a .net domain") } // "acm.org"
위의 unapplySeq는 패턴 매치의 셀렉터를 받아 Option[Seq[String]]을 반환한다. 그 결과물을 가지고 패턴을 정의한 모습이다.
unapplySeq는 고정 요소와 가변 길이를 함께 반환할 수도 있다.
object ExpandedEMail { def unapplySeq(email: String) : Option[(String, Seq[String])] = { val parts = email split "@" if (parts.length == 2) Some(parts(0), parts(1).split("\\.").reverse) else None } } "abcd@company.gmail.com" match { case ExpandedEMail(user, others @ _*) => println(user, others) } // 출력 (abcd,ArraySeq(com, gmail, company))
4. 익스트랙터와 시퀀스 패턴
val List(x, y, r @ _*) = List(1, 2, 3, 4, 5) print(x, y, r)
패턴 매치를 이용해서 위처럼 Seq 안의 원소들을 바로 변수에 할당 할 수 있었다. 이건 실제로 List 객체 안에 unapplySeq가 정의되어있기 때문이다.
5. 익스트랙터와 케이스 클래스
익스트랙터와 케이스클래스가 하는 기능은 유사하다. 아래는 익스트랙터와 케이스 클래스의 장단점이다.
- 익스트랙터는 표현독립성이 유지된다. 객체 내부 데이터 구조와 패턴 사이의 관계가 없어 객체 내부 데이터 추가/제거에 더 유리하다. 케이스클래스는 객체 내부 데이터와 패턴이 연관되어 변경할 때 고려해야 할 점이 더 많다.
- 케이스클래스는 정의가 쉽고 코드가 적다. 컴파일러가 컴파일 할 때 익스트랙터의 패턴 매치보다 좀 더 최적화 할 수 있다.
- Sealed case class 를 사용할 경우 컴파일러가 모든 패턴을 다 다루는지 아닌지에 대해서 경고해줄 수 있다.
6. 정규표현식
스칼라 정규표현식 클래스는 scala.util.matching 패키지에 들어있다.
import scala.util.matching.Regex val regexp = new Regex("\\d+") val numbers = regexp.findAllMatchIn("a123def4").toList numbers // List(123, 4)
정규표현식은 일반적인 문자열로 표현하면 \\을 붙여야 \으로 활용할 수 있다. raw string을 활용하여 \만으로 정규표현식을 나타낼 수도 있다.
val regexp2 = new Regex("""(-)(\d)+""") regexp2.matches("-10") // true regexp2.matches("10") // false
new Regex 대신 StringOps 클래스 안에 정의된 r을 이용하여 정규표현식을 정의할 수도 있다.
val regexp3 = """[A-Z]\w*""".r regexp3.matches("abcd") // false regexp3.matches("ABCD") // true
정규표현식과 익스트랙터
스칼라 정규표현식은 그룹별로 매치하는 부분 문자열을 뽑아낼 수 있는 익스트랙터를 제공한다.
val regexp3 = """(-)?([A-Z]+)(\d*)""".r var regexp3(sign, word, number) = "-ABCD123" print(sign, word, number) // -, ABCD, 123 출력됨 var regexp3(sign, word, number) = "ABCD" print(sign, word, number) // null, ABCD, "" 출력됨
'스칼라' 카테고리의 다른 글
[스칼라] 애노테이션(Annotation) (0) 2022.03.02 [스칼라] 컬렉션 자세히 보기 (0) 2022.02.28 [스칼라] 리스트 구현 (0) 2022.02.27 [스칼라] 암시적 변환과 암시적 파라미터 (implicit) (0) 2022.02.26 [스칼라] 추상 멤버 (0) 2022.02.25