Type Class

  • 원래 하스켈에 있던 개념
  • Trait로 구현

Example : SHOW TYPE CLASS


In [1]:
trait Show[A] {
  def show(a: A): String
}
object Show {
  val intCanShow: Show[Int] = new Show[Int] {
    def show(int: Int): String = s"int $int"
  }
}


Out[1]:
defined trait Show
defined object Show

In [2]:
import Show._
println(intCanShow.show(42))


int 42
Out[2]:
import Show._

Implicit parameters / implicit values

  • 명시적으로 값을 넣어주지 않아도 컴파일 타임에 알아서 집어넣어줌
  • 노트북에선 다시 쓰면 에러를 뱉음...! 제플린이나 ide에서 해야될 것 같습니다

In [6]:
object Show {
  def show[A](a: A)(implicit sh: Show[A]): String = sh.show(a)

  implicit val intCanShow: Show[Int] = new Show[Int] {
    def show(int: Int): String = s"int $int"
  }
}

In [6]:
import Show._
println(show(42))


int 42
Out[6]:
import Show._

In [5]:
Show.show(42)


Out[5]:
res4: String = "int 42"

In [6]:
Show.show("42")


cmd6.sc:1: could not find implicit value for parameter sh: cmd6Wrapper.this.cmd2.cmd0.wrapper.Show[String]
val res6 = Show.show("42")
                    ^
Compilation Failed

참고: IMPLICIT VALUES 선언방법


In [ ]:
implicit val example1 = ???
implicit var example2 = ???
implicit def example3 = ???
implicit object example4 {}

Implicitly


In [7]:
def implicitly[T](implicit e: T): T = e


Out[7]:
defined function implicitly

In [8]:
import Show._


Out[8]:
import Show._

In [8]:
implicitly[Show[Int]].show(42)


cmd8.sc:1: not found: type Show
val res8 = implicitly[Show[Int]].show(42)
                      ^
Compilation Failed

CONTEXT BOUND


In [8]:
def show[A: Show](a: A): String = implicitly[Show[A]].show(a)
  • 너무 기니까 그냥 apply로 변경

In [ ]:
def apply[A](implicit sh: Show[A]): Show[A] = sh

In [ ]:
def show[A: Show](a: A): String = Show.apply[A].show(a)

In [ ]:
def show[A: Show](a: A): String = Show[A].show(a)

Type Class interface Pattern


In [8]:
trait ExampleTypeClass[A] {
  def doSomething(in: A): Foo
}
object ExampleTypeClass {
  def apply[A](implicit instance: ExampleTypeClass[A]): ExampleTypeClass = instance
}

Implicit classes

  • 뒤에 ops로 다는 것이 컨벤션

In [8]:
implicit class ShowOps[A: Show](a: A) {
  def show: String = Show[A].show(a)
}

In [ ]:
import Show._
println(42.show)

In [ ]:
implicit class IntOps(n: Int) {
    def shout: Unit = for (i<-0 until n) println("Shout!")
    def ! : Int = (1 /: (1 ton))(_ * _)
}

In [ ]:
3.shout
3(IntOps).shout //랑 동일
println(3!) // factorial을 구현
// Shout!
// Shout!
// Shout!
  • 필요할 경우 method를 확장시키는 용도로 사용!

VALUE CLASSES

  • 선언하기 위한 조건이 있음
  • 런타임에 존재하지 않음 ( 런타임 오버헤드가 없음 )

In [ ]:
implicit class ShowOps[A](val a: A) extends AnyVal {
  def show(implicit sh: Show[A]): String = sh.show(a)
}

중간요약 2


In [ ]:
trait Show[A] {
  def show(a: A): String
}
object Show {
  def apply[A](implicit sh: Show[A]): Show[A] = sh
  def show[A: Show](a: A): String = Show[A].show(a)
  implicit class ShowOps[A](val a: A) extends AnyVal {
    def show(implicit sh: Show[A]): String = sh.show(a)
  }
  implicit val intCanShow: Show[Int] = new Show[Int] {
    def show(int: Int): String = s"int $int"
  }
}

다른 타입도 지원


In [ ]:
implicit val stringCanShow: Show[String] = new Show[String] {
  def show(str: String): String = s"string $str"
}
  • 중복이 많으니 공통 부분을 뽑자

