티스토리 뷰

반응형

덕 타이핑 (Duck Typing)과 구조적 타이핑(Structural Typing)

만약 오리처럼 걷고 오리처럼 꽥 소리를 낸다면, 그것은 분명히 오리이다.

 

 

Duck typing - Wikipedia

Style of dynamic typing in object-oriented programming Duck typing in computer programming is an application of the duck test—"If it walks like a duck and it quacks like a duck, then it must be a duck"—to determine whether an object can be used for a p

en.wikipedia.org

덕 타이핑은 위의 설명 그대로인데, 좀 더 개념적으로 설명하면, 해당 개체를 구성하는 것들을 통해 개체를 정의한다는 개념이다.

이 개념은 JS의 기반이 되는데, JS에서 Object를 정의함에 있어서 property 추가 등에 대해 열려(open)있는 특성과 맞물린다. 즉, JS에서 object에 동적으로 property를 추가해버릴 수 있는데, 이러면 '오리' 에서 '청둥 오리'로 바뀔 수도 있고 백조 오리처럼 섞어버릴 수도 있다.

물론, 이렇게 객체 속성에 대해 열려있어서 동적으로 객체 자체를 변경해버릴 수 있는 것과 덕 타이핑이 동일한 의미는 아니다. 다만, 덕 타이핑의 특성처럼 JS에서는 객체를 바라볼 때 그 내부를 구성하는 프로퍼티가 핵심이 된다는 것이다.

 

그리고, TS는 이러한 JS의 superset이기때문에 이 특성을 그대로 따른다.

다만, TS는 컴파일 타임에 타입체킹을 하기때문에, 이 '타입'을 기준으로 본다는 점에서 구조적 타이핑(Structural Typing)이라고 부른다. 이러한 구조적 타입 시스템 (Structural Type System)을 다른 표현으로 property-based type system 라고도 부른다.

 

Structural type system - Wikipedia

Class of type systems A structural type system (or property-based type system) is a major class of type systems in which type compatibility and equivalence are determined by the type's actual structure or definition and not by other characteristics such as

en.wikipedia.org

 

따라서, 아래와 같이 유연하다고 할 수도 있지만, 의도치 않은 결과를 낼 수 있다. (하지만 컴파일 타임에서 오류라고 말해주지 않는다!)

 

아래는 유연하다고 할 수 있는 예이다.

interface Vector2D {
  x: number;
  y: number;
}

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

// 이름이 들어간 벡터 추가
interface NamedVector {
  name: string;
  x: number;
  y: number;
}

// NameVector의 구조가 Vector2D와 호환 (x,y 프로퍼티를 가지고 있어서)
const v: NamedVector = { x: 3, y: 4, name: 'Zee' };
calculateLength(v);  // 의도된 결과 반환

 

그리고, 위 코드에 아래 코드를 덧 붙이면, 의도치 않는 동작을 발생시키는 예를 볼 수 있다.

interface Vector3D {
  x: number;
  y: number;
  z: number;
}

function normalize(v: Vector3D) {
  const length = calculateLength(v);
  return {
    x: v.x / length,
    y: v.y / length,
    z: v.z / length,
  }
}
normalize({ x: 3, y: 4, z: 5 });
// { x: 0.6, y: 0.8, z: 1 }

calculteLength를 Vector2D계산용으로 만들어놨는데, 구조적으로 동일하다고 판단하여 문제 없이 연산해버리는 것이다.

그러면, z축에 대한 연산이 빠져버리기 때문에, 이상한 결과가 나오는 것이다.

어쨌든 이러한 TS/JS의 특성은 예기치 않은 오류를 야기한다. 따라서, 이러한 언어 특성을 고려하여 안전장치를 걸어두어야 한다. 나중에 다루겠지만, 이러한 것들을 예방하기 위해서 "brands" 라는 _(undercore prefix) 문법을 사용하는 테크닉이 있다.

// Note: 'brands' in our syntax nodes serve to give us a small amount of nominal typing. // Consider 'Expression'. Without the brand, 'Expression' is actually no different // (structurally) than 'Node'. Because of this you can pass any Node to a function that // takes an Expression without any error. By using the 'brands' we ensure that the type // checker actually thinks you have something of the right type. Note: the brands are // never actually given values. At runtime they have zero cost.

origin: https://github.com/Microsoft/TypeScript/blob/7b48a182c05ea4dea81bab73ecbbe9e013a79e99/src/compiler/types.ts#L693-L698

지금 바로 좀더 자세히 알고 싶다면 아래 링크를 참조하면 도움이 될 것이다.

 

 

Nominal Typing - TypeScript Deep Dive

Note how the brand enums, FooIdBrand and BarIdBrand above, each have single member (_) that maps to the empty string, as specified by { _ = "" }. This forces TypeScript to infer that these are string-based enums, with values of type string, and not enums w

basarat.gitbook.io

 

유닛테스트 작성에 유리한 구조적 타이핑

아래와 같이 DB라는 추상적인 interface를 만들고, 상속이 아닌 구조적 타이핑으로 PostgresDB, FirebaseDB 등 다양한 DB에 대한 처리를 할 수 있다.

interface Author {
  first: string;
  last: string;
}

interface DB {
  runQuery: (sql: string) => any[];
}

function getauthors(database: DB): Author[] {
  const authorRows = database.runQuery(`SELECT FIRST, LAST FROM AUTHORS`);
  return authorRows.map(row => ({ firstName: row[0], last: row[1] });
}

test('getAuthors', () => {
  const authors = getAuthors({
    runQuery(sql: string) {
      return [['Toni', 'Morrison'], ['Maya', 'Angelou']];
    }
  });
  expect(authors).toEqual([
    {first: 'Toni', last: 'Morrison'},
    {first: 'Maya', last: 'Angelou'}
  ]);
});

 

정리

TS/JS의 덕 타이핑과 구조적 타이핑에 대해 다루어 보았다.

여기서 전달하고 싶은 메세지는 사실 간결하다.

해당 언어의 컨셉에 대해 이해하고 사용하자.
(여기는 TS에 대한 것이니) 구조적 타이핑에 대해 익숙해지고 올바르게 사용하자.

따라서, 간단하게 개념에 대해서만 다루었으며 (사실 이 개념이 전부다),

위 메세지를 기억하며 TS를 TS답게 사용하도록 하자 :)

(의도치 않은 오류를 줄이는 것은 덤)

반응형
댓글