abstract class AbstractTable[T](val tableTag: Tag, val schemaName: Option[String], val tableName: String) extends Rep[T] {
/** ... **/
/** The * projection of the table used as default for queries and inserts.
* Should include all columns as a tuple, HList or custom shape and optionally
* map them to a custom entity type using the <> operator.
* The `ProvenShape` return type ensures that
* there is a `Shape` available for translating between the `Column`-based
* type in * and the client-side type without `Column` in the table's type
* parameter. */
def * : ProvenShape[T]
}
위와 같이 AbstractTable[T]의 내부 method로 정의되며 ProvenShape[T]가 Return Type이다.
그리면 이어서 ProvenShape[T]를 살펴보자.
trait ProvenShape[U] {
def value: Any
val shape: Shape[_ <: FlatShapeLevel, _, U, _]
def packedValue[R](implicit ev: Shape[_ <: FlatShapeLevel, _, U, R]): ShapedValue[R, U]
def toNode = packedValue(shape).toNode
}
위와 같은 형태의 trait으로 추론해보면,
(Rep[Int], Rep[String]) 형태의 원천 Tuple 데이터를 ProvenShape[(Int, String)] 형태로 변환하는 implicit type converstion이 어디인가 정의되어 있음을 추정할 수 있다.
해당 변환의 정의는 object ProvenShape에서 찾을 수 있으며, 해당 object는 trait ProvenShape[T]의 동반 객체(Companion Object)로 해당 object내에 정의된 implicit def는 별도의 import 없이 ProvenShape[T]를 사용하는 스코프내에서 처리될 수 있다.
object ProvenShape {
/** Convert an appropriately shaped value to a ProvenShape */
implicit def proveShapeOf[T, U](v: T)(implicit sh: Shape[_ <: FlatShapeLevel, T, U, _]): ProvenShape[U] =
new ProvenShape[U] {
def value = v
val shape: Shape[_ <: FlatShapeLevel, _, U, _] = sh
def packedValue[R](implicit ev: Shape[_ <: FlatShapeLevel, _, U, R]): ShapedValue[R, U] = ShapedValue(sh.pack(value).asInstanceOf[R], sh.packedShape.asInstanceOf[Shape[FlatShapeLevel, R, U, _]])
}
/** ... **/
}
정의된 Tuple의 값이 아래의 provenShapeOf(A)(B) function을 통해 최종적으로는 ProvencShape[U]로 변환 처리됨을 알 수 있다.
Type이 너무 많은 코드라 복잡하고 뭐가 뭔지 알 수가 없다. 갈 길을 잃을 타이밍이나 아래의 내용만 명심하면 된다.
tuple2Shape는 tuple2의 type구조를 반영하는 Shape 객체를 그저 만들어 내는 역할을 한다.
TupleShapeImplicits trait 파일을 실제로 살펴보면 Tuple 의 갯수별로 implicit function이 개별적으로 구현되어 있는데 입력되는 Tuple의 갯수가 한정적이고, 데이터 갯수에 따라서 1:1로 implicit conversion이 정의되어 있음을 알 수 있다.
여기서 드는 의문은 동일한 TupeShape Class의 객체라 할지라도 선언된 Type parameter와 문맥이 일치하는 객체를 찾아 implicit converstion에 적용할 수 있느냐 이다. 아래의 예제를 살펴보자.
다른 특이점으로 Apple object의 apply method 상에서 parameter 정의가 없으나 실제 호출시 명시적으로 값을 설정할 수 있으며, this.asInstanceOf[BaseFruite[A, B]]를 통하여 해당 object의 super class type의 instance를 반환하고 있다. (코드 패턴으로 보임)
다시 본론으로 돌아와서,
아래의 reColumnShape는 context bound 로 정의( [T: BaseTypedType,..] )로 인하여 BaseTypedType[T]의 parameter를 역시나 암시적 매개변수(implicit parameter)로 처리된다.
repColumnShape(intColumnType), repColumnShape(stringColumnType)
// 참고
trait RepShapeImplicits extends OptionShapeImplicits {
/** A Shape for single-column Reps. */
@inline implicit def repColumnShape[T : BaseTypedType, Level <: ShapeLevel]
= RepShape[Level, Rep[T], T]
}
그러면 위의 intColumnType, StringColumnType 은 어떤식으로 implicit로 정의되어 있을까?
Case class는 일반적은 Parameter를 포함하는 생성자를 제공하는 일반적은 클래스와 패턴매칭을 통해 재귀적 객체 추출구성(recursive decomposition)을 제공한다.
아래는 clase class의 예제
abstract class Term
case class Var(name: String) extends Term
case class Fun(arg: String, body: Term) extends Term
case class App(f: Term, v: Term) extends Term
사용상의 편의를 위해서 calse class의 생성시 new 키워드를 사용하지 않아도 된다. 클래스 명을 함수같이 쓰면 된다.
아래의 예제 참고
Fun("x", Fun("y", App(Var("x"), Var("y"))))
생성자의 parameter는 모두 public 변수로 간주되며, 아래와 같이 직접 접근이 가능하다.
val x = Var("x")
Console.println(x.name)
모든 case class는 equal method(내부적으로 equality 규칙과 toString method가 재정의 된)가 컴파일러에 의해 자동 생성되어 아래와 같이 사용할 수 있다.
case class는 아래와 같이 data structure를 구분하는데 사용될 수 있다.
object TermTest extends Application {
def printTerm(term: Term) {
term match {
case Var(n) =>
print(n)
case Fun(x, b) =>
print("^" + x + ".")
printTerm(b)
case App(f, v) =>
Console.print("(")
printTerm(f)
print(" ")
printTerm(v)
print(")")
}
}
def isIdentityFun(term: Term): Boolean = term match {
case Fun(x, Var(y)) if x == y =>truecase _ =>false
}
val id = Fun("x", Var("x"))
val t = Fun("x", Fun("y", App(Var("x"), Var("y"))))
printTerm(t)
println
println(isIdentityFun(id))
println(isIdentityFun(t))
}
위의 예제이서, print 함수는 각 matching 상태를 나타내며, match 키워드에 해당 case의 구현체(body)에 matching 되는 형태를 취하고 있다.
isIdentityFun은 주어진 term이 simple identity 조건에 부합하는지를 검사하는 함수이다. 주어진 값이 패턴에 matching된 이후에 if 함수가 실행(evaluate)된다. 성공적으로 매칭된 경우 true를 return 하고 fail인 경우 다음 pattern matching을 시도하게 된다.
실행 결과는 아래와 같다.
^x.^y.(x y)
true
false
x == y는 scala 컴파일러에 의해 구현된 equal 함수에 의해 자동적으로 처리되었음을 확인 할 수 있다.