-
Notifications
You must be signed in to change notification settings - Fork 36
15장 case classes and pattern matching
zeide edited this page Nov 27, 2011
·
12 revisions
패턴매칭할 클래스에 case키워드를 사용한다.
간단한 예제로 알아보자.
scala> abstract class Expr
defined class Expr
scala> case class Var(name: String) extends Expr
defined class Var
scala> case class Number(num: Double) extends Expr
defined class Number
scala> case class UnOp(operator: String, arg: Expr) extends Expr
defined class UnOp
scala> case class BinOp(operator: String,
| left: Expr, right: Expr) extends Expr
defined class BinOp
case classes modifier가 주는 문법적 편리함
- 클래스명으로 팩토리메소드 추가된다.
- case 클래스의 파라미터 리스트에 있는 인자들은 모두 val
- toString, hashCode, equals 메소드의 "natural"구현 사용할 수 있다.
- copy 메소드를 사용할 수 있다.
- 패턴매칭을 지원한다.
- pattern matching
- selector match { alternatives }
- switch와 다른 점
- match도 expression이다. 즉, 값을 반환한다.
- 명시적으로 break; 를 해야 하는 switch와 달리 match를 찾은 이후엔 계속 matching을 시도하지 않는다.
- 어떤 패턴에도 매칭되지 않으면 MatchError 예외를 던진다. 예외를 피하려면
case _ =>
를 명시하는 방법을 써야 한다.
@nephilim님이 정리하신 지난 스터디 패턴매칭 자료
(*빛바구니 아이디가 필요합니다.)
- 패턴 유형
- wildcard pattern:
case _ =>
. 아무거나 다 대응됨. java switch의 default와 비슷. - constant pattern: 특정 값. 리터럴/ val / 싱글턴 객체 사용 가능함.
- wildcard pattern:
def describe(x: Any) = x match {
case 5 => "five"
case true => "truth"
case "hello" => "hi!"
case Nil => "the empty list"
case _ => "something else"
}
- variable pattern: 아무 객체에나 매칭. _와 달리 매칭된 variable을 활용할 수 있음
expr match {
case 0 => "zero"
case somethingElse => "not zero: "+ somethingElse
}
* variable pattern과 constant를 구분하기 위해 소문자로 시작하는 이름은 pattern variable, 나머지는 상수로 취급.
* 소문자 상수를 어쩔 수 없이 써야한다면? (그러나 이런 짓 하지 말자)
* prefix with qualifier ex) Math.pi
* back tick 사용 ex) \`pi\`
import math.{E,Pi}
val pi = math.Pi
E match {
case Pi => "strange math? Pi = "+ Pi //상수 매칭
case _ => "OK"
}
res0: java.lang.String = OK
E match {
case pi => "strange math? Pi = "+ pi //변수 매칭
//case _ => "OK" //syntax error: unreachable code
}
res1: java.lang.String = strange math? Pi = 2.718281828459045
- constructor pattern: 생성자명(patterns)
- pattern 안에 pattern을 쓸 수 있음: scala는 deep match를 지원함.
expr match {
case BinOp("+", e, Number(0)) => println("a deep match")
case _ =>
}
- sequence pattern: List나 Array 류의 sequence type에 매칭.
expr match {
case List(0, _, _) => println("found it")
case _ =>
}
expr match {
case List(0, _*) => println("found it")
case _ =>
}
- tuple pattern: tuple에 매칭.
expr match {
case (a, b, c) => println("matched "+ a + b + c)
case _ =>
}
- typed pattern: 타입에 매칭
def generalSize(x: Any) = x match {
case s: String => s.length //matches every non-null(!) instance of String. caution: as type of x is Any and type of s is String, you should use s to access member of String.
case m: Map[_, _] => m.size
case _ => 1
}
cf) poor java style. yuck!
if (x.isInstanceOf[String]) {
val s = x.asInstanceOf[String]
s.length
} else ...
* 자바의 generic과 마찬가지로 type erasing이 존재함. 따라서 collection 내부의 type matching 등은 불가능함. 단, array는 예외임.
scala> def isIntIntMap(x: Any) = x match { //does not work as expected
case m: Map[Int, Int] => true
case _ => false
}
warning: there were unchecked warnings; rerun with unchecked for details
isIntIntMap: (x: Any)Boolean
- 변수 binding: 골뱅이로 pattern 일부를 변수에 binding 가능함. (아... 슬슬 머리에 스팀이...)
expr match {
case UnOp("abs", e @ UnOp("abs", _)) => e
case _ =>
}
###kingori pattern matching에 guard(즉 matching 조건 제약)을 붙일 수 있다.
def simplifyAdd(e: Expr) = e match {
case BinOp("+", x, y) if x == y => BinOp("*", x, Number(2))
case _ => e
}
// match only positive integers
case n: Int if 0 < n => ...
// match only strings starting with the letter ‘a’
case s: String if s(0) == 'a' => ...
###kingori
matching될 case의 순서에 주의할 것. 순서가 맞지 않을 경우 unreachble code 에러가 발생. 끝.
scala> def simplifyBad(expr: Expr): Expr = expr match {
case UnOp(op, e) => UnOp(op, simplifyBad(e))
case UnOp("", UnOp("",e)) => e
}
<console>:18: error: unreachable code
case UnOp("", UnOp("", e)) => e
ˆ
###kingori
- sealed classes: default로 할 만한 동작이 없거나, 모든 case를 제대로 cover했는 지 확인하고자 할 때 사용.
- case class의 상위 class에 sealed 지시자를 명시함. 질문 : 예시로 나온 형태 이외의 sealed 활용 형태엔 무엇이 있을까?
- 같은 파일 이외의 위치에선 subclass를 허용하지 않음.
sealed abstract class Expr
case class Var(name: String) extends Expr
case class Number(num: Double) extends Expr
case class UnOp(operator: String, arg: Expr) extends Expr
case class BinOp(operator: String, left: Expr, right: Expr) extends Expr
def describe(e: Expr): String = e match {
case Number(_) => "a number"
case Var(_) => "a variable"
}
==>
warning: match is not exhaustive!
missing combination UnOp
missing combination BinOp
###kingori
- scala는 어떤 값의 여부를 위해 Option과 Some, None 타입을 제공함. Some, None은 Option의 하위 클래스임.
scala> val capitals = Map("France" > "Paris", "Japan" > "Tokyo")
scala> capitals get "France"
res23: Option[java.lang.String] = Some(Paris)
scala> capitals get "North Pole"
res24: Option[java.lang.String] = None
scala> def show(x: Option[String]) = x match {
case Some(s) => s
case None => "?"
}
scala> show(capitals get "Japan")
res25: String = Tokyo
scala> show(capitals get "France")
res26: String = Paris
scala> show(capitals get "North Pole")
res27: String = ?
###kingori
- 변수 선언에 사용 : 이게 왜 이렇게 되는 거지?
scala> val myTuple = (123, "abc")
myTuple: (Int, java.lang.String) = (123,abc)
scala> val (number, string) = myTuple
number: Int = 123
string: java.lang.String = abc
- partial function에 사용
- case를 이용하면 마치 여러 개의 entry point를 가지며, 각 entry point마다 parameter 개수가 다른 function을 사용하는 기분이 듦 (문법적으로는 그냥
_ match
가 생략된 형태로 보이는데 맞는지?)
- case를 이용하면 마치 여러 개의 entry point를 가지며, 각 entry point마다 parameter 개수가 다른 function을 사용하는 기분이 듦 (문법적으로는 그냥
val withDefault: Option[Int] => Int = {
case Some(x) => x
case None => 0
}
//same with this?
val withDefault: Option[Int] => Int = _ match {
case Some(x) => x
case None => 0
}
-
List[Int] => Int
의 형태는 모든 경우에 해당함. 일부 경우에만 실행되어야 하는 function을 위해선PartialFunction
타입을 이용해야 함.- _isDefinedAt : function의 적용 가능 여부
- apply : function 실행
val second: List[Int] => Int = {
case x :: y :: _ => y
}
scala> second(List())
scala.MatchError: List()
at $anonfun$1.apply(<console>:17)
at $anonfun$1.apply(<console>:17)
val second: PartialFunction[List[Int],Int] = {
case x :: y :: _ => y
}
new PartialFunction[List[Int], Int] {
def apply(xs: List[Int]) = xs match {
case x :: y :: _ => y
}
def isDefinedAt(xs: List[Int]) = xs match {
case x :: y :: _ => true
case _ => false
}
}
###kingori
멋진 예재. skip.
###zeide
extractor는 chapter.26에서 만날 수 있습니다.