다형성(in Java, OCaml, C)

Written on January 16, 2017

polymorphism. 多相性.

하나의 정의를 다양한 타입에서 사용가능 한 것을 의미한다.

Java에서의 오버로딩/제네릭이 가장 먼저 떠오른다.

거기에 더해, + 등의 연산자는 좌, 우에 정수/실수/문자열 등이 올 수 있으므로 다형성을 가진다고 할 수 있다.

오버로딩/제네릭 등 함수의 파라미터에 대해서 다형성이 성립할 경우, 그것을 parametric polymorphism이라고 한다.

형 파라미터, type parameter, 型パラメータ 를 인터페이스/클래스에 정의하여 그것을 구체화 함으로서 다형성을 구현할 수 있다.

참고) 자바 제네릭에는 프리미티브 타입을 넣을 수 없다.

Tree<int> t = ...;  // illegal!
Tree<Integer> t = ...; // 대신에.

OCaml

코드로 보자

타입변수는 '가 앞에 붙는다.

type 'elm tree =
  | Lf
  | Br of {
    left: 'elm tree;
    value: 'elm;
    right: 'elm tree;
  }

(* Constructing a sample tree holding integers *)
let t1 = Br {left = Lf; value = 10; right = Lf}
(* Now let's construct a tree holding strings *)
let t11 = Br {left = Lf; value = "I"; right = Lf}

정의할 때는 'elm을 적지만, 실제로 사용할 때는 타입을 명시할 필요 없다. 컴파일러가 추론을 해주기 때문이다. 위의 예에서 정수와 문자열의 예를 확인할 수 있다.

타입변수는 여러 부분에서 찾아볼 수 있다. 아래 코드에서 'a는 트리에 원하는 어떤 종류의 타입도 사용될 수 있음을 의미한다. 정확히는 이것을 타입스킴(type scheme, 型スキーム)이라고 한다.

# let rec size t = ...;;
val size : 'a tree -> int = <fun>

このような,最も一般的な型を 主要型(principal type) と呼び,型推論が主要型を推論してくれることを 主要型性(principal type property) がある,という.

결국 자바와의 차이는, 정의가 아니라 구현에서 타입을 그때마다 적어줄 필요 없이, 컴파일러가 추론해준다는 점이다.


C의 경우 타입변수를 지정해줄 수 없다. 그럼 다형성을 어떻게 구현해야하는가?

void *를 활용한다.

void *는 다른 포인터를 암묵적 형변환을 통해 저장할 수 있다.

즉,

int *i = ...;
void *p = i;   // assignment from int * to void *

이런게 가능하다. 주의할 점은, 이것을 포인트로만 활용하지 않고 내용물을 꺼내려 한다면, 그 타입은 void이기 때문에 문제가 될 것이다.

struct tree {
  enum nkind { LEAF, BRANCH } tag;
  union {
    struct leaf { int dummy; } lf;
    struct branch {
      struct tree* left;
      void* value;        // a pointer to something
      struct tree* right;
    } br;
  } dat;
};

// Prepare elements in the heap
int *e1 = (int *)malloc(sizeof(int)); *e1 = 10;
// Construct a tree
// newbranch 함수 생략.
struct tree *t1 = newbranch(newleaf(), e1, newleaf());

이런 식으로 구현에 활용된다. 이전까지의 구현들은 value로는 정수나 문자열 등을 직접 활용했지만, 이 경우는 그 자리에 포인터를 놓고, 어떤 값을 가리키는 형태로 하여서 다형성을 흉내낸다.

실제 사용은, 형변환을 명시하여 이용한다.

// Functions whose behavior depends on the element type have to be defined separately
void print_inttree(struct tree* t) {
  if (t->tag == LEAF) {
    printf("leaf");
  } else {
    struct branch* b = &t->dat.br;
    int* v = (int*)b->value;
    printf("branch(");
    print_inttree(b->left);
    printf(", %d, ", * v);
    print_inttree(b->right);
    printf(")");
  }
  return;
}

포인트는 int* v = (int*)b->value;이다. 타입이 다르다고해서 컴파일러가 문제를 일으키지는 않으므로, 코드 작성자가 주의를 기울일 수밖에 없다.