덕 타이핑과 구조적 타이핑

Written by Vallista2021년 8월 19일 · 6 min read

1

덕 타이핑과 구조적 타이핑 두 가지는 비슷하면서, 근본적으로 다르다.

흔히 아는 것처럼, 덕 타이핑은 자바스크립트 등의 언어에서 쉽게 찾아볼 수 있었던 개념이었을 것이다. 하지만 구조적 타이핑은 흔히 접해보지 않은 이야기일 수 있다. 물론 C/C++ 등의 언어에서도 구조적 타이핑을 가능하도록 Template과 같은 친구들을 제공하지만. 이는 다른 이야기고 언어적인 차원에서 구조적 타이핑을 중점적으로 다루는 언어는 많지 않다. (OCaml, TypeScript, Go 등.)

그렇다면 덕 타이핑은 무엇이고, 구조적 타이핑은 무엇일까?

덕 타이핑 (Duck-Typing)

2 (위의 사진으로 설명 끝)

덕 타이핑은 객체의 변수, 메소드의 집합이 객체의 타입을 결정하는 것을 말한다. 즉, 위의 사진처럼 콘센트나 돼지코냐 "이름"은 중요하지 않다. 꽃을 수 있는가 없는가 꽃을 수 있으면 그것은 콘센트라고 부를 수 있다.

아래의 자바스크립트 코드를 보면, 아래와 같다 (이하 예제는 자바스크립트와 타입스크립트를 기준으로 설명한다)

class Duck {
  quack() {
    console.log('꽥!')
  }

  feathers() {
    console.log('깃털은 검정색과 흰색')
  }
}

class Human {
  quack() {
    console.log('사람인데요? 꽥!')
  }

  feathers() {
    console.log('사람이라 깃털은 없어요. 하지만 털은 있습니다.')
  }
}

function inTheForest(duck) {
  duck.quack()
  duck.feathers()
}

inTheForest(new Duck())
inTheForest(new Human())

inTheForest 함수는 duck(오리)을 parameter로 받는다. 오리의 울음소리와 날개를 가지면 숲속의 오리라고 생각하는 함수이다. 그래서 숲속의 오리는 quack()으로 소리를 내고, feathers()로 깃털을 보인다. 그렇기 때문에 duck이라고 생각할 수 있다. 하지만 위의 예제를 보면, Human과 Duck 두 클래스의 메소드로 quack과 feather를 선언했고 두 클래스의 인스턴스를 만들어 inTheForest에 넣어주었다. 동일하게 두 객체는 각각 해당하는 콘솔 로그를 실행한다.

여기서 주의깊게 봐야할 부분은 두 군데이다. 하나는 duck이라고 하는 인자를 받는 inTheForest 함수선언부이고, 하나는 inTheForest에 Duck과 Human의 인스턴스를 넣어 실행하는 함수실행부이다.

함수선언부와 함수실행부?

