From fda045a1833554252824a4b17dafd8a9129ebc8b Mon Sep 17 00:00:00 2001 From: qwreey Date: Wed, 12 Aug 2026 16:59:53 +0900 Subject: [PATCH] =?UTF-8?q?decide(tag):=20Added/Removed=20vararg=20->=20st?= =?UTF-8?q?ring|{string}=20=EC=9E=AC=EC=A0=95=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 동적으로 조립한 이름 목록을 vararg에 넘기려면 table.unpack이 필요해 번거롭다는 지적 — string 단일 이름 또는 {string} 배열을 받아 내부에서 flatten하는 쪽으로 같은 세션 내 재수렴. Tag(...) 생성자는 정적 호출 자리라 vararg 유지. Co-Authored-By: Claude Sonnet 5 --- .claude/base/tag-plan.md | 41 +++++++++++-------- ...-slot-relate-scope-tag-splice-additions.md | 17 +++++--- CLAUDE.md | 7 +++- 3 files changed, 39 insertions(+), 26 deletions(-) diff --git a/.claude/base/tag-plan.md b/.claude/base/tag-plan.md index f6a1ded..9ef7646 100644 --- a/.claude/base/tag-plan.md +++ b/.claude/base/tag-plan.md @@ -5,8 +5,8 @@ 2026-08-12 열한 번째 세션에 `TagHandler`의 `process`/`retract` 메커니즘을 참조 카운트 기반으로 전면 정정(옛 버전은 `archive/ retract-always-fires-reversed.md`). 2026-08-12 열다섯 번째 세션에 -`Added`/`Removed`를 단일 이름에서 vararg로 정정(아래 값 모양 절). 새 -결정만 반영, 열린 질문 없음. +`Added`/`Removed`를 단일 이름에서 `string | {string}`으로 정정(아래 값 +모양 절). 새 결정만 반영, 열린 질문 없음. ## 왜 재설계됐나 @@ -19,9 +19,9 @@ retract-always-fires-reversed.md`). 2026-08-12 열다섯 번째 세션에 ## 값 모양 — `Modifier`와 같은 immutable clone 체이닝 ``` -Tag(name1, name2, ...) -- 생성자, 가변인자. Tag() 빈 값도 유효 -tag:Added(name, ...): Tag -- clone 후 이름(들) 추가, 원본 안 건드림 -tag:Removed(name, ...): Tag -- clone 후 이름(들) 제거 +Tag(name1, name2, ...) -- 생성자, 가변인자. Tag() 빈 값도 유효 +tag:Added(name: string | {string}): Tag -- clone 후 이름(들) 추가, 원본 안 건드림 +tag:Removed(name: string | {string}): Tag -- clone 후 이름(들) 제거 tag:Contains(name): boolean -- 멤버십 확인 tag:Apply(factory): U -- factory(self) 체이닝 설탕(Modifier와 동일 패턴) Tag.Merged(tag1, tag2, ...): Tag -- 여러 Tag의 합집합(무손실). Modifier의 @@ -35,19 +35,24 @@ Tag.Merged(tag1, tag2, ...): Tag -- 여러 Tag의 합집합(무손실). Modifie API처럼 보이기 때문** — 실제로는 항상 `table.clone` 후 반환(Modifier 3번 절과 동일한 immutable 확정 이유: 형제 서브트리 오염 방지). -**[정정, 2026-08-12 열다섯 번째 세션] `Added`/`Removed`도 vararg — -`Tag(a,b)`는 `Tag():Added(a,b)` 한 번의 clone sugar, `Tag():Added(a):Added(b)` -처럼 반복 clone하는 게 아님.** 처음엔 단일 `name`만 받고 생성자의 -vararg는 반복 `Added` 호출의 sugar로 서술했으나, 이러면 이름 여러 개를 -한 번에 걸 때(`Tag(a,b,c)`도 결국 clone 1회면 충분한데) 이름 개수만큼 -`table.clone`+해싱이 반복되는 손해가 남 — 태그가 이미 걸려있는 경우 self를 -그냥 리턴하는 최적화도 검토했으나(2026-08-12 열다섯 번째 세션) 그러려면 -매번 먼저 멤버십을 읽어야 해서 오히려 전반적으로 더 비쌈(해싱이 한 번 -더 나고 Set에서도 한 번 더 남) — **기각, `Added`는 항상 새 clone을 -반환하는 현재 동작 유지**. 대신 여러 이름을 한 번의 clone으로 처리하는 -vararg만 추가해 흔한 다중 추가 케이스의 비용을 줄임. `Tag(name1, name2, -...)` 생성자도 이제 정확히 `Tag():Added(name1, name2, ...)`(단일 clone)와 -동치. +**[정정, 2026-08-12 열다섯 번째 세션, 같은 세션 후속 재정정] `Added`/ +`Removed`는 vararg가 아니라 `string | {string}` — 단일 이름 또는 +이름 배열을 받고 내부에서 `type(v) == "table"`이면 순회(flatten)해서 +처리.** 처음엔 이름 여러 개를 한 clone으로 처리하려고 vararg +(`Added(name, ...)`)로 정정했으나, 사용자가 실사용 패턴을 지적하며 +재검토됨 — 조건절로 이름 목록을 동적으로 조립하는 경우(`if cond then +table.insert(names, "x") end`류)엔 결국 테이블에 모은 뒤 +`Added(table.unpack(names))`로 풀어야 해서 vararg가 오히려 더 번거로움. +반면 `string | {string}`은 그 테이블을 그대로 넘기면 끝 — 호출부가 +단일 이름이든 이미 조립해둔 배열이든 분기 없이 통일해서 부를 수 있고, +구현도 `table.unpack` 없이 단순 `type(v) == "table"` 분기 후 `for` +순회만 있으면 됨. **부작용 걱정 없음** — 받는 값이 전부 이미 확정된 +plain string이라(핸들러 계층 값처럼 identity/생명주기가 얽힌 값이 +아님) 테이블로 감싸 넘기든 아니든 의미가 완전히 동일, 오버로드가 +모호해질 여지가 없음. `Tag(name1, name2, ...)` 생성자는 그대로 vararg +유지(정적 리터럴 호출 자리라 동적 조립 문제가 없음) — 내부적으로 +`{...}`로 한 번 패킹해 `self:Added(packed)`(단일 clone, 테이블 인자 +경로 재사용)를 호출하는 것으로 구현. **children 배열 슬롯(array-part)에 직접 놓임** — `Frame { Tag("selected") }`. 정적으로 여러 개 놓아도(`Frame { Tag("a"), Tag("b") }`) 각자 독립적으로 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 a672652..1a4e433 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 @@ -20,12 +20,17 @@ ## 수정한 것 4. **`Tag:Added`/`:Removed`가 문서상 단일 `name`만 받고 있었음 — - vararg로 정정.** 원래 `Tag(a,b)`를 `Tag():Added(a):Added(b)`(clone 2회)의 - sugar로 서술했는데, 사용자가 태그 여러 개를 한 번에 걸 때 이름 개수만큼 - clone+해싱이 반복되는 손해를 지적 — self-return 최적화(이미 걸려있으면 - 그냥 self 반환)도 검토했으나 매번 먼저 멤버십을 읽어야 해서 오히려 - 더 비싸 기각, 대신 `Added(name, ...)`/`Removed(name, ...)`로 vararg를 - 지원해 여러 이름을 단일 clone으로 처리하도록 변경(`tag-plan.md`). + `string | {string}`으로 정정(같은 세션 두 단계로 수렴).** 원래 + `Tag(a,b)`를 `Tag():Added(a):Added(b)`(clone 2회)의 sugar로 서술했는데, + 사용자가 태그 여러 개를 한 번에 걸 때 이름 개수만큼 clone+해싱이 + 반복되는 손해를 지적. **1차 정정**: `Added(name, ...)`로 vararg 지원. + **2차 정정(같은 세션 후속)**: 사용자가 실사용 패턴 지적 — 조건절로 + 이름 목록을 동적으로 조립하는 경우(`if cond then table.insert(names, + "x") end`)엔 결국 `Added(table.unpack(names))`로 풀어야 해서 vararg가 + 더 번거로움, 차라리 `string | {string}`을 받아 내부에서 + `type(v) == "table"`이면 순회(flatten)하는 게 더 단순 — 최종 채택. + self-return 최적화(이미 걸려있으면 그냥 self 반환)도 검토했으나 매번 + 먼저 멤버십을 읽어야 해서 오히려 더 비싸 기각(`tag-plan.md`). ## 추가한 것 diff --git a/CLAUDE.md b/CLAUDE.md index 13f93ae..648ea22 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -634,7 +634,10 @@ 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로 정정(self-return 최적화는 매번 멤버십을 먼저 읽어야 -해서 오히려 더 비싸 기각, `tag-plan.md`). 추가로 `Slot:Splice(index, +불일치 발견해 정정 — 처음엔 vararg로 갔다가, 조건절로 동적 조립한 이름 +목록은 `table.unpack`을 거쳐야 해서 오히려 번거롭다는 지적으로 같은 +세션 안에 `string | {string}`(내부 flatten)로 재수렴(self-return +최적화는 매번 멤버십을 먼저 읽어야 해서 기각, `tag-plan.md`). 추가로 +`Slot:Splice(index, removeCount, ...newElements)` CRUD 신설 — 구간 제거+삽입을 shift/recompute 1회로 묶는 순수 최적화(`slot-plan.md`).