In [ ]:
def instance[A](f: A => String): Show[A] = new Show[A] {
  def show(a: A): String = f(a)
}

implicit val intCanShow: Show[Int] = instance(int => s"int $int")
implicit val stringCanShow: Show[String] = instance(str => s"string $str")
  • 스칼라 2.12 버전 : sam conversion
  • method만 define하면 컴파일 타임에 알아서 바꿔줌

In [ ]:
implicit val intCanShow: Show[Int] = int => s"int $int"
implicit val stringCanShow: Show[String] = str => s"string $str"

중간요약 3


In [ ]:
trait Show[A] {
  def show(a: A): String
}
object Show {
  def apply[A](implicit sh: Show[A]): Show[A] = sh
  def show[A: Show](a: A): String = Show[A].show(a)
  implicit class ShowOps[A](val a: A) extends AnyVal {
    def show(implicit sh: Show[A]): String = sh.show(a)
  }
  implicit val intCanShow: Show[Int] = int => s"int $int"
  implicit val stringCanShow: Show[String] = str => s"string $str"
}

Tuple


In [ ]:
implicit def tupleCanShow[A, B](implicit sa: Show[A], sb: Show[B]): Show[(A, B)] = {
  case (a, b) => s"tuple (${a.show}, ${b.show})"
}

In [ ]:
implicit def tupleCanShow[A: Show, B: Show]: Show[(A, B)] = {
  case (a, b) => s"tuple (${a.show}, ${b.show})"
}

최종 결과물


In [ ]:
trait Show[A] {
  def show(a: A): String
}
object Show {
  def apply[A](implicit sh: Show[A]): Show[A] = sh
  def show[A: Show](a: A): String = Show[A].show(a)
  implicit class ShowOps[A](val a: A) extends AnyVal {
    def show(implicit sh: Show[A]): String = sh.show(a)
  }
  implicit val intCanShow: Show[Int] = int => s"int $int"
  implicit val stringCanShow: Show[String] = str => s"string $str"
  implicit def tupleCanShow[A: Show, B: Show]: Show[(A, B)] = {
    case (a, b) => s"tuple (${a.show}, ${b.show})"
  }
}

In [ ]:
println((2, 2).show)

연습 : Eq


In [10]:
trait Eq[A] {
    def eq(a1: A, a2: A): Boolean
}
object Eq {
    implicit val eqInt: Eq[Int] = new Eq[Int] {
        def eq(a1: Int, a2: Int): Boolean = a1 == a2
    }
}


Out[10]:
defined trait Eq
defined object Eq

In [11]:
assert(implicitly[Eq[Int]].eq(1, 1) == true)
assert(implicitly[Eq[Int]].eq(1, 2) == false)
println("OK!")


OK!
  • implicitly 없이 더 줄이면!
  • hint : apply method 구현

In [11]:
trait Eq[A] {
    def eq(a1: A, a2: A): Boolean
}
object Eq {
    def apply[A](implicit eq: Eq[A]): Eq[A] = eq
    implicit val eqInt: Eq[Int] = _ == _
}

assert(Eq[Int].eq(1, 1) == true)
assert(Eq[Int].eq(1, 2) == false)
println("OK!")
  • 위 코드는 쥬피터 노트북에선 에러가 발생~~ 스칼라피들에선 정상 작동
  • 이젠 ==== mehot를 만들어 봅시다!!
  • 여기선 import Eq._ 해야하거나 밖에서 정의해야함

In [11]:
//밖에서 정의하기
trait Eq[A] {
    def eq(a1: A, a2: A): Boolean
}
object Eq {
    def apply[A](implicit eq: Eq[A]): Eq[A] = eq
    implicit val eqInt: Eq[Int] = _ == _
}

implicit class EqOps[A](a: A)(implicit eq: Eq[A]) {
    def ==== (that: A): Boolean = Eq[A].eq(a, that)
}

assert((1 ==== 1) == true)
assert((1 ==== 2) == false)
println("OK!")

In [ ]:
// 또 정리하면

