decide(tag,slot): Splice는 vararg 유지, Tag:Added 재정 근거 보강

Tag:Added가 string|{string}로 간 진짜 이유를 Lua table.unpack의 tail
위치 제약(뒤에 다른 인자/unpack이 오면 첫 값 하나로 잘림)으로 정확히
보강. 같은 논거가 Slot:Splice의 newElements에도 적용되는지 검토했으나
기각 — 사용 패턴이 소수 고정이고 동적이면 Slot-in-Slot으로 흡수 가능,
게다가 Slot의 T 자체가 테이블이라 T|{T}가 오히려 모호해짐 — vararg 유지로 확정.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
qwreey 2026-08-12 17:10:00 +09:00
parent fda045a183
commit 35306ed5ca
Signed by: qwreey
GPG key ID: D28DB79297A214BD
4 changed files with 52 additions and 10 deletions

View file

@ -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<T>`/nested `Slot` 요소로 흡수 가능
(Slot-in-Slot, 위 "반응형 raw 요소"/"Slot-in-Slot 중첩" 절) — `Splice`
자체가 동적 배치를 떠받칠 이유가 없음. (2) **`T | {T}`가 여기선 오히려
틀림** — `Tag``T`는 항상 plain `string`이라 `type(v) == "table"`
분기가 "배열이냐 아니냐"를 안전하게 구분하지만, `Slot``T`(`Instance
| Slot<Instance>`)는 `Slot` 자신이 이미 테이블이라 "단일 `T`(마침
테이블인)"와 "`{T}` 배열"을 구분할 방법이 없음 — 억지로 하려면
결국 항상 명시적으로 감싸거나 풀어야 해서 vararg 대비 얻는 게 없음.
이미 `{T}` 배열을 들고 있는 호출부는 `Splice(i, n, table.unpack(list))`
충분(리스트가 하나뿐이라 tail-position 제약에도 안 걸림).
- **`Get`/`IndexOf` 신설, 원래 "YAGNI"로 뺐던 것을 재추가.** 처음엔
"`:List`가 자기 key→element 맵을 따로 들고 있어 Slot 내부 상태 조회가
불필요"하다고 판단해 드롭했으나, 위 인덱스 기준 전환과 맞물려 다시

View file

@ -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`
순회만 있으면 됨. **부작용 걱정 없음** — 받는 값이 전부 이미 확정된

View file

@ -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`에 반영).

View file

@ -634,10 +634,13 @@ vararg, `Slot:Splice` 신설** (`session/2026-08-12-15-slot-in-slot-relate-scope
`slotOwner`/`kSlotMap` relate가 최상위 마운트에만 걸림, `Animate` 반환
타입이 `State<Tween<T>|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`).