자바스크립트 코드를 보면, 기존의 정적 타이핑 언어(Java, C#)등과 다르게 함수선언부에 러프하게 파라미터를 넣는 것을 볼 수 있다. 흔한 애노테이션이나 심지어 타입도 없다. 이는 자바스크립트의 특성으로. 자바스크립트는 초기에 비개발자들의 접근을 쉽게 만들기 위해서 동적 타이핑 언어로써 설계되었다. 그렇기 때문에 지금 당장 코드를 짤 때 파라미터가 어떤 값을 들고 있는지는 상관없다. quack()이든 feathers()든 당장 있는지 검사를 하지 않지만 런타임에서 판별하면 되기 때문이다.

inTheForest의 함수실행부를 보면, Duck과 Human 다른 이름의 두 클래스 객체가 들어가는 걸 볼 수 있다. 두 클래스 객체뿐만 아니라 대충 함수, 변수, quack과 feathers가 없는 클래스까지 아무거나 다 들어간다. 다만, 내부에서의 실행조건이 quack, feathers가 없으니 런타임상에서 에러가 날 뿐이다.

함수선언부와 함수실행부를 통해 우리는 두 가지 사실을 알 수 있다.

  • 위험한 코딩: 정적 타이핑 언어처럼 런타임 전 컴파일이나 타입체킹을 하는 시간이 없으니 함수의 의도에 맞지 않는 코딩을 할 수 있는 가능성이 존재한다. 어떤게 들어가든, 심지어 없는게 들어가도 에러가 나오지 않는다!
  • 어떻게든 실행: 타입이 맞지 않거나, 체크가 안되어도 어떻게든 실행은 된다. 런타임 중, 해당 함수를 실행하긴 하는데, 해도 에러만 출력될 뿐이지 실행은 된다.

이렇게 설계하므로써 얻는 이득은 두 가지가 있다.

  • 당장 빠르게 개발할 수 있다.
  • 유연하게 작성할 수 있어서 생산성을 높힐 수 있다 (하나의 함수로 여러 객체(개체)에서 사용할 수 있는 함수를 별다른 코스트 없이 만들 수 있다.)

여기서 집중해야 할 부분은 두 번째 영역이다. 하나의 함수로 여러 객체(개체) 단위의 행동을 유연하게 작성할 수 있다는 것은 타 강력한 타이핑 언어의 경우에는 동일하게 사용하기 위해 Generic 이라던지, 상속(다형성)을 위해서든 어떠한 테크닉을 배워야한다. 하지만 자바스크립트나 파이썬은? 그런거 없다. 그냥 구현해서 사용하면 될 뿐이다.

역사

그렇다면 이러한 구현 방식이 가진 장점은 알게 되었는데, 왜 덕 타이핑이라고 불리는가?

  1. 덕 타이핑 이전에 덕 테스트 (Duck Test)라는 용어가 있다.
  2. 덕 테스트 (Duck Test)란?
    • 덱 테스트는 귀추법이다.
      • 귀추법은 가정을 선택하는 추론의 한 방법이며, 만약 사실이라면 관계있는 증거를 가장 잘 설명할 것 같은 가정을 선택하는 방법이다.
        • a를 b의 설명으로 추론하는 방법이다.
        • ex) "만약 어떤 새가 오리처럼 걷고, 헤엄치고, 꽥꽥거리는 소리를 낸다면 나는 그 새를 오리라고 부를 것이다"
        • 오리처럼 걷고, 헤엄치고, 꽥꽥거리는 소리를 내니까 오리이다.
  3. 1738년 프랑스의 한 자동차 제조업자가 기계오리를 발명했다.
    • 이 기계오리는 오리처럼 꽥꽥거리고 머리를 움직여 곡물을 먹고 배설을 하는 기계
    • 여기서 중대한 논리적 오류가 발생하는데, 먹고 꽥꽥이며 배설하면 오리라고 가정할 수 있는데, 사실은 18세기 기계 로봇이라 귀추법의 오류가 만들어진다.
    • 여기에서 기원이 되어 여러 나라의 유명한(혹은 회의에서) 용어로 널리 쓰이게 된다.
  4. "ㅁㅁ 처럼 보이고 ㅁㅁ처럼 행동하면 ㅁㅁ가 아닐리가 없다" 처럼 쓰인다.
  5. 그래서 이러한 형태의 논리로 확인하는 걸 덕 테스트라고 한다.

구조적 타이핑 (Structural-Typing)

구조적 타이핑은 구조적 타입 시스템(Structural Type System) 이라고도 불린다. 실제 구조와 정의에 의해 결정되는 타입 시스템의 한 종류이다. 명시적 선언이나 이름을 기반으로 하는 명목적 타입 시스템(Nominal Type System) 인 Java, C#등과 다르고, 런타임에 타입을 체크하는 덕 타이핑과 다르다.

interface Vector2D {
  x: number
  y: number
}

interface NamedVector {
  name: string
  x: number
  y: number
}

function calculateLength(v: Vector2D) {
  return Math.sqrt(v.x * v.x + v.y * v.y)
}

const v: NamedVector = { x: 3, y: 4, name: 'mgh' }
calculateLength(v)

위 예제는 명목적 타입 시스템을 기준으로 Vector2D를 사용하도록 의도한 코드일 것이다. 하지만 구조적 타이핑 언어에서는 전혀 다른 개념으로 이해해야 한다. Vector2D의 속성에 해당하는 값이 값을 넣는 타입에 속성으로 존재하는가가 로 이해해야 한다. 이 개념은 집합과 관계가 있는데, 구조적 타이핑은 명목적 타이핑과 같은 명확한 상속 관계(A - B)를 지향하기보다 집합으로 포함한다는 개념을 지향하기 때문이다.

