03. trait

Trait

  • 비슷한 속성을 지닌 Classes들을 한 번에 다루기!!

In [2]:
import java.time.{Duration, LocalDateTime} // import 몰아쓰기

trait Visitor {
  def id: String
  def createdAt: LocalDateTime
  def age = Duration.between(createdAt, LocalDateTime.now())
}
case class Anonymous(
  id: String,
  createdAt: LocalDateTime = LocalDateTime.now()
) extends Visitor
case class User(
  id: String,
  email: String,
  createdAt: LocalDateTime = LocalDateTime.now()
) extends Visitor
println("Ok!")


Ok!
Out[2]:
import java.time.{Duration, LocalDateTime}

defined trait Visitor
defined class Anonymous
defined class User

In [5]:
import java.time.Duration
import java.time.LocalDateTime

trait Visitor {
  def id: String
  def createdAt: LocalDateTime
  def age = java.time.Duration.between(createdAt, LocalDateTime.now())
}
case class Anonymous(
  id: String,
  createdAt: LocalDateTime = LocalDateTime.now()
) extends Visitor
case class User(
  id: String,
  email: String,
  createdAt: LocalDateTime = LocalDateTime.now()
) extends Visitor
println("Ok!")


Ok!
Out[5]:
import java.time.Duration

import java.time.LocalDateTime


defined trait Visitor
defined class Anonymous
defined class User

In [6]:
import java.time.{Duration => D, LocalDateTime}

trait Visitor {
  def id: String
  def createdAt: LocalDateTime
  def age = D.between(createdAt, LocalDateTime.now())
}
case class Anonymous(
  id: String,
  createdAt: LocalDateTime = LocalDateTime.now()
) extends Visitor
case class User(
  id: String,
  email: String,
  createdAt: LocalDateTime = LocalDateTime.now()
) extends Visitor
println("Ok!")


Ok!
Out[6]:
import java.time.{Duration => D, LocalDateTime}


defined trait Visitor
defined class Anonymous
defined class User

In [6]:
// 익명인 사람과 아닌 사람을 확인할 때 사용
// 구현 된 method와 안된 method가 존재함. 위에선 구현된 것을 재사용
// extends ~는 상속 받는다는 것을 의미
// Trait : Java의 인터페이스와 유사함

Sealed Trait

  • 달라진 것은 sealed라는 호칭이 하나 더 붙은 것
  • 같은 file에만 존재한다는 의미! 다른 파일은 상속받는 사람이 없다!
  • sealed를 사용할 경우 장점은 Visitor는 Anonymous와 User뿐! 잘못된 코딩을 예방할 수 있음

In [7]:
import java.time.{Duration, LocalDateTime}
sealed trait Visitor {
  def id: String
  def createdAt: LocalDateTime
  def age = Duration.between(createdAt, LocalDateTime.now())
}
case class Anonymous(
  id: String,
  createdAt: LocalDateTime = LocalDateTime.now()
) extends Visitor
case class User(
  id: String,
  email: String,
  createdAt: LocalDateTime = LocalDateTime.now()
) extends Visitor


Out[7]:
import java.time.{Duration, LocalDateTime}

defined trait Visitor
defined class Anonymous
defined class User

In [8]:
def missingCase(v: Visitor) = v match {
  case User(_, _, _) => "Got a user"
}


Out[8]:
defined function missingCase