In [ ]:
trait Eq[A] {
  def eq(a1: A, a2: A): Boolean
}
object Eq {
  def apply[A](implicit eq: Eq[A]): Eq[A] = eq
  implicit val eqInt: Eq[Int] = _ == _
}
implicit class EqOps[A](val a: A) extends AnyVal {
  def ==== (that: A)(implicit eq: Eq[A]): Boolean = eq.eq(a, that)
}
assert((1 ==== 1) == true)
assert((1 ==== 2) == false)
println("Ok!")
  • string도 되로록 변경해봅시다

In [11]:
trait Eq[A] {
  def eq(a1: A, a2: A): Boolean
}
object Eq {
  def apply[A](implicit eq: Eq[A]): Eq[A] = eq
  implicit val eqInt: Eq[Int] = _ == _
  implicit val eqStr: Eq[String] = _ == _
}
implicit class EqOps[A](val a: A) extends AnyVal {
  def ==== (that: A)(implicit eq: Eq[A]): Boolean = eq.eq(a, that)
}
assert((1 ==== 1) == true)
assert((1 ==== 2) == false)
assert(("foo" ==== "foo") == true)
assert(("foo" ==== "bar") == false)
println("Ok!")
  • 이젠 list도 되도록!!

In [ ]:
trait Eq[A] {
  def eq(a1: A, a2: A): Boolean
}
object Eq {
  def apply[A](implicit eq: Eq[A]): Eq[A] = eq
  implicit val eqInt: Eq[Int] = _ == _
  implicit val eqStr: Eq[String] = _ == _
  implicit def eqList[A: Eq]: Eq[List[A]] = {
    case (Nil, Nil) => true
    case (x :: xs, Nil) => false
    case (Nil, y :: ys) => false
    case (x :: xs, y :: ys) => Eq[A].eq(x, y) && Eq[List[A]].eq(xs, ys)
  }
}
implicit class EqOps[A](val a: A) extends AnyVal {
  def ==== (that: A)(implicit eq: Eq[A]): Boolean = eq.eq(a, that)
}
assert((1 ==== 1) == true)
assert((1 ==== 2) == false)
assert(("foo" ==== "foo") == true)
assert(("foo" ==== "bar") == false)
assert((List(1, 2) ==== List(1, 2)) == true)
assert((List(1, 2) ==== List(1, 3)) == false)
assert((List("foo", "bar") ==== List("foo", "bar")) == true)
assert((List("foo") ==== List("foo", "bar")) == false)
println("Ok!")

연습 : Show 심화

  • list도 show 가능하도록 만들기

In [ ]:
// list.mkString -> ['a','b','c] => abc
// list.mkString("[",",, ","]") => [a, b, c]

trait Show[A] {
  def show(a: A): String
}
object Show {
  def apply[A](implicit sh: Show[A]): Show[A] = sh
  def show[A: Show](a: A): String = Show[A].show(a)
  implicit class ShowOps[A](val a: A) extends AnyVal {
    def show(implicit sh: Show[A]): String = sh.show(a)
  }
  implicit val intCanShow: Show[Int] = int => s"int $int"
  implicit val stringCanShow: Show[String] = str => s"string $str"
  implicit def tupleCanShow[A: Show, B: Show]: Show[(A, B)] = {
    case (a, b) => s"tuple (${a.show}, ${b.show})"
  }
  implicit def listCanShow[A: Show]: Show[List[A]] = _ map Show[A].show mkString("list [", ", ", "]")
  
}
import Show._
assert(List("foo", "bar").show == "list [string foo, string bar]")
println("Ok!")

연습 : MONOID

  • 항등원(zero)과 combine operation이 정의되는 타입클래스를 Monoid라고 합니다.

In [9]:
trait Monoid[A] {
  def zero: A
  def combine(a1: A, a2: A): A
}
object Monoid {
  implicit val intMonoid: Monoid[Int] = new Monoid[Int] {
      def zero: Int = 0
      def combine(a1: Int, a2:  Int): Int = a1 + a2
  }
}
assert(implicitly[Monoid[Int]].zero == 0)
println("Ok!")


Out[9]:
defined trait Monoid

In [11]:
// 미래를 대비해 instance를 정의해서 리팩토링하면

In [ ]:
trait Monoid[A] {
  def zero: A
  def combine(a1: A, a2: A): A
}
object Monoid {
  
