- TypeScript의 런타임 타입 정보 방출 필요성을 주장하고, 이 문제를 우회하는 Type Mapping, Code Generation / External Tool, Adapter 프로젝트를 모아 둔 목록
- 핵심 문제는 리플렉티브 타입 시스템 없이 직렬화와 검증을 처리할 때 끝없는 보일러플레이트나 스키마 파일 기반 bespoke 코드 생성이 필요하다는 점
- 우회책으로 io-ts, zod 등이 제시되며, 타입을 라이브러리별 방식으로 다시 선언해야 하고 라이브러리들이 TypeScript의 모든 타입 기능을 지원하지 못한다는 불편
- TypeScript의 타입 삭제는 JavaScript 프로젝트가 TypeScript 지식 없이 방출된 JavaScript를 사용할 수 있게 하는 장점으로 인정하면서도, 코드와 분리된 조회 테이블 형태로 타입 정보를 방출할 수 있다는 주장
- 데코레이터로 해결하지 말아 달라는 요청과 함께, 인터페이스 사용과 외부 라이브러리 타입 지원을 위해
typescript.generateRuntimeType<T>()같은 컴파일러 인식 고차 함수, F# Type Providers, C# Source Generators 같은 접근을 제안 - 관련된 기존 논의로 8년 된 GitHub issue를 연결하고, 같은 문제를 겪은 프로젝트라면 별 수와 무관하게 목록에 PR을 보내 추가해 달라는 요청
TypeScript Needs to Emit Runtime Type Information - TypeScript 런타임 타입 정보 방출 요구
(github.com/akutruff)댓글과 토론
Hacker News 의견들
-
문제 설명을 4번쯤 읽어야 뭘 요구하는지 이해됐고, 이런 사례야말로 간결한 글쓰기가 왜 중요한지 보여줌
실제 요구는 런타임 타입 안전성으로 보이지만, TypeScript는 JavaScript의 대체/추가 런타임이 되지 않겠다는 선을 그어 왔기 때문에 가능성이 낮아 보임. TypeScript는 JS로 컴파일하고 빠지는 역할이고, 이후 V8 등에서 벌어지는 일은 범위 밖임.
“TypeScript가 런타임 타입 정보를 내보내야 한다”는 요구는 현재 TypeScript와는 꽤 다른 새 제품을 요구하는 것에 가까움- 런타임 타입 안전성 자체를 요구한다기보다, 컴파일 시점에 타입을 반영(reflection)해서 값을 생성하고 그 정보를 런타임에서 쓰게 해 달라는 요구에 가까움
예를 들어 임의의 인터페이스와 객체를 받아 검증하는 범용validate함수를 만들 수 있으면 좋겠다는 것임. 컴파일 시점 반영으로 타입별 JS 검증 코드를 생성하거나, 런타임에T를 인자로 넘겨 비교하는 방식이 가능할 수 있지만, 현재 TypeScript 철학에서는 어렵다고 봄 - 제목만 보고도 오래전부터 원하던 기능이라는 걸 바로 알았고, 더 공식적이고 잘 지원되는 https://github.com/rbuckton/reflect-metadata를 요구하는 것에 가까움
TypeScript는 컴파일 중 타입 정보를 많이 알고 있는데, 컴파일이 끝나면 그 정보를 버림. 이 정보를 파일로 내보내거나Reflect에 메타데이터로 남길 수 있는데도 버리는 건 특히 “모든 것이 객체”라는 JavaScript의 성격을 생각하면 아쉬운 제한임.
정확한 TS 타입 안전성까지는 못 얻더라도, getter/setter나 ES2015Proxy로 일부 검사를 붙일 수 있고, 타입 정보를 런타임에 남기면 실행 가능한 메타데이터로 여러 흥미로운 가능성이 열림 - TypeScript가 컴파일러라서 불가능하다는 건 맞지 않음. 타입 정보를 JS 객체로 출력하고 런타임에서 조회하는 반영 라이브러리를 지원하면 됨
이미 TSenum은 JS 객체로 출력되어 런타임 조회가 가능하지만, 문자열 리터럴 유니언 타입은 그렇지 않음. 또 TypeScript는 타입 가드 함수를 지원하므로, 타입 시스템 안의 정보를 이용해 이런 함수를 자동 생성하는 것도 크게 어렵지 않아 보임 - 대충 훑어봐도 요구는 명확했음. TypeScript가 타입 소거 과정에서 알아낸 타입 정보를 생성된 JavaScript 옆의 부가 채널로 내보내 달라는 것임
PDB 파일 같은 비유로 생각하면 됨. TypeScript가 이미 가지고 있다가 버리는 정보라서 완전히 새 제품이 필요한 건 아님 - README 상단에 “7년 된 GitHub 이슈”가 링크되어 있고, 거기서 문제를 더 직접적으로 설명함
https://github.com/microsoft/TypeScript/issues/3628
- 런타임 타입 안전성 자체를 요구한다기보다, 컴파일 시점에 타입을 반영(reflection)해서 값을 생성하고 그 정보를 런타임에서 쓰게 해 달라는 요구에 가까움
-
TypeScript PM 입장에서 욕구는 이해함. 데이터 검증에는 런타임 타입 검사가 자주 필요하고, 그 빈틈을 메우기 위한 라이브러리도 많음
다만 서로 다른 설계 결정을 가진 라이브러리가 많다는 사실 자체가 이 문제가 뚜렷한 정답이 있는 해결된 문제가 아니라는 신호임. TypeScript 초기 설계 때도 이를 알고 있었고, 이 원칙은 잘 버텨 왔다고 봄.
대신 TypeScript는 런타임 타입 검사 라이브러리가 실제로 하는 일을 타입으로 정확히 표현할 만큼 강력해졌고, 사용자는 API를 통해 타입에서 런타임 검증 로직을 구성할 수 있음. 이 정도가 합리적인 유연성으로 느껴짐- 프로그래밍을 시작한 지 얼마 안 됐는데, TypeScript가 왜 더 정교한 사용자 정의 데이터 타입을 만들 수 없는지 궁금함
예를 들어 특정 범위 안의 숫자나 우편번호 패턴을 만족하는 문자열을 타입으로 정의하고, 컴파일러가 일반 함수처럼 작성된 검증 함수를 사용해 유효성을 확인하는 언어를 상상함. 이런 기능이 없는 건 불필요한 복잡성을 피하려는 설계 결정인지, 성능 같은 기술적 제약 때문인지 궁금함 - TypeScript 컴파일러에 공식 전처리기 플러그인이 있으면 도움이 될 수 있음
지금도 필요한 사람은tsc에 넘기기 전에 타입 정보에서 런타임 객체를 생성하는 전처리기를 만들 수 있지만, 노력이 제각각 흩어져 있음. 공식 플러그형 전처리기와 생태계가 있으면 좋은 해법이 발견될 수 있음 - Microsoft가 TypeScript 플러그인이나 최상위 래퍼로 MacroScript를 호스팅하면 이 문제를 풀 수 있음
불씨만 붙이면 커뮤니티가 유지보수를 도울 것이고, 클라이언트 생성부터 런타임 타입 단언까지 여러 코드 생성 요구를 해결할 수 있음 - 컴파일 후 타입 정보를
Class객체에 남기자는 다른 제안에 대해서는 어떻게 생각하는지 궁금함
- 프로그래밍을 시작한 지 얼마 안 됐는데, TypeScript가 왜 더 정교한 사용자 정의 데이터 타입을 만들 수 없는지 궁금함
-
이걸 하지 않는 데에는 이유가 있음. TypeScript가 JavaScript 위의 런타임 같은 것이 되고, JS로 컴파일되는 새 언어가 되어 버림
지금 TypeScript는 타입 주석이 붙은 JavaScript에 가까움. JS로 컴파일되는 언어는 이미 많으니 그중 하나를 쓰면 됨. 런타임 타입이 있는 TypeScript를 원하는 사람들은 JavaScript보다 Java/OOP 스타일 코드를 쓰고 싶어 하는 것처럼 보이지만, JavaScript는 동적 타입 언어이고 그 점도 장점임- 꼭 그렇진 않음.
generateTypeInfo!()같은 매크로가Foo타입을 인코딩한 JS 객체로 확장되는 식이면 여전히 읽을 수 있는 JavaScript로 컴파일됨
다만 이 매크로는 “TS = 타입 주석이 붙은 JS이고 컴파일은 주석 제거만 하면 된다”는 성질을 깨뜨림.Foo의 구조적 타입을 실제로 계산해야 하기 때문임.
또 TypeScript 타입은 구조적이라 런타임에서 객체 구조를 검사하면 어느 정도 타입을 알 수 있지만, 문자열 유니언에 속하는지 같은 지워진 명목 정보나 타입 이름을 요구하면 TypeScript 타입 시스템의 튜링 완전성과 암묵적 구조 변환 때문에 금방 어려운 문제가 됨 - 둘 다 가질 수 있음. Lisp의 동형성처럼 코드와 데이터가 가까운 세계를 떠올릴 수 있음
진지하게 말하면, 런타임 없이도 타입을 데이터로 노출하면 반영을 얻을 수 있음. 많은 개발자가 이를 흉내 내려 애쓴 걸 보면, 안 하는 게 오히려 어리석어 보임 - 그 논리는 잘 이해가 안 됨. TypeScript는 이미 JS로 컴파일되는 새로운 상위집합 언어임
런타임 타입은 컴파일용 타입 시스템과 데이터 검증용 타입 시스템을 따로 중복 유지해야 하는 문제를 푸는 것이고, OOP와 직접 관련이 없어 보임. 이 문제의 우회 라이브러리로 선호하는io-ts도 함수형 프로그래밍 쪽에 강하게 기대고 있음 - 충분히 큰 프로젝트에서 동적 타입 언어는 좋지 않고,
NaN같은 예상치 못한 값에 계속 걸려 넘어지는 얽힌 재난에 가까움
내가 본 워크플로에서는 TypeScript가 이미 JS로 컴파일되는 언어이므로, 이왕이면 그 장점을 최대한 활용하는 편이 낫다고 봄 - TypeScript는 단순히 타입 주석이 붙은 JavaScript가 아님. 모드 중 하나일 수는 있지만, 오래된 JS 구조를 출력하는 기능도 있어 원본 TS 코드와 전혀 다르게 보일 수 있음
타입 주석 + Babel에 가깝고, 여기에 매우 유용한 기능 하나를 더하는 건 좋은 생각임.JSON.parse한 문자열을 타입 있는 구조로 안전하게 바꿀 수 없다는 건 이상하고, 다른 언어들은 대체로 가능함
- 꼭 그렇진 않음.
-
아래 내용보다 먼저, 이건 충분히 토론할 만한 유효한 주제이고 객관적으로 하나의 정답이 있다고 보진 않음
TypeScript는Enum이 객체를 내보내는 예외를 빼면 JavaScript 위의 선택적 계층이고, TS 코드는 타입만 지우면 JS가 됨. 이 원칙을 크게 벗어나지 않는다면, 실제 요구는 타입에서 직렬화기/검증기를 만드는 라이브러리에 가까움. 그런 라이브러리는 이미 많고, 결국 그중 하나를 공식 표준 선택지로 만들자는 요구처럼 보임.
개인적으로는 TypeScript의 핵심 언어에 런타임 반영이 들어오는 걸 원치 않음. 런타임에는 그냥 JavaScript만 있고, 소스맵이 없어도 출력 JS가 읽기 쉽고 디버깅 가능하다는 점을 소중하게 봄Enum말고도 런타임 코드를 내보내는 예외가 있고, 대부분 역시 실수로 여겨짐. 대표적으로module과namespace가 런타임 구조였음
다만 최근 몇 년은 주로 TypeScript 자체에서 쓰였고, TypeScript도 최근엔 거기서 벗어남. 또 꽤 인기 있는 예외로 매개변수 프로퍼티가 있는데, 생성자 매개변수에서 클래스 멤버 타입을 정의하는 문법이고 중복 보일러플레이트를 줄여서 논란을 덜 피한 듯함- “TS 코드는 변환 없이 JS가 된다”는 말은 언어 전체와 타입 표기를 그렇게 본다면 가능한 얘기임
-
TypeScript 신들에게 애원하기보다 JavaScript 쪽에 거래를 걸어서 타입 검사를 JS에 넣는 게 낫지 않을까 싶음
런타임 타입이 필요하면 타입 가드를 쓰고, 자주 필요하면io-ts나zod를 써서 타입을 검증기/코덱/스키마로 작성함. JavaScript에 합의된 방식이 생기기 전까지는 TS 명세, 타입 검사기, 커뮤니티가 런타임 검증을 떠안아야 한다고 보지 않음- 완벽하진 않아도
zod로 스키마와 검증기를 쓰고 거기서 타입을 파생하는 방식에 꽤 만족함. 검증이 타입 변화와 함께 진화한다는 점이 좋음
이런 기능이 언어에 들어가야 한다고 느끼는 사람도 있지만, 라이브러리마다 논쟁적인 설계 선택이 많아서 프로젝트 요구에 맞춰 구현을 고르는 편이 오히려 나을 수 있음.
다만zod런타임 검증과 스키마 기반 타입 파생 같은 패턴이ts-pattern의 패턴 매칭과 항상 잘 맞지는 않음. 이런 라이브러리들의 타입 정의는 미로 같고, 될 것 같은 코드가 기대대로 동작하지 않을 때가 있음. 런타임 안전성과 완전성 검사 패턴 매칭이 매끄럽게 결합된다면 매우 좋을 것 같음 - JavaScript에 정말 그런 기능이 들어가는 게 좋은지는 큰 의문임
Promise, 데코레이터, 새 파이프 연산자 모두 구현 방향이 잘못 갔다고 느껴짐
- 완벽하진 않아도
-
TypeScript 개발자 중 한 명이 다시 시작한다면
enum은 넣지 않았을 거라고 했던 기억이 있음.enum이 런타임 코드를 내보내는 유일한 기능이기 때문임
TypeScript는 런타임 동작을 바꾸지 않고, 특별한 TypeScript용{#if}같은 것도 없음- 그 말은 사실임. 그 이유가 유일한 이유였는지는 모르지만 분명 이유 중 하나였음
다만 TypeScript에는 “JavaScript + 타입 주석”을 넘어서는 기능이 몇 개 있음.namespace, 생성자 인자에서 클래스 프로퍼티를 정의하는 문법, 오래된 실험적 데코레이터, 컴파일 시 사라지지만 JS 함수에 타입 주석만 붙인 것 이상처럼 보이는this매개변수 문법 등이 있음
- 그 말은 사실임. 그 이유가 유일한 이유였는지는 모르지만 분명 이유 중 하나였음
-
더 나은 제목은 “TypeScript, 반영/런타임 타입을 제공해 달라”에 가까움
현재 최선의 해법은 아마emitDecoratorMetadata로 보임. https://www.typescriptlang.org/tsconfig#emitDecoratorMetadata -
글쓴이는 TypeScript의 설계 목표를 오해한 것 같음. 목표는 복잡성 없는 깨끗한 JS 출력을 만드는 게 아니라, TypeScript의 런타임 의미론이 JavaScript와 동일하게 유지되도록 하는 것임
아쉬운 예외인enum을 빼면 TypeScript는 타입 주석 제거만으로 JavaScript가 됨. TypeScript 주변 생태계는 완전한 타입 소거에 기대고 있으며, 이 요구가 받아들여지면 ESBuild, Deno, Bun의 TS 지원은 거의 불가능해질 수 있음. 각각tsc전체를 자기 언어로 다시 구현해야 하기 때문임.
반면 OP가 불평하는 복잡한 라이브러리들은 사용자 공간에 구현되어 있어서 이런 도구들과 호환됨- 런타임 없이도 정적으로 런타임 타입 정보를 내보낼 방법은 많음.
keyof를 클래스 키 목록으로 변환하거나, TS 클래스가 ES6 클래스처럼 키를undefined로 초기화하게 할 수도 있음
지금 TypeScript 클래스는 명시적으로 정의하지 않은 키를 모두 지워서, 새 인스턴스에Object.keys()를 호출하면 아무것도 나오지 않음.tsconfig.json옵션으로 TS 클래스를 ES6 클래스처럼 변환하거나 ES6 클래스를 TS 안에서 함께 허용하기만 해도 코드 생성이 훨씬 쉬워짐.
또한tstypeof Foo::bar같은 정적 문법이"string"으로 컴파일된다면 훌륭할 것임. 기본적인 RTTI만 있어도 지저분한 보일러플레이트 TypeScript 코드가 즉시 많이 사라질 수 있음
- 런타임 없이도 정적으로 런타임 타입 정보를 내보낼 방법은 많음.
-
이 글이 반가움. 2018년에 TypeScript를 시작했고 2년쯤 지나면서, 이것이 본질상 완전한 해법은 아니라고 믿기 시작했음
Haskell/C#/F#에서 왔는데, TS는 강력한 타입 시스템이 있어도 개발 중 일부를 제외하면 그런 언어들이 주는 장점을 많이 주지 못함. 현실 세계를 다룰 때는 C#보다 실용상 훨씬 제한적임. 컴파일 후 검사 없이 JS로 떨어진다는 점을 모르면, 정적 타입이라고 주장하는 도구에서는 불가능해야 할 버그를 피하기 위해 새는 추상화를 항상 의식해야 함- TypeScript의 목적은 항상 웹에서 실행되는 것이었고, C#보다 언어적 우위를 주려는 게 아니었음
.NET을 개발해야 하면 C#을 쓰고, 웹을 개발해야 하면 TypeScript를 쓰는 식임. C#은 명목 타입이고 꽤 괜찮은 실체화된 제네릭이 있으며, TypeScript는 구조적 타입이고 건전성을 포기한 대신 복잡한 타입 관계를 표현하는 강력한 타입 시스템을 가짐.
둘을 합친 언어가 좋을지는 의심스럽고, 두 언어는 서로 다른 방향으로 잘 작동하지만 그 방향은 서로 잘 맞지 않음
- TypeScript의 목적은 항상 웹에서 실행되는 것이었고, C#보다 언어적 우위를 주려는 게 아니었음
-
런타임 데이터 검증에는 https://zod.dev를 쓰는 데 완전히 만족했음
별도의 명목 타입을 따로 정의하지 않고, 유창한 API로 그 자리에서 의도를 표현할 수 있다는 점이 꽤 멋졌음- 명목 타입은 다른 용도가 있음
간단한 예로 도메인에서 숫자2와 통화 금액2를 구분하는 데 필요함
- 명목 타입은 다른 용도가 있음