In [8]:
<pastie>:18: warning: match may not be exhaustive.
It would fail on the following input: Anonymous(_, _)
def missingCase(v: Visitor) = v match {
                              ^
missingCase: (v: Visitor)String

Final

  • 특정 클래스는 더이상 extend가 불가능하다고 이야기하고 싶을 때!
  • 상속이 더 안됨!

In [9]:
sealed trait Visitor { /* ... */ }
final case class User(/* ... */) extends Visitor
final case class Anonymous(/* ... */) extends Visitor


Out[9]:
defined trait Visitor
defined class User
defined class Anonymous

PRODUCT TYPE PATTERN

  • Foo가 a(type A), b(type B), c(type C)를 가졌으면:

In [9]:
case class Foo(a: A, b: B, c: C)

SUM TYPE PATTERN

  • Foo가 A, B 또는 C이면:
  • 이거 아니면 저거

In [ ]:
sealed trait Foo
final case class A() extends Foo
final case class B() extends Foo
final case class C() extends Foo

FOO HAS A, B OR C


In [10]:
trait Foo {
  def bar: Bar
}
sealed trait Bar
final case class A() extends Bar
final case class B() extends Bar
final case class C() extends Bar
// 이런 방식을 더 선호


Out[10]:
defined trait Foo
defined trait Bar
defined class A
defined class B
defined class C

In [11]:
sealed trait Foo
final case class Bar(a: A) extends Foo
final case class Baz(b: B) extends Foo
final case class Qux(c: C) extends Foo


Out[11]:
defined trait Foo
defined class Bar
defined class Baz
defined class Qux

In [12]:
// FOO IS A, B AND C : with
// 다중 상속!
// trait에서만 다중 상속
// trait만 상속하고 나머지에선 상속을 안하는 것을 추천드립니다


trait A
trait B
trait C
trait Foo extends A with B with C


Out[12]:
defined trait A
defined trait B
defined trait C
defined trait Foo

DIAMOND INHERITANCE PROBLEMS


In [13]:
trait Foo {
  def sound: String
}
trait Bar extends Foo {
  override def sound = "Baaaaaaar!"
}
trait Baz extends Foo {
  override def sound = "Baaaaaaaaz!"
}
case object Qux extends Bar with Baz
// Bar를 하고 Baz를 덮어씀! 제일 우측에 있는 것이 적용됨
// override : method를 덮어 씌워라!


Out[13]:
defined trait Foo
defined trait Bar
defined trait Baz
defined object Qux

In [15]:
Qux.sound


Out[15]:
res14: String = "Baaaaaaaaz!"

POLYMORPHISM VS PATTERN MATCHING

  • POLYMORPHISM : 상속을 받아 내용을 바꾸는 것
  • PATTERN MATCHING : 케이스를 쪼개서 분기 처리

  • 둘 다 구현 가능!

POLYMORPHISM EXAMPLE


In [16]:
trait Animal {
  def sound: String
}
class Cat extends Animal {
  override def sound: String = "mew"
}
class Dog extends Animal {
  override def sound: String = "bowwow"
}


Out[16]:
defined trait Animal
defined class Cat
defined class Dog

In [17]:
val cat1 = new Cat


Out[17]:
cat1: Cat = $sess.cmd15Wrapper$Helper$Cat@474c6562

In [18]:
cat1.sound


Out[18]:
res17: String = "mew"

PATTERN MATCHING

  • 상위 trait에 this match

In [19]:
sealed trait Integer {
  def abs: Int = this match {
    case NaturalNumber(n) => n
    case Zero => 0
    case NegativeInteger(n) => -n
  }
}
final case class NaturalNumber(n: Int) extends Integer
final case class NegativeInteger(n: Int) extends Integer
final case object Zero extends Integer


Out[19]:
defined trait Integer
defined class NaturalNumber
defined class NegativeInteger
defined object Zero
  • 어떤 경우에 POLYMORPHISM?
    • 케이스를 계속 늘려갈 경우
  • 어떤 경우 PATTERN MATCHING?
    • 자료 구조는 그대로고 method가 변하는 경우
    • sealed, final을 이용해 빠지는 method을 검사해서 사용 가능
    • 이 형태가 조금 더 functional에 가까움

In [19]:
sealed trait shape{

    def sides: Double = this match {
        case Circle(r) => 0
        case Rectagle(r) => 4
        case Square(r) => 4
    }
    def perimeter: Double = this match {
        case Circle(r) => 
        case
    }
    def area: Double = this match {
        case Circle(r) =>
        case Recta
    }
}


SyntaxError: found "}\n    def area: Doub", expected TypePattern | BindPattern at index 227
    }
    ^

실습 : Shape trait을 구현

  • sides는 변의 숫자를 구합니다.
  • perimeter는 둘레를 구합니다.
  • area는 면적을 구합니다

In [22]:
sealed trait Shape {
    def sides: Int
    def perimeter: Double
    def area: Double
}

case class Circle(radius: Double) extends Shape{
    override val sides = 0
    override val perimeter = 2 * math.Pi * radius
    override val area = math.Pi * radius * radius
}