  def instance[A](z: A)(f: (A, A) => A): Monoid[A] = new Monoid[A] {
    def zero: A = z
    def combine(a1: A, a2: A): A = f(a1, a2)
  }
  implicit val intMonoid: Monoid[Int] = instance(0)(_ + _)
}
assert(implicitly[Monoid[Int]].zero == 0)
assert(implicitly[Monoid[Int]].combine(10, 20) == 30)
println("Ok!")

In [11]:
// implicity 가 없어도 되도록 리팩토링

In [ ]:
trait Monoid[A] {
  def zero: A
  def combine(a1: A, a2: A): A
}
object Monoid {
  def apply[A](implicit m: Monoid[A]): Monoid[A] = m
  def instance[A](z: A)(f: (A, A) => A): Monoid[A] = new Monoid[A] {
    def zero: A = z
    def combine(a1: A, a2: A): A = f(a1, a2)
  }
  implicit val intMonoid: Monoid[Int] = instance(0)(_ + _)
}
assert(Monoid[Int].zero == 0)
assert(Monoid[Int].combine(10, 20) == 30)
println("Ok!")

In [ ]:
// |+| 연산자가 가능하도록 리팩토링

In [ ]:
trait Monoid[A] {
  def zero: A
  def combine(a1: A, a2: A): A
}
object Monoid {
  def apply[A](implicit m: Monoid[A]): Monoid[A] = m
  def instance[A](z: A)(f: (A, A) => A): Monoid[A] = new Monoid[A] {
    def zero: A = z
    def combine(a1: A, a2: A): A = f(a1, a2)
  }
  implicit val intMonoid: Monoid[Int] = instance(0)(_ + _)
}

implicit class MonoidOps[A](val a: A) extends AnyVal {
    def |+| (that: A)(implicit m: Monoid[A]): A = m.combine(a, that)
}
assert((10 |+| 20) == 30)
assert(Monoid[Int].zero == 0)
assert(Monoid[Int].combine(10, 20) == 30)
println("Ok!")

In [ ]:
// zero를 정의해서 Monoid[Int].zero를 Monoid.zero[Int]로 축약

trait Monoid[A] {
  def zero: A
  def combine(a1: A, a2: A): A
}
object Monoid {
  def apply[A](implicit m: Monoid[A]): Monoid[A] = m
  def zero[A: Monoid]: A = Monoid[A].zero
  def instance[A](z: A)(f: (A, A) => A): Monoid[A] = new Monoid[A] {
    def zero: A = z
    def combine(a1: A, a2: A): A = f(a1, a2)
  }
  implicit val intMonoid: Monoid[Int] = instance(0)(_ + _)
}

implicit class MonoidOps[A](val a: A) extends AnyVal {
  def |+| (that: A)(implicit m: Monoid[A]): A = m.combine(a, that)
}
assert((10 |+| 20) == 30)                          // 덧셈
assert((10 |+| Monoid.zero[Int]) == 10)            // 항등원
assert((Monoid.zero[Int] |+| 10) == 10)            // 항등원
assert((10 |+| 20 |+| 30) == (10 |+| (20 |+| 30))) // 결합법칙
println("Ok!")

In [ ]:
// list에서도 가능하도록 리팩토링

In [ ]:
trait Monoid[A] {
  def zero: A
  def combine(a1: A, a2: A): A
}
object Monoid {
  def apply[A](implicit m: Monoid[A]): Monoid[A] = m
  def zero[A: Monoid]: A = Monoid[A].zero
  def instance[A](z: A)(f: (A, A) => A): Monoid[A] = new Monoid[A] {
    def zero: A = z
    def combine(a1: A, a2: A): A = f(a1, a2)
  }
  implicit val intMonoid: Monoid[Int] = instance(0)(_ + _)
  implicit def listMonoid[A]: Monoid[List[A]] = instance(List.empty[A])(_ ::: _)
}