Cook, W.R.; Hill, W.L.; Canning, P.S. (January 1990). "Inheritance is not subtyping". Proceedings of the Seventeenth Annual ACM Symposium on Principles of Programming Languages. San Francisco, California: 125–135. doi:10.1145/96709.96721. ISBN 978-0897913430. 상속은 서브타이핑이 아니다를 증명한 논문도 있다. 즉 둘은 서로 다르고 집합관계와 구분된다.

  • 같은 속성의 타입이 있는지를 체크하기 때문에 위의 두 인터페이스는 호환된다.
    • 그러므로 중복되는 범위가 있다면 재사용할 수 있고, 생산성 있는 코드를 쉽게 생산할 수 있다.
  • 단점으로, 의도하지 않았지만 동일한 타입을 가지는 경우 의도치 않게 동일한 유형으로 간주될 수 있다.

역사

집한 단위로 체크하기 때문에 중복되는 속성 혹은 중복되는 메서드를 획기적으로 줄일 수 있는 큰 장점이 존재한다. 그렇다면 이러한 구조적 타이핑의 역사를 살펴보면 아래와 같다.

  1. 느슨한 프로그래밍 언어는 빡빡한 언어들에서 생산성을 높이고자 하는 도전 실패의 결과였다. (예를 들자면 C언어등의 빡빡한 언어들이 가진 생산성과 접근성이 낮아지는 문제들..)
  2. 이런 예시로, C언어의 진화 단계를 보면 이해하기가 수월한데,
    1. C언어는 초기 단순한 타입 시스템을 지향하고 있었고, 현재도 그러한 장점으로 많이 쓰이고 있는 언어다. (원래 함수 선언에 인수 유형도 포함되지 않았음!)
    2. 그런데, 후속 버전인 C++에서 굉장히 다양한 멀티 패러다임을 섞기 시작했고, 확장되었다
      • 타입 및 인수 전달 방법, 컴파일러에 대한 엄청난 복잡함이 만들어짐
      • C++은 타입 시스템의 좋은 예가 아니었다! 하지만 이러한 예를 계승한 수많은 언어들이 있음 (ㅠㅠ)
    3. 쨋든 이러한 언어의 문제는 강력한 타입 시스템 및 복잡한 문법으로 코드 재사용성을 제한하는 것
      • 그래서 다형석으로 완화를 시도했는데..
        • C++이 클래스기반 다형성을 제공했고, 다양한 관심을 받음
        • 관심과 인기가 증가하면서 일반 라이브러리의 다양한 대응 및 확장이 필요했음 (범용적으로 사용할 수 있도록 언어단위의 제공의 필요)
        • 그래서 템플릿이라는(generic과 비슷한, 하지만 구동원리는 엄청 다른) 메타 프로그래밍 언어 체계를 개발했는데 너무 복잡하고 개발하는데 어려웠음
      • 강력한 타입 시스템은 대다수의 런타임 에러를 컴파일 단위에서 막아주지만, 도움이 되지 않는 에러 및 단순한 문제를 우회하게 만들기도 함
      • 그래서 어렵고, 빡세기 때문에 라이브러리를 구현하기 어렵고 잘 다루는 사람이 압도적으로 적음
    4. 자바스크립트와 비교해보면
      • 품질이 낮은 라이브러리가 많지만 품질 높은 라이브러리도 많고, 압도적으로 C++보다 라이브러리 수가 많다. (생태계가 넓다)
  3. 그래서
    • 견고한 이론적 토대를 기반으로 한 타입 시스템 언어들이 속속히 등장!
      • Haskell이라던지, Rust라던지, Go 라던지..
    • 이런 친구들 사이에서 구조적 타입 시스템이라는 개념이 등장하게 되었음 (최근 아님!)
      • 생산성이 좋으면서도 타입의 장점을 가져가는 그러한 언어를 워너비로 개발하는 것이 "구조적 타입 시스템"이다.

덕 타이핑과 구조적 타이핑의 차이

그래서, 두 개를 알아보았는데, 덕 타이핑과 구조적 타이핑의 차이는 무엇인가?

  1. 덕 타이핑은 런타임에 타입을 체크한다 (혹은 안할수도 있음)
  2. 구조적 타이핑은 타입 시스템 기반에서 컴파일 타임(혹은 타입체커)에서 타입을 체크한다.
  3. 즉, 둘 다 객체의 변수, 메소드 같은 필드를 기반으로 타입을 체크(혹은 안할수도)하지만 덕 타이핑은 동적 타이핑에서, 구조적 타이핑은 정적 타이핑에서 쓰인다
  4. 덕 타이핑은 다형성 관점에서 주목해야하고, 구조적 타이핑은 타입 체킹 관점이다.

reference