case class Rectangle(width: Double, height: Double) extends Shape {
    override val sides = 4
    override val perimeter = 2 * (width + height)
    override val area = width * height
}

case class Square(side: Double) extends Shape {
    override val sides = 4
    override val perimeter = 4 * side
    override val area = side * side
}
println("OK")


OK
Out[22]:
defined trait Shape
defined class Circle
defined class Rectangel
defined class Square

Rectanguler 시리즈로 묶는다면


In [23]:
sealed trait Shape {
    def sides: Int
    def perimeter: Double
    def area: Double
}

case class Circle(radius: Double) extends Shape{
    override val sides = 0
    override val perimeter = 2 * math.Pi * radius
    override val area = math.Pi * radius * radius
}

trait Rectangular extends Shape {
    def width: Double
    def height: Double
    
    override val sides = 4
    override val perimeter = 2 * (width + height)
    override val area = width * height    
}

case class Rectangle(width: Double, height: Double) extends Rectangular
case class Square(side: Double) extends Rectangular {
    override val width = side
    override val height = side
}

println("OK")


OK
Out[23]:
defined trait Shape
defined class Circle
defined trait Rectangular
defined class Rectangle
defined class Square

In [ ]:

연습 : 신호등

  • Red, Green, Yellow로 구성된

  • TrafficLight 데이터타입을 구현해보세요

  • Red → Green → Yellow → Red 로 순환되는 method next를 구현해보세요.

  • 메소드를 클래스 외부에 구현해보세요.

  • 메소드를 클래스 내부에 polymorphism으로 구현해보세요.
  • 메소드를 클래스 내부에 패턴 매칭으로 구현해보세요.
  • 어떤 방법이 가장 나은가요? 그 이유는?

외부


In [29]:
sealed trait TrafficLight 

case object Red extends TrafficLight
case object Green extends TrafficLight
case object Yellow extends TrafficLight
// 파라미터가 필요하지 않기 떄문에 case object로!!!!

def next(current: TrafficLight): TrafficLight = current match {
    case Red => Green
    case Green => Yellow
    case Yellow => Red
}


assert(next(Red) == Green)
assert(next(Green) == Yellow)
assert(next(Yellow) == Red)

println("OK")


OK
Out[29]:
defined trait TrafficLight
defined object Red
defined object Green
defined object Yellow
defined function next
  • 케이스 외부 : 아예 밖에 해보라는 것

In [26]:
next(Red)


Out[26]:
res25: TrafficLight = Green

In [27]:
next(Green)


Out[27]:
res26: TrafficLight = Yellow

In [28]:
next(Yellow)


Out[28]:
res27: TrafficLight = Red

this : 파이썬의 self와 같은 존재!!!! 클래스 내부에서 존재

내부 polymorphism

  • 인자가 없으니 case object

In [36]:
sealed trait TrafficLight {
    def next: TrafficLight
}

case object Red extends TrafficLight {
    override lazy val next: TrafficLight = Green
}

case object Green extends TrafficLight {
    override lazy val next: TrafficLight = Yellow
}

case object Yellow extends TrafficLight {
    override lazy val next: TrafficLight = Red
}

assert(Red.next == Green)
assert(Green.next == Yellow)
assert(Yellow.next == Red)


Out[36]:
defined trait TrafficLight
defined object Red
defined object Green
defined object Yellow
  • lazy를 안하니 StackOverFlow Error가 발생했는데, 이건 재귀적인 무언가 에러같은데 lazy를 넣어서 중간에 끊어주면 됨

내부에 패턴 매칭


In [41]:
sealed trait TrafficLight {
    def next: TrafficLight = this match {
        case Red => Green
        case Green => Yellow
        case Yellow => Red
    }
}

case object Red extends TrafficLight
case object Green extends TrafficLight
case object Yellow extends TrafficLight


assert(Red.next == Green)
assert(Green.next == Yellow)
assert(Yellow.next == Red)

println("OK")


OK
Out[41]:
defined trait TrafficLight
defined object Red
defined object Green
defined object Yellow
  • 신호등은 3개밖에 존재하지 않기 때문에 패턴 매칭이 더 나음
  • 클래스 외부에 구현하는 것은 여러가지 구현이 있으면 가능하지만, default처럼 쓰려면 내부에
  • 순서가 바뀌지 않는 전제가 있다면 내부에 패턴 매칭이 제일 적절