implicit class MonoidOps[A](val a: A) extends AnyVal {
  def |+| (that: A)(implicit m: Monoid[A]): A = m.combine(a, that)
}
assert((List(1, 2, 3) |+| List(4, 5, 6)) == List(1, 2, 3, 4, 5, 6))
assert((List(1, 2, 3) |+| Monoid.zero[List[Int]]) == List(1, 2, 3))
assert((10 |+| 20) == 30)                          // 덧셈
assert((10 |+| Monoid.zero[Int]) == 10)            // 항등원
assert((Monoid.zero[Int] |+| 10) == 10)            // 항등원
assert((10 |+| 20 |+| 30) == (10 |+| (20 |+| 30))) // 결합법칙
println("Ok!")

In [11]:
// tuple도 가능하도록

In [ ]:
trait Monoid[A] {
  def zero: A
  def combine(a1: A, a2: A): A
}
object Monoid {
  def apply[A](implicit m: Monoid[A]): Monoid[A] = m
  def zero[A: Monoid]: A = Monoid[A].zero
  def instance[A](z: A)(f: (A, A) => A): Monoid[A] = new Monoid[A] {
    def zero: A = z
    def combine(a1: A, a2: A): A = f(a1, a2)
  }
  implicit val intMonoid: Monoid[Int] = instance(0)(_ + _)
  implicit def listMonoid[A]: Monoid[List[A]] = instance(List.empty[A])(_ ::: _)
  implicit def tuple2Monoid[A, B](implicit ma: Monoid[A], mb: Monoid[B]): Monoid[(A, B)] =
    instance((ma.zero, mb.zero)){ case ((a1, b1), (a2, b2)) => (ma.combine(a1, a2), mb.combine(b1, b2)) }
}

implicit class MonoidOps[A](val a: A) extends AnyVal {
  def |+| (that: A)(implicit m: Monoid[A]): A = m.combine(a, that)
}
assert((10 |+| 20) == 30)                          // 덧셈
assert((10 |+| Monoid.zero[Int]) == 10)            // 항등원
assert((Monoid.zero[Int] |+| 10) == 10)            // 항등원
assert((10 |+| 20 |+| 30) == (10 |+| (20 |+| 30))) // 결합법칙
assert((List(1, 2, 3) |+| List(4, 5, 6)) == List(1, 2, 3, 4, 5, 6))
assert((List(1, 2, 3) |+| Monoid.zero[List[Int]]) == List(1, 2, 3))
val lhs = (List(1, 2), (1, List("a")))
val rhs = (List(3, 4), (2, List("b")))
assert((lhs |+| rhs) == (List(1, 2, 3, 4), (3, List("a", "b"))))
println("Ok!")

In [ ]:
trait Monoid[A] {
  def zero: A
  def combine(a1: A, a2: A): A
}
object Monoid {
  def apply[A](implicit m: Monoid[A]): Monoid[A] = m
  def zero[A: Monoid]: A = Monoid[A].zero
  def instance[A](z: A)(f: (A, A) => A): Monoid[A] = new Monoid[A] {
    def zero: A = z
    def combine(a1: A, a2: A): A = f(a1, a2)
  }
  implicit val intMonoid: Monoid[Int] = instance(0)(_ + _)
  implicit def listMonoid[A]: Monoid[List[A]] = instance(List.empty[A])(_ ::: _)
  implicit def tuple2Monoid[A: Monoid, B: Monoid]: Monoid[(A, B)] =
    instance((zero[A], zero[B])){ case ((a1, b1), (a2, b2)) => (a1 |+| a2, b1 |+| b2) }
}

implicit class MonoidOps[A](val a: A) extends AnyVal {
  def |+| (that: A)(implicit m: Monoid[A]): A = m.combine(a, that)
}
assert((10 |+| 20) == 30)                          // 덧셈
assert((10 |+| Monoid.zero[Int]) == 10)            // 항등원
assert((Monoid.zero[Int] |+| 10) == 10)            // 항등원
assert((10 |+| 20 |+| 30) == (10 |+| (20 |+| 30))) // 결합법칙
assert((List(1, 2, 3) |+| List(4, 5, 6)) == List(1, 2, 3, 4, 5, 6))
assert((List(1, 2, 3) |+| Monoid.zero[List[Int]]) == List(1, 2, 3))
val lhs = (List(1, 2), (1, List("a")))
val rhs = (List(3, 4), (2, List("b")))
assert((lhs |+| rhs) == (List(1, 2, 3, 4), (3, List("a", "b"))))
println("Ok!")