본문 바로가기

학교 공부/프로그래밍원리

11/10 Type Classes with Multiple Parameters

 

 

 

 

trait Ord[A] {
def cmp(me: A, you: A): Int
//Data가 없기 때문에 this가 없음.

def ===(me: A, you: A): Boolean = cmp(me,you) == 0 
def < (me: A, you: A): Boolean = cmp(me,you) < 0 
def > (me: A, you: A): Boolean = cmp(me,you) > 0 
def <= (me: A, you: A): Boolean = cmp(me,you) <= 0 
def >= (me: A, you: A): Boolean = cmp(me,you) >= 0
}

def max3[A](a: A, b: A, c: A)(implicit ord: Ord[A]) : A = 
	if (ord.<=(a, b)) {if (ord.<=(b,c)) c else b }
	else {if (ord.<=(a,c)) c else a }
    
implicit val intOrd : Ord[Int] = new Ord[Int] { def cmp(me: Int, you: Int) = me - you }
max3(3,2,10) // 10

 

 

 

INTERFACE 1

 

슬라이드 16쪽

// trait Iter[A] {
// def getValue:Option[A] 
// def getNext:Iter[A] 
// }
//예) Iter[Int]

trait Iter[I,A] {
  def getValue(i: I): Option[A]
  def getNext(i: I): I 
}
//내부적으로 구현됐던 type이 parameter로 나온 것.
//예) Iter[List[Int],Int]


// trait Iterable[A] { 
// def iter:Iter[A] 
// }

trait Iterable[R,A] {
  type Itr
  def iterIF: Iter[Itr, A] 
  //여기에 implicit을 붙여도 인식이 안돼서 교수님이 싫어하심 ..
  def iter(a: R): Itr
}

I : iter 그 자체의 type

 

** 중요

OOP에서는 Iter가 return하는게 데이터+ 데이터를 처리하는 함수가 동시에 리턴.

하지만 이제는 iter가 리턴하는건 데이터만 리턴하는 것을 확인할 수 있음.

** 추가

Iterable을 정의하므로써 더 편해지는데 그 이유를 이해해보자.

 

 

타입과 타입에 대한 구현체

 

 

 

 

 

슬라이드 17쪽

def sumElements[I](xs: I)(implicit IT: Iter[I,Int]) : Int = 
  IT.getValue(xs) match {
	case None => 0
	case Some(n) => n + sumElements(IT.getNext(xs)) }
    
def printElements[I,A](xs: I)(implicit IT: Iter[I,A]) : Unit = 
  IT.getValue(xs) match {
	case None =>
	case Some(n) => {println(n); printElements(IT.getNext(xs))}}
    
def sumElements2[R](xs: R)(implicit ITR: Iterable[R,Int]) = 
  sumElements(ITR.iter(xs))(ITR.iterIF)
  //여기에서 뒤에 (ITR.iterIF)를 붙여야 된다는게 중요함. 스칼라의 단점.
  //본질적인 type class에서는 컴파일러가 알아서 해줘서 안붙여도 되는데 스칼라에선 해야됨.
//sumElements[ITR.Itr](ITR.iter(xs))(ITR.iterIF)

def printElements2[R,A](xs: R)(implicit ITR: Iterable[R,A]) = 
  printElements(ITR.iter(xs))(ITR.iterIF)
//printElements[I,A](ITR.iter(xs))(ITR.iterIF)

**implicit 사용하는 법 잘 이해해두기

 

 

 

 

 

 

 

INTERFACE 2

--> OOP와 다르게 인터페이스 정의하기

 

인터페이스로 코드를 짜야 뭔가 바꿀 때(업데이트 ..) 내 코드 전체를 바꿀 필요가 사라짐.

 

슬라이드 18쪽

trait ListIF[L,A] {
  def empty : L
  def head(l: L) : Option[A] 
  def tail(l: L) : L 
  def cons(a:A,l:L):L 
  def append(l1: L, l2: L) : L
}


trait TreeIF[T,A] {
  def empty : T 
  def node(a:A,l:T,r:T):T 
  def head(t: T) : Option[A] 
  def left(t: T) : T
  def right(t: T) : T
}

trait ListOF[A] {

def empty : ??? 

// base가 없어서 아무 것도 할 수 없음. 

// 무에서 유를 창조하는 거니까

def head: Option[A]

def tail: ListOF[A]

def append(that:ListOF[A]):ListOF[A]

-> 인자를 받아도 내가 아는 데이터 타입인지 알 수 없음. (임의의 모든 데이터 타입에 대해서 구현해야됨.)

}

 

반면에 trait ListIF[L,A] 는 내가 생각하고 있는 데이터 타입인 L에 대해서만 구현하게 됨.

아래처럼 생성해주는 애를 하나 정의해야 됨.

 

trait ListFactory[A] {

  def empty: ListOF[A]

}

 

따라서 좋은 프로그래밍 패턴이 인터페이스 중심으로 하는 것. OOT 중심보단.

 

def testList[L](implicit LI: ListIF[L,Int], IT: Iter[L,Int]) { 
  val l = LI.cons(3, LI.cons(5, LI.cons(2, LI.cons(1, LI.empty)))) 
  println(sumElements(l)) //sumElements(l)(listIter[Int]) 
  printElements(l) //printElements(l)(listIter[Int])
}

def testTree[T](implicit TI: TreeIF[T,Int], ITR: Iterable[T,Int]) {
  val t: T = TI.node(3, TI.node(4, TI.empty, TI.empty), TI.node(2, TI.empty, TI.empty))
  println(sumElements2(t))
  printElements2(t)
}

interface는 라이브러리를 직접 인포트 한 것 같이 자연스럽게 코딩이 가능해진다. 

 

 

이렇게 인터페이스끼리 합치는 것도 자연스럽게 사용 가능.

trait ListF1[L,A] {
  def empty:L
  def head(I:L):Option[A]
}


trait ListF2[L,A]{
  def listIF1 : ListF1[L,A]
  def tail(I:L):L
  def cons(a:A,I:L):L
  def append(l1:L,l2,L):L
}

 

 

 

implicit def listIter[A] : Iter[List[A], A] = 
  new Iter[List[A],A] {
    def getValue(a: List[A]) = a.headOption
    def getNext(a: List[A]) = a.tail 
  }
  
  
implicit def listIF[A] : ListIF[List[A],A] =
  new ListIF[List[A],A] {
	def empty: List[A] = Nil
	def head(l: List[A]) = l.headOption
	def tail(l: List[A]) = l.tail
	def cons(a: A, l: List[A]) = a :: l
	def append(l1: List[A], l2: List[A]) = l1 ::: l2
  }