RECURSIVE DATA

  • functional 함수에선 재귀적인 스타일로 많이 짬

In [42]:
sealed trait PeanoNumber {
  def toInt: Int = this match {
    case Zero => 0
    case Succ(prev) => 1 + prev.toInt
  }
}
final case object Zero extends PeanoNumber
final case class Succ(prev: PeanoNumber) extends PeanoNumber


Out[42]:
defined trait PeanoNumber
defined object Zero
defined class Succ
  • 모든 자연수를 표현할 수 있음
  • 재귀적 케이스

In [43]:
Zero.toInt


Out[43]:
res42: Int = 0

In [44]:
Succ(Succ(Succ(Zero))).toInt


Out[44]:
res43: Int = 3

In [45]:
Succ(Succ(Zero)).toInt


Out[45]:
res44: Int = 2

In [46]:
// 구조만 있는 Tree 저장은 없어용
sealed trait Tree
final case object Leaf extends Tree
final case class Node(left: Tree, right: Tree) extends Tree
// 이건 Tail Recurson을 만들 수는 없음! 그러나 StackOverflow Error를 방지하기 위해 Heap을 사용함


Out[46]:
defined trait Tree
defined object Leaf
defined class Node
  • Stack : 빠르게 Access할 수 있는, 재귀를 다루는 친구
  • Heap : object를 선언할 경우 외부의 heap에 만들어주고 관리. 느리지만 오래 가는 친구

In [47]:
def height(tree: Tree): Int = tree match {
  case Leaf => 0
  case Node(left, right) => 1 + (height(left) max height(right))
}


Out[47]:
defined function height

(height(left) max height(right)) : 둘 중 큰거를 해라!


In [48]:
height(Node(Node(Node(Leaf, Leaf), Leaf), Leaf))


Out[48]:
res47: Int = 3

TAIL RECURSON

  • 재귀적인 것을 사용해서 StackOverflow Error가 발생할 수 있어서 이런 것을 사용
  • 재귀함수로 구현했어도 컴파일 타임에 어떤 조건을 맞추면 루프로 바꿔서 돌려줌 => Overflow가 안나옴
  • 이것을 Checkt하는 것이 @annotation.tailrec
    • 결과값을 다시 사용하지 않음!! acc + 1, 이전에는 1 + prev.toInt
    • accumurate에 더하는 방식으로 사용

In [50]:
sealed trait PeanoNumber {
  @annotation.tailrec
  def toInt(acc: Int = 0): Int = this match {
    case Zero => acc
    case Succ(prev) => prev.toInt(acc + 1)
  }
}
final case object Zero extends PeanoNumber
final case class Succ(prev: PeanoNumber) extends PeanoNumber


Out[50]:
defined trait PeanoNumber
defined object Zero
defined class Succ

In [53]:
Zero.toInt()
// 괄호가 있어야 함!! acc가 있어서!


Out[53]:
res52: Int = 0

In [52]:
Succ(Succ(Succ(Zero))).toInt()


Out[52]:
res51: Int = 3

연습: INTLIST

  • 다음 IntList정의를 활용해서 length 메소드를 구현해보세요
  • 맨 끝 단을 base case라고 하고, 그 이후에 나머지를 채우는 방식으로 코딩하세요

In [65]:
sealed trait IntList {
    def length: Int = this match {
        case End => 0
        case Pair(head, tail) => tail.length + 1
    }
    
}
final case object End extends IntList
final case class Pair(head: Int, tail: IntList) extends IntList

val example = Pair(1, Pair(2, Pair(3, End)))

assert(example.length == 3)
assert(example.tail.length == 2)
assert(End.length == 0)

println("OK")


OK
Out[65]:
defined trait IntList
defined object End
defined class Pair
example: wrapper.wrapper.Pair = Pair(1,Pair(2,Pair(3,End)))

In [66]:
sealed trait IntList {
  def length: Int = {
    @annotation.tailrec
    def loop(current: IntList, acc: Int = 0): Int = current match {
      case End => acc
      case Pair(head, tail) => loop(tail, acc + 1)
    }
    loop(this)
  }
}
final case object End extends IntList
final case class Pair(head: Int, tail: IntList) extends IntList

val example = Pair(1, Pair(2, Pair(3, End)))

