15장 case classes and pattern matching - codeport/scala GitHub Wiki

패턴매칭할 클래스에 case키워드를 사용한다.

15.1 A simple example

zeide

간단한 예제로 알아보자.

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 메소드를 사용할 수 있다.
  • 패턴매칭을 지원한다.

kingori

  • pattern matching
    • selector match { alternatives }
    • switch와 다른 점
      • match도 expression이다. 즉, 값을 반환한다.
      • 명시적으로 break; 를 해야 하는 switch와 달리 match를 찾은 이후엔 계속 matching을 시도하지 않는다.
      • 어떤 패턴에도 매칭되지 않으면 MatchError 예외를 던진다. 예외를 피하려면 case _ => 를 명시하는 방법을 써야 한다.

15.2 Kinds of patterns

zeide

@nephilim님이 정리하신 지난 스터디 패턴매칭 자료
(*빛바구니 아이디가 필요합니다.)

kingori

  • 패턴 유형
    • wildcard pattern: case _ => . 아무거나 다 대응됨. java switch의 default와 비슷.
    • constant pattern: 특정 값. 리터럴/ val / 싱글턴 객체 사용 가능함.
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 _ =>
}

15.3 Pattern guards

###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' => ...

15.4 Pattern overlaps

###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
                              ˆ 

15.5 Sealed classes

###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

15.6 The Option type

###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 = ?

15.7 Patterns everywhere

###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 가 생략된 형태로 보이는데 맞는지?)
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
  }
}

15.8 A larger example

###kingori

멋진 예재. skip.

15.9 Conclusion

###zeide

extractor는 chapter.26에서 만날 수 있습니다.

⚠️ **GitHub.com Fallback** ⚠️