diff --git a/.claude/base/modifier-plan.md b/.claude/base/modifier-plan.md index 3e42e78..401c772 100644 --- a/.claude/base/modifier-plan.md +++ b/.claude/base/modifier-plan.md @@ -198,6 +198,38 @@ State에 담기면 그 값이 반응형으로 바뀔 수 있다는 뜻이 되어 아예 못 넣게 막을 것**(`State` 같은 조합을 타입 정의 단계에서 거부) — 런타임 가드가 아니라 타입 차단을 우선 검토. +### 8. `:Apply(factory)` — 팩토리 함수 체이닝 지원 (2026-08-07) + +**동기**: 재사용 가능한 스타일 프리셋을 만들고 싶을 때(예: `Boldify(mod) +-> mod`처럼 어떤 modifier든 받아 기본값보다 더 두껍게 만들어 돌려주는 +함수, 커링해서 `Boldify(10)(mod) -> mod`처럼도 씀) 이런 "modifier +팩토리"를 체이닝에 자연스럽게 끼워 넣을 방법이 없었음 — 팩토리를 직접 +호출하면 `Italicify(Boldify(10)(mod:FontSize(14)))`처럼 안에서 밖으로 +쌓여 읽는 순서가 실행 순서와 반대로 뒤집힘. + +**결정**: `mod:Apply(factory)`를 지원 — `factory`는 그냥 `Modifier -> +Modifier` 평범한 함수(커링된 클로저 포함, 새 타입 개념 아님). 동작은 +`function(self, factory) return factory(self) end`이 전부. 이걸로 +`mod:FontSize(14):Apply(Boldify(10)):Apply(Italicify)`처럼 필드 +setter 체이닝과 팩토리 적용을 같은 fluent 문법 하나로 섞어 쓸 수 +있음 — 읽는 순서 = 적용 순서. + +**왜 좋은 아이디어인가**: Jetpack Compose의 커스텀 `Modifier` 확장 함수 +패턴(`fun Modifier.myStyle(): Modifier = this.then(...)`)과 동일한 +효용(모듈화된 스타일 프리셋을 라이브러리로 나눠 배포/재사용, 체이닝으로 +조합)을 Luau엔 확장 함수 문법이 없으니 `:Apply` 콤비네이터로 흉내낸 것. +새 개념을 추가하는 게 아니라 "펑션도 그냥 값"이라는 Lua 특성과 이미 있는 +immutable clone 체이닝(3번)에 얹는 얇은 sugar라 구현/개념 비용이 거의 +없음 — 팩토리 자신이 내부에서 이미 `:FontSize(...)` 같은 필드 setter를 +호출해 clone된 새 Modifier를 반환하므로, `Apply` 자체는 clone할 필요조차 +없음(`factory(self)`가 이미 새 값을 만들어 줌). + +**구현 시 주의**: `Apply`는 제네릭 `__index`가 즉석에서 만들어주는 필드 +setter 클로저(4번)와 이름이 겹치면 안 됨 — `__index`가 고정 메소드 +테이블(현재는 `Apply` 하나)을 먼저 확인하고, 없을 때만 필드 setter를 +합성하도록 구현. 따라서 **`Apply`는 Modifier 필드 이름으로 예약됨**(실제 +스타일 프로퍼티 이름과 겹칠 일은 거의 없어 보이지만 문서화 필요). + ## 열린 질문 (`.claude/question.md`에도 취합) - **[해소됨]** Getter 정확한 이름/모양 — 2026-08-06 후속 세션에서 getter diff --git a/CLAUDE.md b/CLAUDE.md index 4c9c69c..91fda0b 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -604,3 +604,21 @@ architecture.md` 소스트리 주석 두 곳 — `Store.luau`가 여전히 옛 ` 다음 세션이 할 일은 안 바뀜(위 2026-08-06 네 번째 세션 절 참고) — 이 결정은 M0 스파이크(Store/State propagation 검증)가 실제로 짜볼 때 참고할 구체 스펙이 하나 더 생긴 것뿐. + +## 2026-08-07 두 번째 세션 — Modifier `:Apply(factory)` 팩토리 체이닝 추가 + +사용자 제안: `Boldify(mod) -> mod`처럼 어떤 modifier든 받아 적절히 변형해 +돌려주는 재사용 가능한 "팩토리 함수"(커링 지원, `Boldify(10)(mod) -> mod`)를 +`mod:Apply(Boldify(10)):Apply(Italicify)`처럼 기존 필드 setter 체이닝과 +같은 fluent 문법으로 끼워 넣을 수 있게 하자는 것 — Jetpack Compose의 커스텀 +`Modifier` 확장 함수 패턴과 같은 효용(모듈화된 스타일 프리셋 재사용)을 +Luau엔 확장 함수 문법이 없으니 콤비네이터로 흉내낸 아이디어. 채택 확정, +`base/modifier-plan.md` 8번 절에 반영 — `:Apply`는 `function(self, factory) +return factory(self) end`이 전부인 얇은 sugar(팩토리 자신이 이미 clone된 +새 Modifier를 반환하므로 Apply 자체는 clone 불필요), 기존 3번(immutable +clone 체이닝)/4번(제네릭 `__index`) 결정 위에 그대로 얹힘. 구현 시 주의점 +하나만 새로 생김: `Apply`는 제네릭 `__index`가 필드 setter를 즉석 합성하기 +전에 먼저 확인해야 하는 고정 메소드 이름이라, **Modifier 필드 이름으로는 +예약됨**(실 스타일 프로퍼티와 겹칠 일은 거의 없어 보이나 문서화 필요). +`ROADMAP.md` M7에 체크박스 추가 완료. 다음 세션이 새로 알아야 할 건 없음 — +M7 착수 시 `modifier-plan.md` 8번 참고하면 됨. diff --git a/ROADMAP.md b/ROADMAP.md index a794898..30900ab 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -80,6 +80,8 @@ Luau 코드로 부딪혀본 적 없는 세 가지**를 던지는 코드로 검 - [ ] flatten-before-dispatch, immutable `table.clone` 체이닝 - [ ] `Modifier.Merge`(가칭) - [ ] `State` 조합 타입 차단 확인(`modifier-plan.md` 7번, UB 확정) +- [ ] `:Apply(factory)` 팩토리 함수 체이닝(`modifier-plan.md` 8번, 예약 키 + `Apply`가 제네릭 `__index` 필드 setter와 안 겹치는지 확인) ## M8 — Ref