assert(example.length == 3)
assert(example.tail.length == 2)
assert(End.length == 0)

println("Ok")


Ok
Out[66]:
defined trait IntList
defined object End
defined class Pair
example: wrapper.wrapper.Pair = Pair(1,Pair(2,Pair(3,End)))

연습: CALCULATOR

  • Expression은 Addition, Subtraction, 혹은 Number입니다.
  • Addition은 left와 right Expression을 가지고 있습니다.
  • Subtraction은 left와 right Expression을 가지고 있습니다.
  • Number는 Double타입의 value 하나를 갖고 있습니다,
  • Expression을 실제로 계산해서 Double로 바꿔주는 eval 메소드를 구현

  • 실패할 수 있는 계산도 있습니다.

  • Double에는 NaN이라는 값이 이를 나타내긴 합니다만, 계산 실패와 그 이유를 명시적으로 나타내면 더 좋을 겁니다.

  • 적절한 데이터 타입 Success, Failure을 구현하시고 eval을 그에 맞추어 수정

  • Division과 SquareRoot 연산을 추가해보세요. 다음을 통과할 수 있어야 합니다


In [74]:
sealed trait Expression 

final case class Addition(left: Expression, right: Expression) extends Expression
final case class Substraction(left: Expression, right: Expression) extends Expression
final case class Number(value: Double) extends Expression

println("OK")


OK
Out[74]:
defined trait Expression
defined class Addition
defined class Substraction
defined class Number

In [74]:
// eval method 구현

In [75]:
sealed trait Expression {
    def eval: Double = this match{
        case Number(v) => v
        case Addition(l, r) => l.eval + r.eval
        case Substraction(l, r) => l.eval - r.eval
    }
}



final case class Addition(left: Expression, right: Expression) extends Expression
final case class Substraction(left: Expression, right: Expression) extends Expression
final case class Number(value: Double) extends Expression

println("OK")


OK
Out[75]:
defined trait Expression
defined class Addition
defined class Substraction
defined class Number
  • 데이터 자료는 case class로 만드는 것이 좋음

In [75]:
// 실패 계산
// data type을 success, failure

In [76]:
sealed trait Expression {
    def eval: Result = this match{
        case Number(v) => Success(v)
        case Addition(l, r) => (l.eval, r.eval) match {
            case (Success(v1), Success(v2)) => Success(v1 + v2)
            case (Failure(m), _) => Failure(m)
            case (_, Failure(m)) => Failure(m)
        }
        case Substraction(l, r) => (l.eval, r.eval) match {
            case (Success(v1), Success(v2)) => Success(v1 - v2)
            case (Failure(m), _) => Failure(m)
            case (_, Failure(m)) => Failure(m)
        }
    }
}

final case class Addition(left: Expression, right: Expression) extends Expression
final case class Substraction(left: Expression, right: Expression) extends Expression
final case class Number(value: Double) extends Expression

sealed trait Result
final case class Success(value: Double) extends Result
final case class Failure(msg: String) extends Result

println("OK")


OK
Out[76]:
defined trait Expression
defined class Addition
defined class Substraction
defined class Number
defined trait Result
defined class Success
defined class Failure

강사님이 수정한 것


In [1]:
sealed trait Expression {
  def eval: Result = this match {
    case Number(v) => Success(v)
    case Addition(l, r) => l.eval match {
      case Success(lv) => r.eval match {
        case Success(rv) => Success(lv + rv)
        case Failure(msg) => Failure(msg)
      }
      case Failure(msg) => Failure(msg)
    }
    case Subtraction(l, r) => l.eval match {
      case Success(lv) => r.eval match {
        case Success(rv) => Success(lv - rv)
        case Failure(msg) => Failure(msg)
      }
      case Failure(msg) => Failure(msg)
    }
  }
}
final case class Addition(left: Expression, right: Expression) extends Expression
final case class Subtraction(left: Expression, right: Expression) extends Expression
final case class Number(value: Double) extends Expression

sealed trait Result
final case class Success(value: Double) extends Result
final case class Failure(msg: String) extends Result

println("Ok")


Ok
Out[1]:
defined trait Expression
defined class Addition
defined class Subtraction
defined class Number
defined trait Result
defined class Success
defined class Failure

In [ ]: