Project Valhalla — Java 객체 모델과 제네릭의 진화
1. 개요
Project Valhalla는 OpenJDK가 추진하는 Java 언어 및 JVM 개선 프로젝트이다.
핵심 목표 “Java 클래스가 제공하는 객체지향적 추상화와, primitive type이 제공하는 메모리 효율 및 실행 성능을 함께 얻는 것”
공식 문서 : Project Valhalla
OpenJDK는 Valhalla의 방향을 다음과 같이 요약한다.
“Codes like a class, works like an int.” 클래스처럼 작성하되, int처럼 효율적으로 동작하게 한다.
- Primitive type: int, long, double, boolean 등 메모리 효율이 높고 계산에 적합하다. 객체가 아니므로 제네릭이나 객체지향 추상화에 직접 사용할 수 없다.
- Reference type: String, Integer, 사용자 정의 클래스 등 메서드, 인터페이스, 제네릭 등 객체지향 기능을 활용할 수 있다. 객체 할당, 참조, 객체 헤더, GC 등의 비용이 발생할 수 있다.
Valhalla는 이 두 세계의 단절을 완화하려는 프로젝트이다. 특히 대량의 숫자, 가격, 날짜, 색상, 벡터와 같이 작고 값 중심인 데이터를 다룰 때, 객체지향적인 코드 구조는 유지하면서도 불필요한 객체 비용을 줄이는 것을 목표로 한다.
주의: Valhalla는 여러 기능이 단계적으로 설계·도입되는 약 10년짜리 장기 프로젝트다.
ADTmag에 따르면 2027년 3월에 JDK28이 릴리즈되면서 Vahalla Project 가 포함되어 배포된다고 한다.
2.1 Primitive와 객체 타입의 분리
Java에서는 int와 Integer가 비슷한 목적을 갖지만, 언어와 JVM 차원에서는 다른 타입이다.
1
2
int quantity = 10;
Integer boxedQuantity = 10;
| 항목 | int | Integer |
|---|---|---|
| Primitive type인가 | 예 | 아니오 |
| 객체인가 | 아니오 | 예 |
| null 가능 여부 | 불가능 | 가능 |
| 제네릭 타입 인자로 사용 | 불가능 | 가능 |
| 메서드 및 객체 API | 제한적 | 가능 |
| 객체 할당 가능성 | 없음 | 있음 |
이 구분은 Java 초창기부터 존재해 왔다. Primitive는 단순한 데이터 처리에 효율적이지만, Java의 객체지향 기능은 참조 타입을 중심으로 설계되었다.
2.2 제네릭에서 발생하는 문제
현재 Java에서는 primitive를 제네릭 타입 인자로 직접 사용할 수 없다.
1
List<int> quantities; // 컴파일 불가
1
2
List<Integer> quantities = new ArrayList<>();
quantities.add(10);
이때 int 값은 상황에 따라 Integer 객체로 박싱 가능
1
2
int quantity = 10;
Integer boxed = quantity; //boxing
반대로 Integer를 다시 int로 꺼내는 언박싱도 가능
1
2
Integer boxed = 10;
int quantity = boxed; //unboxing
컬렉션에 많은 primitive 데이터를 장기간 저장할 경우, 이 구조는 다음과 같은 비용으로 이어질 수 있다.
- wrapper 객체 사용
- 객체 헤더 및 객체 관리 비용
- 실제 값 접근 시 간접 참조
- 많은 객체에 따른 GC 부담
- 데이터가 메모리에 흩어지는 현상
- CPU 캐시 효율 저하 가능성
다만 JVM은 escape analysis, wrapper caching 등의 최적화를 수행할 수 있다. 따라서 모든 boxing이 항상 새 객체 할당으로 이어지는 것은 아니다. 그러나 값이 컬렉션 내부에 장기간 보관되는 경우에는 Object[] 및 참조 중심 구조라는 한계가 본질적으로 남는다.
Valhalla는 단일 기능이 아니라, Java의 객체 모델과 타입 시스템을 점진적으로 확장하는 프로젝트다.
| 개선 영역 | 해결하려는 문제 | 핵심 방향 | 예시 |
|---|---|---|---|
| Value Objects | 모든 객체가 identity를 가져야 하는 비용 | 값 중심 데이터에 identity 없는 객체 모델 제공 | Money(5000, “KRW”)처럼 “어느 객체인가”보다 값이 중요한 데이터 |
| Flattening | 객체 참조와 별도 객체로 인한 메모리 분산 | 데이터를 배열·필드에 직접 저장할 수 있는 기반 마련 | 기존: [Money 참조] → Money 객체 / 지향: [5000][12000][8000] |
| Null-restricted storage | null 가능성이 직접 저장을 어렵게 함 | null을 제한하는 저장 모델 검토 | 가격 목록에 가격 또는 null 대신 항상 유효한 가격만 저장 |
| Primitive/Object 간극 축소 | int와 Integer의 분리 | primitive 효율성과 클래스 추상화의 결합 | int를 컬렉션에 넣기 위해 Integer로 boxing해야 하는 문제 |
| Universal Generics | primitive를 제네릭에 직접 사용할 수 없음 | 제네릭이 모든 타입을 다루는 방향 | 현재: List |
| Specialized Generics | 제네릭의 Object 중심 런타임 표현 | 타입별 데이터 배치·호출 규약 전문화 | List |
| Safe/Strict Initialization | 초기화되지 않은 값의 노출 위험 | 값 중심 객체와 직접 저장을 위한 초기화 규칙 강화 | 생성 중인 객체의 일부 필드만 설정된 상태가 외부에 노출되지 않도록 제한 |
3.1 Value Objects
Java의 일반 객체는 기본적으로 identity object다. 즉, 값이 동일하더라도 서로 다른 객체라면 구별될 수 있다.
반면 가격, 날짜, 색상, 통화 금액 등은 대체로 “어느 객체인가”보다 “어떤 값을 가지는가”가 중요하다.
1
2
3
4
record Money(
long amount,
String currency
) { }
Money(5000, “KRW”)라는 데이터는 대부분의 상황에서 객체가 생성된 위치나 메모리 주소보다, 금액과 통화라는 상태가 중요하다.
Valhalla의 value object 모델은 이러한 값 중심 데이터를 대상으로 한다. identity가 필요하지 않다면 JVM은 해당 값을 더 자유롭게 복사, 분해, 전달, 저장할 수 있다. 이를 통해 객체 identity를 유지하기 위해 필요한 일부 제약을 제거하고, 보다 효율적인 데이터 표현의 기반을 제공한다.
현재 Value Objects는 JEP 401문서 중심으로 논의·도입되고 있다. JEP 401은 value class와 value object를 Java 객체 모델에 도입하여, identity 없는 객체를 표현하는 것을 목표로 한다.
(이걸 해결하려고 일단 공개된건 Fattening(평탄화) 작업으로 해결한다고 한다.)
3.2 Flattening
일반적인 객체 컬렉션은 개념적으로 다음과 같이 저장될 수 있다.
1
2
3
[객체 참조] [객체 참조] [객체 참조] ...
↓ ↓ ↓
객체 A 객체 B 객체 C
이 구조에서는 배열 자체에는 데이터가 아니라 객체를 가리키는 참조가 들어가며, 각 객체는 별도의 메모리 영역에 존재할 수 있다.
Valhalla는 identity가 없는 값 중심 데이터를 대상으로, 가능하다면 값을 더 직접적이고 밀도 높게 저장하는 방향을 지향한다.
1
2
3
4
5
기존 참조 중심 구조
[참조] [참조] [참조] ...
값 직접 저장을 지향하는 구조
[값] [값] [값] ...
이러한 데이터 배치 최적화를 일반적으로 flattening 또는 inline/flat layout이라고 부른다.
Flattening이 가능해질 경우 다음과 같은 효과를 기대할 수 있다.
- 객체 헤더 및 메타데이터 비용 감소 가능성
- 데이터 지역성(locality) 향상
- CPU 캐시 활용 개선 가능성
- 대량 객체에 대한 GC 부담 완화 가능성
다만 실제 flattening 가능 여부는 타입 특성, null 허용 여부, JVM 구현 및 저장 위치 등에 따라 달라질 수 있다. 즉, value object를 선언한다고 해서 모든 상황에서 항상 동일한 메모리 최적화가 보장되는 것은 아니다..
이건 정말 내부적인 알고리즘으로 해결할듯 하다. 어떤식으로 개선했는지 알려는 주겠지만, 내부 구현은 안보여주겠지.. 보여준다고 해도 내가 이해 할수있을려나..
3.3 Null-restricted Storage
1
String title = null;
하지만 일부 도메인 데이터에서는 null이 허용되지 않는 것이 더 자연스럽다. 예를 들어 가격 목록의 모든 항목이 반드시 유효한 가격이어야 한다면, 요소가 null일 수 있는 구조는 불필요한 상태를 추가한다. (우리는 보통 null 이면 안되는 구조는 대부분 Business Service 로직으로 풀어간다.)
null 가능성은 JVM이 값을 직접 저장하는 데도 영향을 준다. 어떤 위치에 값 또는 null이 들어갈 수 있다면, JVM은 그 상태를 표현하기 위한 별도 처리나 표현 공간을 고려해야 할 수 있다. (여기서도 비용발생)
Valhalla의 설계에서는 값 중심 타입을 더 효율적으로 저장하기 위해 null-restricted storage, 즉 null을 제한하는 저장 모델이 중요한 요소로 다뤄진다.
1
2
3
4
5
nullable storage
→ 값 또는 null
null-restricted storage
→ 항상 유효한 값
이 특성은 flattening 및 고밀도 데이터 배치의 기반이 될 수 있다. 해결된다면 메모리 확보하는 측면에서 이점을 확실하게 볼수있을것 같긴하다.
3.4 Strict Field Initialization
값 중심 객체는 생성 중간 상태가 외부로 노출되지 않아야 한다. 특히 객체가 identity 없이 복사·분해·직접 저장될 수 있는 모델에서는, 모든 필드가 올바르게 초기화되었는지가 더 중요하다.
JEP 539: Strict Field Initialization은 필드 초기화 안전성을 강화하기 위한 작업이다. 이 기능은 값 객체뿐 아니라 일반적인 객체 초기화 안정성과도 관련되며, Valhalla가 요구하는 더 강한 객체 모델의 기반으로 볼 수 있다.
일반적인 객체 생성 과정
1
2
3
4
5
6
7
8
9
class Order {
private final String orderId;
private final long amount;
Order(String orderId, long amount) {
this.orderId = orderId;
this.amount = amount;
}
}
- Order 객체를 위한 공간 확보
- orderId 필드 초기화
- amount 필드 초기화
- 완성된 Order 객체 사용 시작
정상적인 상황이라면 5번 이후에 객체를 사용하므로 문제없음
문제가 되는 경우: 생성자 안에서 this가 외부로 나감
1
2
3
4
5
6
7
8
9
10
class Order {
private final String orderId;
private final long amount;
Order(String orderId, long amount) {
OrderRegistry.register(this); // 아직 객체 생성 중
this.orderId = orderId;
this.amount = amount;
}
}
여기서 OrderRegistry.register(this)는 생성 중인 Order 객체를 외부에 전달합니다.
그런데 이 시점에는 아직 아래 코드가 실행되지 않았습니다.
1
2
this.orderId = orderId;
this.amount = amount;
즉 외부 코드가 객체를 받았을 때, 객체는 이런 상태일 수 있습니다.
1
2
3
4
Order 객체 {
orderId = null
amount = 0
}
Java에서 참조 타입 필드의 초기값은 null, 숫자 필드의 초기값은 0입니다.
따라서 다른 코드나 다른 스레드가 이 객체를 너무 일찍 사용하면, 생성자가 끝난 뒤에는 절대 나와서는 안 될 중간 상태를 볼 수 있습니다.
요거를 JDK28에서 컴파일 에러로 잡겠다!!
- Java 제네릭의 기존 구조: Type Erasure
4.1 Type Erasure란 무엇인가
Java 제네릭은 Java 5에서 도입되었으며, 기존 Java 코드와의 호환성을 위해 주로 type erasure 기반으로 설계되었다.
1
2
3
4
5
6
7
8
9
10
11
class Container<T> {
private T value;
void set(T value) {
this.value = value;
}
T get() {
return value;
}
}
소스 코드 수준에서는 T가 String, Integer 등 여러 타입이 될 수 있다.
1
2
Container<String> name = new Container<>();
Container<Integer> quantity = new Container<>();
그러나 런타임의 일반적인 모델에서는 타입 인자 정보가 지워지거나 상한 타입으로 처리된다. T에 명시적인 상한이 없다면 일반적으로 Object 중심으로 이해할 수 있다.
1
2
3
4
5
6
7
8
9
10
11
class Container {
private Object value;
void set(Object value) {
this.value = value;
}
Object get() {
return value;
}
}
실제 컴파일 결과에는 타입 캐스팅, bridge method 등 추가 요소가 포함될 수 있으므로 위 코드는 개념적 설명이다. 핵심은 Java의 기존 제네릭이 동일한 런타임 표현을 공유하는 homogeneous translation, 즉 type erasure 기반이라는 점이다.
4.2 Type Erasure와 Primitive의 충돌
Type erasure 모델에서 제네릭 타입 변수는 Object 또는 참조 타입의 상한으로 다뤄진다. 그러나 primitive인 int, long, double은 Object의 하위 타입이 아니다.
1
Object value = 10; // 실제로는 Integer boxing이 필요
1
List<int> quantities; // 현재 Java에서는 불가
1
List<Integer> quantities = new ArrayList<>();
이 문제는 단순히 문법상 int를 막아 놓은 것이 아니다. Java 제네릭의 타입 시스템, bytecode 표현, 런타임 호환성, 라이브러리 구현이 오랫동안 참조 타입과 type erasure를 전제로 만들어졌기 때문이다.
OpenJDK의 Valhalla 설계 문서는 이러한 문제를 primitive와 객체 타입 사이의 type rift, 즉 타입 체계의 단절로 설명한다.
5.1 개념
Universal Generics는 제네릭 타입 변수가 참조 타입에만 한정되지 않고, primitive와 값 타입까지 다룰 수 있게 하려는 방향이다.
1
2
List<Integer> quantities;
List<Long> timestamps;
1
2
List<int> quantities;
List<long> timestamps;
1
2
3
4
5
기존 제네릭
T = 참조 타입 중심
Universal Generics
T = 참조 타입 + primitive + value object 등
OpenJDK의 Valhalla 설계 문서는 이를 제네릭의 첫 번째 진화 단계로 설명한다. 즉, 먼저 타입 시스템 차원에서 제네릭이 더 넓은 범위의 타입을 수용하게 하고, 이후 실행 및 저장 구조의 전문화를 추구하는 단계적 접근이다.
5.2 Universal Generics가 해결하는 문제
Universal Generics는 다음 문제를 완화하려는 방향이다.
1. Primitive 제네릭 사용 제약
1
List<int> quantities;
2. API 분열
현재 Java에는 primitive 성능을 위해 별도 API가 존재한다.
1
2
3
4
Stream<Integer>
IntStream
LongStream
DoubleStream
1
2
3
4
5
6
7
8
Predicate<T>
IntPredicate
LongPredicate
DoublePredicate
Function<T, R>
ToIntFunction<T>
IntFunction<R>
이러한 분리는 primitive boxing 비용을 줄이기 위해 필요했지만, API 수와 학습 부담을 증가시킨다. (알야될게 많으니까.. 사실 다 컨벤션이긴 한데 까먹으면 뭐 말짱 도루묵)
Universal Generics는 장기적으로 일반 제네릭 API가 primitive까지 포괄할 수 있는 기반을 제공한다.
3. 값 타입의 재사용성
값 중심 타입이 제네릭 API에 자연스럽게 참여할 수 있다면, 특정 타입마다 별도의 컨테이너나 전용 API를 만들 필요가 줄어든다.
5.3 Universal Generics만으로는 성능 문제가 해결되지 않는다
1
List<int> quantities;
1
2
3
int 값
→ wrapper 객체로 변환
→ Object[]에 객체 참조로 저장
즉, 문법은 List
따라서 Universal Generics는 중요하지만, 그 자체로 최종 목적지는 아니다.
Universal Generics는 “어떤 타입을 제네릭에 넣을 수 있는가”를 확장하는 단계다. Specialized Generics는 “그 타입을 런타임에서 어떻게 효율적으로 저장하고 실행할 것인가”를 다루는 단계다.
- Specialized Generics와 Parametric VM
6.1 Specialized Generics의 필요성
Specialized Generics는 제네릭 타입 인자에 맞춰, JVM이 저장 방식과 실행 방식을 전문화할 수 있게 하려는 방향이다.
예를 들어 다음 세 가지 컨테이너는 동일한 제네릭 API를 사용할 수 있다.
1
2
3
List<int> quantities;
List<long> timestamps;
List<String> names;
| 타입 | 자연스러운 저장 방식 |
|---|---|
| int | 고정 크기 primitive 값 |
| long | 고정 크기 primitive 값 |
| String | 객체 참조 |
| 값 중심 도메인 타입 | 조건에 따라 직접 저장 가능 |
기존 erased generics는 다양한 타입을 하나의 Object 중심 표현으로 통합한다. 이는 코드 공유와 호환성에는 유리하지만, primitive 또는 flat value의 효율적인 표현과 충돌한다.
Specialized Generics는 다음과 같은 방향을 목표로 한다.
1
2
3
4
5
6
7
8
List<int>
→ 연속된 int 값에 가까운 저장 구조
List<long>
→ 연속된 long 값에 가까운 저장 구조
List<String>
→ String 객체 참조 중심 저장 구조
즉, 동일한 제네릭 API를 유지하면서도 타입 특성에 따라 더 적절한 내부 표현을 사용할 수 있도록 하는 것이다.
지금 전부 내부적으로 Object로 표현하는걸 각자의 특성에 맞게 잘분리한다…라… 이게 참 어려울거같은데
6.2 Parametric VM
OpenJDK는 Specialized Generics와 관련된 장기적 JVM 설계를 Parametric VM이라는 이름으로 논의한다.
Parametric VM은 단순히 Java 컴파일러가 모든 타입 조합에 대해 별도 클래스를 생성하는 방식이 아니라, JVM 차원에서 제네릭의 전문화 가능성을 다루려는 설계다.
- 모든 타입 인자 조합에 대해 별도 클래스 파일을 만들면 코드와 메타데이터가 폭증할 수 있다.
- Java의 wildcard, raw type, 기존 바이너리 호환성은 단순한 템플릿 방식과 충돌할 수 있다. 일부로 어떤건지 모르겠는데 선언해둔거 List<?> 이런애들을 새로 정의를 해버리는게 무슨말이지..? 모순이 되는거임
- specialization은 JVM의 런타임 정보와 JIT 최적화에 따라 유연하게 적용될 필요가 있다. JIT 컴파일러의 장점 : 나는 필요할때만 최적화해서 컴파일 할꺼야. 근데 Generic으로 다 선언해버린다구? 이상함
OpenJDK의 Parametric VM 설계 문서는 specialization을 classfile, constant pool, 동적 연결 구조와 연계하여 설명한다. 특히 기존 erased code와 specialized code가 함께 동작할 수 있어야 한다는 호환성 요구를 강조한다.
6.3 Erasure를 완전히 버리지 않는 이유
Java 제네릭의 한계를 설명할 때 “type erasure는 나쁜 설계”라고 단정하기 쉽다. 그러나 type erasure는 다음과 같은 장점을 제공해 왔다.
- Java 5 이전 라이브러리와의 호환성
- 기존 bytecode 및 JVM 생태계와의 연속성
- generic code의 코드 중복 감소
- raw type 및 오래된 라이브러리와의 상호 운용성
Valhalla의 방향은 단순히 erasure를 폐기하는 것이 아니다. OpenJDK의 설계 문서인 In Defense of Erasure는 Java 생태계의 점진적 마이그레이션과 호환성을 매우 중요한 설계 목표로 제시한다. [8]
따라서 Valhalla의 제네릭 진화는 다음과 같이 이해하는 것이 적절하다.
1
2
3
4
5
기존 erased generics의 호환성 유지
+
primitive/value type을 위한 확장
+
필요한 경우 JVM 수준의 전문화
- Universal Generics와 Specialized Generics 비교
| 구분 | Universal Generics | Specialized Generics |
|---|---|---|
| 주요 관심사 | 타입 시스템과 언어 표현 | JVM 실행 및 메모리 표현 |
| 핵심 질문 | primitive를 제네릭에 넣을 수 있는가? | primitive를 boxing 없이 효율적으로 처리할 수 있는가? |
| 대표 예시 | List | List |
| 기존 제네릭과의 관계 | 타입 인자의 범위 확장 | 저장 구조·호출 규약의 전문화 |
| 성능 효과 | 직접적 성능 개선이 보장되지는 않음 | boxing, 참조, 데이터 배치 비용 완화 가능 |
| 구현 난이도 | 언어 규칙·타입 시스템 변화 | JVM, classfile, linkage, JIT, 호환성까지 포함 |
1
2
3
4
5
Universal Generics
= 제네릭이 모든 타입을 수용하도록 만드는 것
Specialized Generics
= 수용한 타입을 실제로 효율적으로 실행·저장하도록 만드는 것
8.1 메모리 효율 개선 가능성
대량의 primitive 데이터를 List
1
2
3
4
5
6
7
기존 참조 중심 구조
[참조] [참조] [참조] ...
↓ ↓ ↓
wrapper wrapper wrapper
전문화된 primitive 중심 구조
[10] [20] [30] [40] ...
8.2 GC 부담 완화 가능성
boxing으로 생성되거나 유지되는 wrapper 객체 수가 줄어들면, GC가 추적하고 회수해야 하는 객체 수가 감소할 수 있다.
8.3 CPU 캐시 효율 개선 가능성
연속된 메모리 영역에 데이터를 저장하면 CPU 캐시 활용 측면에서 유리할 수 있다. 반대로 객체 참조를 따라 여러 위치에 흩어진 데이터를 읽는 방식은 cache miss를 증가시킬 수 있다.
8.4 API 단순화 가능성
현재의 IntStream, LongStream, DoubleStream 및 여러 primitive 전용 함수형 인터페이스는 primitive 제네릭 부재에 따른 API 분열의 결과다.
Valhalla의 제네릭 확장은 장기적으로 이러한 분열을 줄일 수 있는 기반이 될 수 있다. 다만 기존 API가 즉시 제거되거나 단일 API로 통합된다는 뜻은 아니다.
9.1 모든 코드가 자동으로 빨라지는 것은 아니다
Valhalla의 목표는 효율적인 표현을 가능하게 하는 것이다. 실제 성능은 다음 요소에 따라 달라진다.
- JIT 컴파일러의 최적화 판단
- 애플리케이션의 실제 병목 지점
따라서 “Valhalla를 사용하면 무조건 몇 배 빨라진다”와 같은 단정은 적절하지 않다.
9.2 기존 코드와의 호환성이 핵심 과제다
1
2
3
4
5
List<String>
List<Object>
List<?>
List<? extends Number>
List rawList
또한 reflection, serialization, class loading, bytecode, 라이브러리 ABI 등과의 호환성도 유지해야 한다.
Valhalla의 어려움은 단순히 List
Project Valhalla는 Java의 객체 모델을 현대화하고, primitive와 객체 타입 사이의 오랜 단절을 줄이려는 프로젝트다.
- Value Objects identity보다 값이 중요한 데이터를 위한 객체 모델
- Flattening 및 null-restricted storage 값을 더 직접적이고 효율적으로 저장하기 위한 기반
- Primitive와 객체 타입의 간극 축소 boxing과 참조 중심 표현의 한계 완화
- Universal Generics primitive와 value type도 제네릭 타입 인자로 수용하는 방향
- Specialized Generics / Parametric VM 제네릭 타입에 따라 저장 구조와 실행 방식을 전문화하는 장기 방향
제네릭 관점에서 Valhalla의 핵심은 다음 문장으로 요약할 수 있다.
현재 Java 제네릭은 Object 중심의 type erasure 모델 때문에 primitive를 직접 다루기 어렵다. Valhalla는 Universal Generics로 타입 시스템의 범위를 넓히고, Specialized Generics로 그 확장을 실제 메모리 및 실행 성능 개선과 연결하려 한다.
따라서 List
| 문서 | 설명 |
|---|---|
| OpenJDK Project Valhalla | Project Valhalla 공식 프로젝트 페이지 |
| JEP 401: Value Objects | Value object 및 value class 관련 JEP |
| Project Valhalla — Value Objects | Value objects 공식 설명 |
| State of Valhalla, Part 1: The Road to Valhalla | Valhalla의 문제 정의, primitive/reference 간극, 단계적 설계 |
| JEP 539: Strict Field Initialization | 엄격한 필드 초기화 규칙 |
| JEP Draft 8261529: Universal Generics | Universal Generics 초기 JEP 초안 |
| The Saga of the Parametric VM | Specialized Generics 및 Parametric VM 설계 노트 |
| In Defense of Erasure | Type erasure를 유지하며 진화해야 하는 이유 |
| State of Valhalla, Part 2: The Language Model | identity, value, reference 모델에 대한 설계 문서 |
