: 패턴 매치의 생성자 패턴은 해당 클래스가 케이스 클래스이기 때문에 가능하다. 케이스 클래스는 만들고 싶지 않지만 생성자 패턴을 사용하고 싶고, 자신만의 패턴을 만들고 싶다면 익스트랙터를 사용한다.
26.1 예제 : 전자우편 주소 추출 - 전자 우편 주소를 표현하는 문자열 분석
def isEMail(s: String): Boolean // 전자우편 주소인지 아닌지
if (isEMail(s)) println(getUser(s) +" AT "+ getDomain(s))
=> 전자 우편 문자열을 Email(user, domain) 으로 패턴 매치할 수 있다면? s match {
// 같은 사용자의 전자우편 주소 2개가 연속으로 있는 경우 ss match { |
: 세 가지 도우미 함수로 작성한 것보다 패턴 매치의 가독성이 좋다. 하지만 전자우편이 문자열이라 케이스 클래스가 아니다. => 문자열은 EMail(user, domain)으로 패턴 매치가 불가능하다. => 익스트랙터를 사용하면 기존 타입에 새로운 패턴을 정의할 수 있다.
26.2 익스트랙터
: 익스트랙터는 unapply라는 메소드가 있는 객체
: unapply 메소드의 목적은 값을 매치시켜 각 부분을 나누는 것
: 반대로 값을 만들어내는 apply라는 메소드도 존재(필수는 아님)
object EMail { // apply 메소드를 명시적으로 만들고 싶다면 Function2[String, String, String] 함수 타입 상속 // object EMail extends ((String, String) => String) { ... }
// 전자우편 주소 문자열을 받아서 (사용자 문자열, 도메인 문자열)을 Option 타입으로 반환 : apply와 역으로 진행 // str이 전자 우편 주소라면 Some(user, domain), 아니라면 None 반환
=> 패턴 매치 시 익스트랙터 객체를 참조하는 패턴을 만나면 항상 그 익스트렉터의 unapply 메소드를 설렉터 식에 대해 호출
selectorString match { case EMail(user, domain) => ... } // == EMail.unapply(selectorString) // unapply에서 None이 반환되면 패턴 매치가 이뤄지지 않는다. // Some(u,d)이 반환되면 패턴이 매치되어 unapply가 반환한 값이 각 변수에 바인딩 => user가 u에 domain이 d에 바인딩 |
* 익스트랙터를 이용한 패턴 매치를 하려면 셀렉터 식의 타입은 unapply 인자 타입보다 일반적이어야 한다.
val x: Any = ... : 패턴 매처가 위 코드를 본다면 x가 EMail의 unapply 메소드 인자 타입인 String과 부합하는 지 살펴본다. 부합하는 경우, 매처는 값을 String으로 캐스팅해서 처리하고 부합하지 않으면 매치가 바로 실패한다. |
- apply 메소드 : 인젝션, 인자를 몇 가지 받아서 어떤 집합의 원소를 만들어 낸다.(익스트랙터 객체에 의무 X)
- unapply 메소드 : extraction, 어떤 집합에 속한 원소에서 여러 부분의 값을 뽑아낸다.
- 인젝션과 익스트랙션 메소드는 서로 쌍대성 => 쌍대성은 좋은 설계 원칙으로 익스트랙터를 설계할 때 지키는 편이 좋다.
EMail.unapply(EMail.apply(user, domain)) => (user, domain)에 apply와 unapply를 적용하면 Some(user, domain) 반환 EMail.unapply(obj) match { => user@domain에 unapply와 apply를 적용하면 Some(user@domain) 반환 case Some(u, d) => EMail.apply(u, d) } |
26.3 변수가 없거나 1개만 있는 패턴
: unapply로 N개의 변수를 바인딩하고 싶다면 N개의 원소로 된 튜플을 Some에 감싸서 반환하면 된다.
: 패턴이 변수를 하나만 바인딩해야 할 경우 스칼라에는 1튜플이 없기 때문에 unapply는 원소 자체를 Some으로 감싼다.
" 같은 부분 문자열을 두 번 반복해 만든 문자열 매치 " object Twice { |
: 아무 변수도 바인딩하지 않을 경우 unapply 메소드는 Boolean 값을 반환한다. 매치 성공인 경우 true, 실패인 경우 false
" 문자열의 모든 문자가 대문자인지 확인 " object UpperCase { |
" 전자우편 주소의 사용자 부분이 두 번 반복되는 대문자 문자열일 경우 매치 " def userTwiceUpper(s: String) = s match { |
26.4 가변 인자 익스트랙터
: unapplySeq 메소드를 사용하면 가변 길이 매치를 할 수 있다. unapplySeq의 결과 타입은 꼭 Option[Seq[T]]와 부합해야 한다.
* unapply는 매치 성공 시 항상 고정된 숫자의 하위 원소를 반환했기 때문에 가변 인자에 적용 X
* Seq는 시퀀스를 나타내는 List, Array, WrappedString 등 여러 클래스의 공통 슈퍼 클래스
object Domain {
" 이름이 tom이고 도메인이 .com인 전자우편 주소 검색 " def isTomInDotCom(s: String): Boolean = s match {
scala> isTomInDotCom("tom@sun.com") |
: 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 } } scala> val s = "tom@support.epfl.ch" s: java.lang.String = tom@support.epfl.ch scala> val ExpandedEMail(name, topdom, subdoms @ _*) = s name: String = tom topdom: String = ch subdoms: Seq[String] = WrappedArray(epfl, support) |
26.5 익스트랙터와 시퀀스 패턴
: 리스트나 배열의 원소를 시퀀스 패턴으로 접근할 수 있다.
List() List(x, y, _*) Array(x, 0, 0, _) |
: 시퀀스 패턴은 모두 표준 스칼라 라이브러리의 익스트랙터를 사용해 구현한 것
Ex. List(...) 패턴이 가능한 이유는 scala.List 동반 객체에 unapplySeq 정의가 있기 때문
26.6 익스트랙터와 케이스 클래스
: 케이스 클래스는 아주 유용하지만 생성자 패턴에 있는 클래스 이름이 셀렉터 객체의 구체적인 표현 타입과 대응한다.(=데이터의 구체적인 표현이 드러난다.) => 케이스 클래스에 대해 패턴 매치를 하는 클라이언트 코드가 이미 있다면 케이스 클래스 이름을 바꾸거나 클래스 계층 구조를 변경하면 클라이언트 코드에 영향을 끼친다.
: 익스트랙터는 패턴과 그 패턴이 선택하는 객체의 내부 데이터 표현 사이에 아무런 관계가 없도록 만든다.(표현 독립성) => 클래스가 바뀌더라도 클라이언트 코드에는 영향을 미치지 않는다.
* 표현 독립성은 익스트랙터의 장점이다.
* 케이스 클래스는 설정하고 정의하기 훨씬 쉽고 코드도 적게 필요하다.
* 케이스 클래스는 익스트랙터보다 더 효과적인 패턴매치가 가능하다.(케이스 클래스의 메커니즘은 변하지 않는 반면 익스트랙터의 unapply 안에서는 아무 일이나 할 수 있기 때문에 스칼라 컴파일러가 케이스 클래스의 패턴 매치를 익스트랙터의 패턴 매치보다 더 잘 최적화한다.)
* 케이스 클래스를 봉인된 케이스 클래스로 만들 경우 패턴 매치가 모든 가능한 패턴을 다 다루는지 스칼라 컴파일러가 검사해서 그렇지 않은 경우 경고를 해준다.
=> 여러 클라이언트에게 노출해야 한다면 표현 독립성을 위해 익스트랙터를 사용하고 아니면 케이스 클래스를 사용한다
* 익스트랙터나 케이스 클래스의 패턴 매치는 똑같이 보이기 때문에 케이스 클래스로 시작한 다음 필요에 따라 익스트랙터로 바꾸면 된다.
26.7 정규 표현식
< 정규 표현식 만들기 >
- ab? : 'a' or 'ab'
- \d+ : \d는 숫자(0~9), 하나 이상의 숫자로 구성된 문자열
- [a-dA-D]\w* : a부터 d까지 대문자/소문자로 시작하는 단어, \w는 단어를 이루는 문자(알파벳, 숫자, 밑줄 문자), *는 0개 이상의 반복
- (-)?(\d+)(\.\d*)? : 맨 앞에 음수가 있을 수 있고, 그 뒤에 1개 이상의 숫자가 필수, 그 뒤에 선택적으로 소수점과 0개 이상의 숫자가 오는 문자열 의미한다. 이 패턴에는 음수 부호, 소수점 앞의 숫자들, 소수 부분 세가지 그룹으로 나뉜다. 각 그룹은 괄호로 둘러싼다.
scala> import scala.util.matching.Regex scala> val Decimal = new Regex("(-)?(\\d+)(\\.\\d*)?") Decimal: scala.util.matching.Regex = (-)?(\d+)(\.\d*)? scala> val Decimal = new Regex("""(-)?(\d+)(\.\d*)?""") Decimal: scala.util.matching.Regex = (-)?(\d+)(\.\d*)? scala> val Decimal = """(-)?(\d+)(\.\d*)?""".r Decimal: scala.util.matching.Regex = (-)?(\d+)(\.\d*)? package scala.runtime import scala.util.matching.Regex class StringOps(self: String) ... { ... def r = new Regex(self) } |
< 정규 표현식 검색 >
regex findFirstIn str : str 문자열 안에 regex 정규 표현식과 매치되는 가장 첫 번째 부분 문자열 검색
regex findAllIn str : str 문자열 안에 regex 정규 표현식과 매치되는 모든 문자열 반환
regex findPrefix str : str 문자열의 맨 앞 부분부터 검사해 정규 표현식 regex와 매치시킬 수 있는 접두사를 반환
scala> val Decimal = """(-)?(\d+)(\.\d*)?""".r Decimal: scala.util.matching.Regex = (-)?(\d+)(\.\d*)? scala> val input = "for -1.0 to 99 by 3" input: java.lang.String = for -1.0 to 99 by 3 scala> for (s <- Decimal findAllIn input) | println(s) -1.0 99 3 scala> Decimal findFirstIn input res7: Option[String] = Some(-1.0) scala> Decimal findPrefixOf input res8: Option[String] = None |
< 정규 표현식 뽑아내기 >
: 스칼라의 모든 정규 표현식은 익스트랙터를 정의한다. 익스트랙터를 사용해 정규 표현식 안의 그룹과 매치하는 부분 문자열을 구별할 수 있다.
scala> val Decimal = """(-)?(\d+)(\.\d*)?""".r Decimal: scala.util.matching.Regex = (-)?(\d+)(\.\d*)? scala> val Decimal(sign, integerpart, decimalpart) = "-1.23" sign: String = - integerpart: String = 1 decimalpart: String = .23 scala> val Decimal(sign, integerpart, decimalpart) = "1.0" sign: String = null // 어떤 그룹이 빠진 경우 해당 값은 null이 된다. integerpart: String = 1 decimalpart: String = .0 |
'스칼라' 카테고리의 다른 글
30장 객체의 동일성 (0) | 2019.06.24 |
---|---|
스칼라 25장 스칼라 컬렉션의 아키텍처(Programming in Scala, 3rd) (0) | 2019.06.24 |
24장 컬렉션 자세히 들여다보기(1) - Traversable, Iterable, Seq, 집합, 맵(Programming in Scala, 3rd) (0) | 2019.06.24 |
스칼라 23장 for 표현식 다시 보기(Programming in Scala, 3rd) (0) | 2019.06.24 |
스칼라 22장 리스트 구현(Programming in Scala, 3rd) (0) | 2019.06.23 |