diff --git a/.claude/base/slot-plan.md b/.claude/base/slot-plan.md index e322c4c..85f0fc7 100644 --- a/.claude/base/slot-plan.md +++ b/.claude/base/slot-plan.md @@ -367,6 +367,22 @@ Remove/Extract/Move하려 해도 참조를 안 들고 있는 경우가 잦음. ` 경계 원칙 그대로 재적용, 새 분리 아님). 제거된 구간이 뒤 요소를 당기고 삽입된 구간이 다시 밀어내는 게 순수하게 겹치면 상쇄되는 부분이 있어 `Extract 반복 + Add 반복`보다 실제 이동 계산량도 더 적음. + **`newElements`는 `Tag:Added`와 달리 의도적으로 vararg 유지, `T | {T}`로 + 안 바꿈(같은 세션 후속 논의).** `Tag:Added`가 `string | {string}`로 간 + 이유(조건절로 조립한 여러 동적 테이블을 한 호출로 합칠 수 없는 vararg의 + 구조적 한계, 위 `tag-plan.md` 참고)가 여기도 적용되는지 검토했으나 + 기각 — (1) 실사용 패턴이 다름: 한 번에 갈아끼우는 요소 개수는 호출부가 + 이미 아는 소수인 경우가 대부분이고, 정말 개수를 모르는 동적 삽입이면 + `Slot:Add`가 이미 받는 `State`/nested `Slot` 요소로 흡수 가능 + (Slot-in-Slot, 위 "반응형 raw 요소"/"Slot-in-Slot 중첩" 절) — `Splice` + 자체가 동적 배치를 떠받칠 이유가 없음. (2) **`T | {T}`가 여기선 오히려 + 틀림** — `Tag`의 `T`는 항상 plain `string`이라 `type(v) == "table"` + 분기가 "배열이냐 아니냐"를 안전하게 구분하지만, `Slot`의 `T`(`Instance + | Slot`)는 `Slot` 자신이 이미 테이블이라 "단일 `T`(마침 + 테이블인)"와 "`{T}` 배열"을 구분할 방법이 없음 — 억지로 하려면 + 결국 항상 명시적으로 감싸거나 풀어야 해서 vararg 대비 얻는 게 없음. + 이미 `{T}` 배열을 들고 있는 호출부는 `Splice(i, n, table.unpack(list))`로 + 충분(리스트가 하나뿐이라 tail-position 제약에도 안 걸림). - **`Get`/`IndexOf` 신설, 원래 "YAGNI"로 뺐던 것을 재추가.** 처음엔 "`:List`가 자기 key→element 맵을 따로 들고 있어 Slot 내부 상태 조회가 불필요"하다고 판단해 드롭했으나, 위 인덱스 기준 전환과 맞물려 다시 diff --git a/.claude/base/tag-plan.md b/.claude/base/tag-plan.md index 9ef7646..2d56252 100644 --- a/.claude/base/tag-plan.md +++ b/.claude/base/tag-plan.md @@ -40,10 +40,17 @@ API처럼 보이기 때문** — 실제로는 항상 `table.clone` 후 반환(Mo 이름 배열을 받고 내부에서 `type(v) == "table"`이면 순회(flatten)해서 처리.** 처음엔 이름 여러 개를 한 clone으로 처리하려고 vararg (`Added(name, ...)`)로 정정했으나, 사용자가 실사용 패턴을 지적하며 -재검토됨 — 조건절로 이름 목록을 동적으로 조립하는 경우(`if cond then -table.insert(names, "x") end`류)엔 결국 테이블에 모은 뒤 -`Added(table.unpack(names))`로 풀어야 해서 vararg가 오히려 더 번거로움. -반면 `string | {string}`은 그 테이블을 그대로 넘기면 끝 — 호출부가 +재검토됨 — 단순히 "번거로움" 정도가 아니라 **Lua 문법상 실제로 못 하는 +경우가 생김**: `table.unpack(t)`는 그 호출식이 인자 목록의 **맨 끝(tail) +위치일 때만** 여러 값으로 펼쳐지고, 그 뒤에 다른 인자가 오면(또는 다른 +`table.unpack` 호출이 뒤따르면) 첫 값 하나로 잘림 — 그래서 조건절로 여러 +개의 독립된 동적 이름 테이블을 만든 뒤(`namesA`, `namesB`, ...) 그걸 한 +`Added` 호출로 합쳐 넘기는 건 vararg로는 애초에 표현이 안 됨(마지막 +테이블만 완전히 펼쳐지고 나머지는 각각 첫 이름만 반영됨) — 결국 호출부가 +먼저 테이블 하나로 합친 뒤 `table.unpack`을 딱 한 번만 쓰거나, 아예 +테이블을 그대로 넘기게 해야 함. 반면 `string | {string}`은 그 테이블을 +그대로 넘기면 끝(여러 동적 테이블도 `table.move`/`table.insert`로 먼저 +합치기만 하면 그대로 통과) — 호출부가 단일 이름이든 이미 조립해둔 배열이든 분기 없이 통일해서 부를 수 있고, 구현도 `table.unpack` 없이 단순 `type(v) == "table"` 분기 후 `for` 순회만 있으면 됨. **부작용 걱정 없음** — 받는 값이 전부 이미 확정된 diff --git a/.claude/session/2026-08-12-15-slot-in-slot-relate-scope-tag-splice-additions.md b/.claude/session/2026-08-12-15-slot-in-slot-relate-scope-tag-splice-additions.md index 1a4e433..8c81693 100644 --- a/.claude/session/2026-08-12-15-slot-in-slot-relate-scope-tag-splice-additions.md +++ b/.claude/session/2026-08-12-15-slot-in-slot-relate-scope-tag-splice-additions.md @@ -42,3 +42,19 @@ CRUD 반복으로도 재현 가능한 결과, 비용만 다름). 비파괴(제거분은 파괴 안 하고 반환) — 실제 물리 detach/reattach는 기존 base/roblox 패키지 경계 그대로 backend Handler 몫(`slot-plan.md`). + +## 후속 — `Tag:Added` 재정 근거 보강, `Slot:Splice`는 vararg로 확정 + +`Tag:Added`가 `string | {string}`로 간 진짜 이유를 사용자가 더 정확히 +짚음: 단순 번거로움이 아니라 **Lua `table.unpack(t)`가 인자 목록의 tail +위치에 있을 때만 완전히 펼쳐지고, 그 뒤에 다른 인자/다른 `unpack`이 오면 +첫 값 하나로 잘리는 문법적 한계** — 조건절로 조립한 여러 개의 독립된 +동적 이름 테이블을 한 `Added` 호출로 합치는 게 vararg로는 애초에 표현이 +안 됨(`tag-plan.md`에 이 설명으로 보강). 이 논거가 `Slot:Splice`의 +`newElements`에도 적용되는지 검토했으나 **기각, vararg 유지** — (1) +한 번에 갈아끼우는 요소 수는 호출부가 아는 소수인 경우가 대부분이고 +정말 동적이면 Slot-in-Slot으로 흡수 가능해 `Splice` 자체가 그 케이스를 +떠받칠 이유가 없음, (2) `T | {T}`는 `Slot`의 `T`(`Instance | Slot`)가 +이미 테이블이라 "단일 T(마침 테이블)"와 "{T} 배열"을 구분 못 해 오히려 +틀림 — `Tag`의 `T=string`(항상 non-table)이라 안전했던 것과 다름 +(`slot-plan.md`에 반영). diff --git a/CLAUDE.md b/CLAUDE.md index 648ea22..045411e 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -634,10 +634,13 @@ vararg, `Slot:Splice` 신설** (`session/2026-08-12-15-slot-in-slot-relate-scope `slotOwner`/`kSlotMap` relate가 최상위 마운트에만 걸림, `Animate` 반환 타입이 `State|T>`, Slot retract는 전부 파괴·포탈 없음)는 문서와 일치해 확인만. 1개(`Tag:Added`/`:Removed`가 문서상 단일 `name`만 받던 것)는 -불일치 발견해 정정 — 처음엔 vararg로 갔다가, 조건절로 동적 조립한 이름 -목록은 `table.unpack`을 거쳐야 해서 오히려 번거롭다는 지적으로 같은 -세션 안에 `string | {string}`(내부 flatten)로 재수렴(self-return -최적화는 매번 멤버십을 먼저 읽어야 해서 기각, `tag-plan.md`). 추가로 -`Slot:Splice(index, +불일치 발견해 정정 — 처음엔 vararg로 갔다가, `table.unpack(t)`가 인자 +목록 tail 위치일 때만 완전히 펼쳐진다는 Lua 문법 한계(조건절로 조립한 +여러 동적 테이블은 한 vararg 호출로 못 합침) 때문에 같은 세션 안에 +`string | {string}`(내부 flatten)로 재수렴(self-return 최적화는 매번 +멤버십을 먼저 읽어야 해서 기각, `tag-plan.md`). 추가로 `Slot:Splice(index, removeCount, ...newElements)` CRUD 신설 — 구간 제거+삽입을 shift/recompute -1회로 묶는 순수 최적화(`slot-plan.md`). +1회로 묶는 순수 최적화. `newElements`는 `Tag:Added`와 달리 의도적으로 +vararg 유지(요소 개수가 대개 소수로 고정, 동적이면 Slot-in-Slot으로 흡수 +가능, `T|{T}`는 `Slot`의 `T` 자체가 테이블이라 오히려 모호해짐, +`slot-plan.md`).