Programming in Sala 책으로 스칼라 스터디하면서 정리했던 내용이다. 지금은 3판 번역본도 나왔지만, 약간 앞서서 스터디를 시작해서 2판으로 진행했다.
15장
케이스 클래스
case
라는 수식자가 붙은 클래스
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
- 기능이 추가됨
- 클래스 이름과 동일한 팩토리 메소드 추가
- 클래스 파라미터 목록에 있는 모든 클래스 파라미터에 암시적으로
val
접두어 추가 toString
,hashCode
,equals
메소드의 자연스러운 구현 추가 (전체 구조를 비교함)copy
메소드 추가 : 클래스에서 일부를 변경한 복사본 생성
// 팩토리 메소드
val v = Var("x")
val op = BinOp("+", Number(1), v) // 중첩해서 객체 생성
// 파라미터가 클래스의 필드가 됨 (val)
v.name
op.left
// 전체를 구조적으로 서로 비교함
op.right == Var("x") // true
// copy 메소드
op.copy(operator = "-") // BinOp(-,Number(1.0),Var(x))
패턴 매치
match
표현식 : 자바의 switch와 비슷.셀렉터 match { 대안들 }
def simplifyTop(expr: Expr): Expr = expr match {
case UnOp("-", UnOp("-", e)) => e // 부호를 두번 반전
case BinOp("+", e, Number(0)) => e // 0을 더함
case BinOp("*", e, Number(1)) => e // 1을 곱합
}
- Java switch VS Scala match
- match는 표현식
- 대안(case)은 다음으로 넘어가지 않는다
- 매치 실패시 MatchError 발생
패턴의 종류
와일드카드 패턴
- 모든 객체 매치
- 매치한 값을 사용하지 않음
expr match {
case BinOp(_, _, _) => println(expr +" is a binary operation")
case _ => println("It's something else")
}
상수 패턴
- 자신과 똑같은 값. 모든 리터럴, val, 싱글톤 객체
==
연산자를 적용하여 매치
def describe(x: Any) = x match {
case 5 => "five"
case true => "truth"
case "hello" => "hi!"
case Nil => "the empty list"
case _ => "something else"
}
변수 패턴
- 모든 객체 매치
- 화살표 오른쪽에 있는 표현식에 변수가 나타나면 해당 변수의 값은 매치된 값을 참조
expr match {
case 0 => "zero"
case somethingElse => "not zero: "+ somethingElse
}
변수 / 상수 패턴 구분
- 소문자로 시작하는 간단한 이름은 변수
- 그 외에는 상수
- 대문자로 시작 :
Pi
- 긴 이름 사용 :
this.pi
- 역따옴표 사용
- 대문자로 시작 :
생성자 패턴
UnOp("-", e)
과 같은 형태- 케이스 클래스에 속하는지 검사하고, 인자로 전달 받은 값을 검사
- 깊은 매치 지원 : 최상위 객체만을 매치시킬 뿐 아니라, 내포 패턴과 객체의 내용에 대해서도 매치
expr match {
case BinOp("+", e, Number(0)) => println("a deep match")
case _ =>
}
시퀀스 패턴
- 배열이나 리스트 등
// 길이가 정해진 경우
expr match {
case List(0, _, _) => println("found it")
case _ =>
}
// 길이 관계없이 매치
expr match {
case List(0, _*) => println("found it")
case _ =>
}
튜플 패턴
expr match {
case (a, b, c) => println("matched "+ a + b + c)
case _ =>
}
타입 지정 패턴
- 타입 검사나 변환을 간편하게 대신함
def generalSize(x: Any) = x match {
case s: String => s.length
case m: Map[_, _] => m.size
case _ => -1
}
- 타입 검사 :
isInstanceOf[T]
- 타입 변환 :
asInstanceOf[T]
if (x.isInstanceOf[String]) {
val s = x.asInstanceOf[String]
s.length
} else ...
- 타입 소거 : Map 매치 시에 타입 인자에 대한 매치가 불가
def isIntIntMap(x: Any) = x match {
case m: Map[Int, Int] => true // 모든 Map에 대해서 매칭된다
case _ => false
}
def isStringArray(x: Any) = x match {
case a: Array[String] => "yes" // Array는 타입 소거되지 않기때문에 Array[String]만 매칭된다
case _ => "no"
}
변수 바인딩
- 패턴에 변수를 추가
[변수 이름] @ [패턴]
expr match {
case UpOp("abs", e @ UnOp("abs", _)) => e
case _ =>
}
패턴 가드
- 패턴 변수는 한 패턴 안에 오직 한 번만 나올 수 있음
expr match {
case BinOp("+", x, x) => BinOp("*", x, Number(2)) // Wrong
case _ => e
}
- 패턴 가드 :
[패턴] if [불리언 표현식]
expr match {
case BinOp("+", x, y) if x == y => BinOp("*", x, Number(2))
case _ => e
}
패턴 겹침
코드에 있는 순서대로 패턴을 매치시키므로, 구체적인 패턴일 수록 앞에 와야하며, 모든 경우를 처리하는 패턴은 가장 뒤에 있어야 함.
봉인한 클래스(sealed class)
- 봉인한 클래스는 클래스와 같은 파일이 아닌 다른 곳에서 서브 클래스를 만들 수 없다.
- 패턴 매치 시에 이미 알려진 서브 클래스만 고려하면 되므로, 놓친 패턴 조합을 찾기 쉽다.(컴파일러 경고)
sealed
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
@unchecked
: 문맥상 MatchError 발생하지 않는 경우, 패턴을 모두 다루는지 검사를 생략
def describe(e: Expr): String = (e: @unchecked) match {
case Number(_) => "a number"
case Var(_) => "a variable"
}
Option 타입
Some(x)
: x가 실제값None
: 값이 없음
패턴 사용
변수 정의
val myTuple = (123, "abc")
val (number, string) = myTuple
val exp = new BinOp("*", Number(5), Number(1))
val BinOp(op, left, right) = exp
case 시퀀스로 부분 함수 만들기
PartialFunction[]
// 함수 타입
val second: List[Int] => Int = {
case x :: y :: _ => y
}
// 부분 함수 타입
val second: PartialFunction[List[Int], Int] = {
case x :: y :: _ => y
}
isDefinedAt
: 부분 함수가 어떤 값에 대해 결과 값을 정의하고 있는지 확인
second.isDefinedAt(List(5, 6, 7)) // true
second.isDefinedAt(List()) // false
for 표현식
for ((country, city) <- capitals) // 튜플 패턴 사용
println("The capital of "+ country +" is "+ city)
val results = List(Some("apple"), None, Some("orange))
for (Some(fruit) <- results)
println(fruit) // apple과 orange만 출력
예제
import Element.elem
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
class ExprFormatter {
// 연산자 우선 순위
private val opGroups =
Array(
Set("|", "||"),
Set("&", "&&"),
Set("^"),
Set("==", "!="),
Set("<", "<=", ">", ">="),
Set("+", "-"),
Set("*", "%")
)
private val precedence = {
val assocs =
for {
i <- until opGroups.length
op <- opGroups(i)
} yield op -> i
assocs.toMap
}
private val unaryPrecedence = opGroups.length // 단항 연산자
private val fractionPrecedence = -1 // 나눗셈
private def format(e: Expr, enclPrec: Int): Element =
e match {
case Var(name) =>
elem(name)
case Number(num) =>
def stripDot(s: String) =
if (s endsWith ".0") s.substring(0, s.length - 2)
else s
elem(stripDot(num.toString))
case UnOp(op, arg) =>
elem(op) beside format(arg, unaryPrecedence)
case BinOp("/", left, right) =>
val top = format(left, fractionPrecedence)
val bot = format(right, fractionPrecedence)
val line = elem("-", top.width max bot.width, 1)
val frac = top above line above bot
if (enclPrec != fractionPrecedence) frac
else elem(" ") beside frac beside elem(" ")
case BinOp(op, left, right) =>
val opPrec = precedence(op)
val l = format(left, opPrec)
val r = format(right, opPrec + 1)
val oper = l beside elem(" "+ op + " ") beside r
if (enclPrec <= opPrec) oper
else elem("(") beside oper beside elem(")")
}
def format(e: Expr): Element = format(e, 0)
}
object Express extends Application {
val f = new ExprFormatter
val e1 = BinOp("*", BinOp("/", Number(1), Number(2)),
BinOp("+", Var("x"), Number(1)))
val e2 = BinOp("+", BinOp("/", Var("x"), Number(2)),
BinOp("/", Number(1,5), Var("x")))
var e3 = BinOp("/", e1, e2)
def show(e: Expr) = println(f.format+ "\n\n")
for (e <- Array(e1, e2, e3)) show
}
'Developing' 카테고리의 다른 글
Programming in Scala 스터디 정리 - 16장. 리스트 (0) | 2017.09.26 |
---|---|
Programming in Scala 스터디 정리 - 14장. 단언문과 단위 테스트 (0) | 2017.09.23 |
Programming in Scala 스터디 정리 - 13장. 패키지와 임포트 (0) | 2017.09.21 |