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
}
'학교 공부 > 프로그래밍원리' 카테고리의 다른 글
1122 Stacking with Type Classes (0) | 2022.11.22 |
---|---|
11/16 Type Classes With Multiple Parameters & Higher-kind (0) | 2022.11.15 |
PART 2 - Object-Oriented Programming (1) (0) | 2022.10.23 |
PART 1 - Exception & Handling , DataType (0929) (0) | 2022.10.20 |
HW2 - Exercise 2 (0) | 2022.10.19 |