decide(base): Modifier :Apply(factory) 팩토리 체이닝 추가

Compose 확장 함수 패턴을 콤비네이터로 흉내낸 얇은 sugar로 확정 —
modifier-plan.md 8번 절, ROADMAP.md M7 체크박스, CLAUDE.md 세션 요약 반영.
This commit is contained in:
qwreey 2026-08-07 11:52:39 +09:00
parent ec7469f74f
commit 7901ea96c4
Signed by: qwreey
GPG key ID: D28DB79297A214BD
3 changed files with 52 additions and 0 deletions

View file

@ -198,6 +198,38 @@ State에 담기면 그 값이 반응형으로 바뀔 수 있다는 뜻이 되어
아예 못 넣게 막을 것**(`State<Modifier>` 같은 조합을 타입 정의 단계에서 아예 못 넣게 막을 것**(`State<Modifier>` 같은 조합을 타입 정의 단계에서
거부) — 런타임 가드가 아니라 타입 차단을 우선 검토. 거부) — 런타임 가드가 아니라 타입 차단을 우선 검토.
### 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`에도 취합) ## 열린 질문 (`.claude/question.md`에도 취합)
- **[해소됨]** Getter 정확한 이름/모양 — 2026-08-06 후속 세션에서 getter - **[해소됨]** Getter 정확한 이름/모양 — 2026-08-06 후속 세션에서 getter

View file

@ -604,3 +604,21 @@ architecture.md` 소스트리 주석 두 곳 — `Store.luau`가 여전히 옛 `
다음 세션이 할 일은 안 바뀜(위 2026-08-06 네 번째 세션 절 참고) — 이 다음 세션이 할 일은 안 바뀜(위 2026-08-06 네 번째 세션 절 참고) — 이
결정은 M0 스파이크(Store/State propagation 검증)가 실제로 짜볼 때 결정은 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번 참고하면 됨.

View file

@ -80,6 +80,8 @@ Luau 코드로 부딪혀본 적 없는 세 가지**를 던지는 코드로 검
- [ ] flatten-before-dispatch, immutable `table.clone` 체이닝 - [ ] flatten-before-dispatch, immutable `table.clone` 체이닝
- [ ] `Modifier.Merge`(가칭) - [ ] `Modifier.Merge`(가칭)
- [ ] `State<Modifier>` 조합 타입 차단 확인(`modifier-plan.md` 7번, UB 확정) - [ ] `State<Modifier>` 조합 타입 차단 확인(`modifier-plan.md` 7번, UB 확정)
- [ ] `:Apply(factory)` 팩토리 함수 체이닝(`modifier-plan.md` 8번, 예약 키
`Apply`가 제네릭 `__index` 필드 setter와 안 겹치는지 확인)
## M8 — Ref ## M8 — Ref