다형성

다형성이란 타입정보를 매개변수화 함으로써 인터페이스의 재사용을 높인다.
https://twitter.github.io/scala_school/ko/type-basics.html

1 제네릭 클래스 (Generic Classes)

값만 넘길 수 있도록 정의된 함수는 보통의 함수가 되고, 구체적 타입과 값을 넘겨야 하는 경우 일반적(제네릭) 함수가 된다

제네릭은 다양한 타입의 객체들을 다루는 메서드나 컬렉션 클래스에 컴파일 시의 타입 체크를 해주는 기능이다.
즉, 클래스 내부에서 사용할 데이터 타입을 나중에 인스턴스를 생성할 때 확정하는 것을 제네릭이라 한다.

(C++ / JAVA 에서도 제네릭 메서드가 존재) http://devbox.tistory.com/entry/Java-%EC%A0%9C%EB%84%A4%EB%A6%AD
자바 개념이 있어서 그런지 스칼라 문서들은 다 대충 설명한다.
입문자라면 자바에서 개념을 찾는 것이 편하다.
JAVA

class Person<T>{
    public T info;
    // p1 일시 데이터 타입은 String이된다.(인스턴스 생성시 String 제네릭 생성) 
    // p2 일시 데이터 타입은 StringBuilder이 된다. 
}
public class GenericDemo {
    public static void main(String[] args) {
        Person<String> p1 = new Person<String>();
        Person<StringBuilder> p2 = new Person<StringBuilder>();
    }
}

스칼라

class Stack[T] {
  var elems: List[T] = Nil
  def push(x: T) { elems = x :: elems }
  def top: T = elems.head
  def pop() { elems = elems.tail }
}
2 다형성 메소드 (Polymorphic Methodes)
def dup[T](x: T, n: Int): List[T] =
3 가변성 (타입 가변성) (Type Variance)

위 예제에서 Stack[T] 가 Stack[Char] 의 인스턴스에는 Stack[Char] 만 가능한 제약을 해결하기 위해 도입

3-1 순가변자 (Covariant)

순가변자는 타입 파라미터가 하위 타입을 포괄하도록 함으로써 타입 파라미터의 호환 범위를 넓혀준다.

class X[+T]
// 순가변적 파라미터 T가 선언된 타입을 상속했다면, 언제나 scala.Nothing 타입(모든 타입의 최하위 타입)을 타입 파라미터로 사용할 수 있다.
val x: X[String] = new X[Nothing]   // legal
val x: X[String] = new X[Object]    // illegal
3-2 역가변자 (Contravariant)

역가변자는 타입 파라미터가 상위 타입을 포괄하도록 함으로써 타입 파라미터의 호환 범위를 넓혀준다

class Y[-T]
// new Y 임을 주목
val y: Y[String] = new Y[Nothing]   // illegal
val y: Y[String] = new Y[Object]    // legal
타입경계 Type Boundaries

타입 파라미터의 가변성과는 별도로, 타입 파라미터의 범위를 한정해 타입간 포함 관계를 제약해야 할 때가 있다.

  • 상위 타입 경계의 경우, 타입 파라미터 T가 반드시 타입 A의 하위 타입이어야 한다면 ‘T <: A’

  • 하위 타입 경계의 경우, 타입 파라미터 T가 반드시 타입 A의 상위 타입이어야 한다면 ‘T >: A’로 표현해 이 관계를 지정할 수 있다.

타입 가변성 & 경계 (Type Variance & Boundaries)

타입 가변성은 타입 파라미터가 허용하는 타입의 범위를 확장 하지만 이것만 가지고 범위를 제한 할 수 없다.
아래는 한계점을 설정해줘야 하는데 가변성&경계를 이용해 범용성을 높인 예제

// 타입 변수 A를 순가변자로 어노테이션해서 하위 타입을 포용할 수 있도록 변경
class Stack[+A] {
  // 하지만 push 메소드의 파라미터가 역가변자 위치 <<< 이건 먼말인지 확인하라
  //  A를 그대로 사용할 경우 컴파일 오류
  // 새로운 타입 변수 B를 도입해서 push 메소드의 타입을 A와 분리하는 한편, A와 B 간에 타입 경계 관계를 설정해 두 타입 간에 연결이 유지
  def push[B >: A](elem: B): Stack[B] = new Stack[B] {
    override def top: B = elem
    override def pop: Stack[B] = Stack.this
    override def toString() = elem.toString() + " " +
                              Stack.this.toString()
  }
  def top: A = sys.error("no element on stack")
  def pop: Stack[A] = sys.error("no element on stack")
  override def toString() = ""
}
상위타입 경계

http://docs.scala-lang.org/ko/tutorials/tour/upper-type-bounds.html
타입 파라미터와 추상타입의 타입
상위 타입 경계 T <: A
타입 변수 T를 선언하면서 서브타입 A를 참조

trait Similar {
  def isSimilar(x: Any): Boolean
}
case class MyInt(x: Int) extends Similar {
  def isSimilar(m: Any): Boolean =
    m.isInstanceOf[MyInt] &&
    m.asInstanceOf[MyInt].x == x
}
object UpperBoundTest extends App {
  // 여기
  // 상위 타입 경계 어노테이션이 없었다면 메소드 findSimilar에서 isSimilar 메소드를 호출할 수 없었을 것
  def findSimilar[T <: Similar](e: Txs: List[T]): Boolean =
    if (xs.isEmpty) false
    else if (e.isSimilar(xs.head)) true
    else findSimilar[T](e, xs.tail)
  val list: List[MyInt] = List(MyInt(1)MyInt(2)MyInt(3))
  println(findSimilar[MyInt](MyInt(4), list))
  println(findSimilar[MyInt](MyInt(2), list))
}
하위타입 경계

http://docs.scala-lang.org/ko/tutorials/tour/lower-type-bounds.html
상위 타입 경계 T >: A
T나 추상 타입 T가 타입 A의 슈퍼타입

 


+ Recent posts