From b668109eaf684b3fda5d5cd2ea86bbd9d13d33c7 Mon Sep 17 00:00:00 2001 From: qwreey Date: Tue, 11 Aug 2026 10:52:20 +0900 Subject: [PATCH] decide(bind-system): :Compute trailing deps as fn positional args, fix previous ordering MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Expose :Compute(fn, ...)'s trailing deps to fn as lazy State-handle positional args (removes the closure-capture duplication/drift risk in curried fn factories). Confirms self's lazy-handle sugar generalizes here because trailing args are local to one call, unlike :With chains. previous must sit right after self, before the deps pack (fn(self, previous?, ...deps)) — not after it as first proposed: Luau's "..." must trail the parameter list, so a fixed arg after a generic type pack is very likely unrepresentable. Adds luau-test 15 with a negative control (old broken ordering) and positive control (corrected ordering) to verify, plus the remaining open question (whether heterogeneous dep types survive one generic pack). Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01BBBW9GakG8J3CFumTSJ8bw --- .claude/base/bind-system-plan.md | 65 ++++++++ ...5-type-compute-trailing-deps-typepack.luau | 140 ++++++++++++++++++ .claude/luau-test/README.md | 16 +- CLAUDE.md | 88 +++++++++++ ROADMAP.md | 9 ++ 5 files changed, 317 insertions(+), 1 deletion(-) create mode 100644 .claude/luau-test/15-type-compute-trailing-deps-typepack.luau diff --git a/.claude/base/bind-system-plan.md b/.claude/base/bind-system-plan.md index e42ee6f..45542f1 100644 --- a/.claude/base/bind-system-plan.md +++ b/.claude/base/bind-system-plan.md @@ -1247,6 +1247,71 @@ lazy State 핸들로 통일, 아래 "Store/State/Source 온톨로지" 절의 "`: 안 그런가"가 겉보기엔 비일관적으로 보이지만 실제로는 "숨겨지는 비용이 있는가"라는 하나의 원칙에서 나온 것이라는 게 소재. +### trailing deps를 `fn`에 lazy positional 인자로도 노출 — 방향+순서(`fn(self, previous?, ...deps)`) 확정, 이형 다중 deps 표현 가능 여부만 실측 필요 (2026-08-11 후속) + +**문제 제기(사용자)**: `:Compute(fn, a, b, c)`가 이미 `a,b,c`를 trailing +args로 받아 구독을 건다면, 그 값을 `fn(self, a, b, c)`처럼 위치 인자로도 +그대로 넘겨줘도 되지 않는가 — `:With`가 값을 포지셔널로 안 주는 이유는 +`:With(a):With(b):With(c)`처럼 체인이 여러 호출에 걸쳐 길어지면 최종 +합쳐진 노드가 몇 번째 인자로 뭘 받는지 추적하기 복잡해지기 때문인데, +`:Compute(fn, a, b, c)`의 trailing args는 그 호출문 **하나 안에 로컬하게** +다 드러나 있어서 같은 문제가 없다는 지적. + +**방향 확정 — 채택.** 지적이 정확함: + +- **`:With`가 회피하는 문제 자체가 여기엔 없음.** `:With` 체인의 위험은 + 의존성 목록이 여러 호출/여러 스코프에 걸쳐 누적될 수 있어("체인이 + 길어지면 순서 지키기가 복잡") 최종 위치 매핑을 코드 한 줄만 보고 + 못 읽는다는 것 — `:Compute(fn, a, b, c)`는 그 반대로 한 호출문의 + 인자 목록 자체가 곧 최종 순서라 누적/추적 문제가 원천적으로 없음. +- **실질적 이득 — 커링 패턴에서의 중복/드리프트 위험 제거.** 지금 + 설계(trailing args는 구독 등록 전용, 값은 closure로 재획득)로 + `:Compute`를 커링 스타일(위 "`fn`을 커링 스타일로 짜는 것도 권장" 절)과 + 같이 쓰면 `a, b`를 **두 번** 써야 함 — 한 번은 `makeComputer(f, a, b)`의 + 클로저 캡처용, 한 번은 `:Compute(fn, a, b)`의 trailing args(구독 + 등록용). 리팩터링 중 한쪽만 바뀌면 "구독은 `a`에 걸려있는데 실제로 + 읽는 값은 `a'`"인 조용한 버그가 생길 수 있음. 값을 `fn`의 위치 + 인자로 노출하면 `makeComputer(f)`가 `a,b`를 아예 몰라도 되고 + (`function(self, a, b) return f(self:Get(), a:Get(), b:Get()) end`), + `:Compute`의 trailing args 목록 하나가 "무엇을 구독하는가"와 "`fn`이 + 몇 번째 인자로 뭘 받는가" 둘 다의 유일한 소스가 됨 — 중복 자체가 사라짐. +- **`self`가 이미 raw 값이 아니라 lazy 핸들로 넘어가는 원칙을 trailing + deps에도 그대로 적용** — `fn(self: State, dep1: State, dep2: + State, ...)`, 각 `depN:Get()`을 실제로 호출할 때만 그 값의 계산이 + 트리거됨. self에 대해 이미 확정된 "조건부로 특정 값을 아예 안 읽고 + 건너뛸 수 있음"이라는 이점이 trailing deps에도 똑같이 적용됨. + +**`previous`(아래 절, 2026-08-06)와의 위치 충돌 — 사용자 정정으로 확정, +`fn(self, previous?, ...deps)`.** 처음엔 "`previous`를 dep 개수와 무관하게 +항상 마지막 인자로 고정"(`fn(self, dep1, ..., depN, previous?)`)을 +제안했으나 **틀림 — 사용자가 정정**: Luau 값 레벨 `...`(vararg)가 +파라미터 리스트 맨 끝에만 올 수 있는 것과 똑같이, 타입 레벨 제네릭 팩 +(`...U`)도 함수 타입 시그니처에서 **항상 맨 끝**이어야 함(팩이 나머지 +자리를 전부 채우는 개념이라 그 뒤에 고정 타입이 하나 더 오는 건 Luau +타입 문법 자체가 원천적으로 허용 안 할 가능성이 매우 높음 — 이건 "안 +될 수도 있는 불확실성"이 아니라 "거의 확실히 안 되는 문법 제약"에 가까움). +반대로 **`previous`를 `self` 바로 다음, deps 팩 앞에 두면**(`fn(self, +previous?, dep1, dep2, ..., depN)`) 고정 인자 다음에 팩이 오는 정상적인 +모양이 되어 이 제약과 안 부딪힘 — **이게 유일하게 구조적으로 안전한 +순서라 이걸로 확정**. `N=0`이면 기존 `fn(self, previous?)`로 그대로 +축약되므로 하위 호환도 유지됨. **트레이드오프**: `previous`를 안 쓰고 +deps만 받고 싶어도 `previous`가 2번째 자리를 차지하므로, 그 경우 호출부는 +`function(self, _, dep1, dep2) ... end`처럼 안 쓰는 자리를 이름으로라도 +비워둬야 함 — deps만 쓰는 흔한 케이스가 약간 불편해지지만, Luau 문법 +제약상 다른 선택지가 없음(대안은 애초에 이 확장 자체를 안 하는 것뿐). + +**실측 필요 — `.claude/luau-test/15-type-compute-trailing-deps-typepack.luau` +신규(ROADMAP.md M3 반영).** 순서 문제 자체는 위 정정으로 구조적으로 +풀렸으므로, 스파이크가 실제로 확인할 진짜 불확실성은 (B) 하나로 좁혀짐 — +나머지는 그 결론을 뒷받침하는 대조군: (A) 균일 타입 dep 1개를 고정 +인자로 좁히는 대조군(실패하면 B/C/D를 볼 것도 없이 기반 자체가 문제), +(B) 이형(heterogeneous) 타입 dep 여러 개를 제네릭 팩 하나로 정확히 +좁혀 받을 수 있는지(안 되면 위치 인자 노출 자체를 동종 타입 dep 1개로 +한정), (C) 처음 제안했던(틀린) "팩 뒤에 `previous?`" 순서가 실제로 +막히는지 보여주는 음성 대조군(막혀야 정상), (D) 정정된 "`previous?` 뒤에 +팩" 순서가 통과하는지 보여주는 양성 대조군(통과해야 정상 — 예상과 +다르게 C가 통과하거나 D가 막히면 이 순서 결정 자체를 재검토). + ### `:Compute(fn)`의 선택적 두 번째 인자 — `previous` (무거운 파생 객체 재사용, 2026-08-06) **배경**: `:Compute`의 결과가 그 자체로 무겁고 재생성 비용이 큰 엔진 diff --git a/.claude/luau-test/15-type-compute-trailing-deps-typepack.luau b/.claude/luau-test/15-type-compute-trailing-deps-typepack.luau new file mode 100644 index 0000000..157e486 --- /dev/null +++ b/.claude/luau-test/15-type-compute-trailing-deps-typepack.luau @@ -0,0 +1,140 @@ +--!strict +--[[ + 검증 대상: `:Compute(fn, ...)`의 trailing deps를 `fn`의 위치 인자로도 + 노출하는 확장(2026-08-11 후속 세션)이 Luau generic type pack으로 + 타입 가능한지. + + 배경: base/bind-system-plan.md "trailing deps를 `fn`에 lazy positional + 인자로도 노출" 절. `self`는 이미 raw 값이 아니라 State 핸들로 + 넘어가는 게 확정돼 있고(2026-08-07), trailing deps도 같은 원칙(State + 핸들)으로 `fn`에 노출하고 싶음. + + **순서는 이미 정정되어 확정됨 — `fn(self, previous?, ...deps)`.** + 처음엔 `previous?`를 deps 팩 뒤에 두려 했으나(팩 뒤에 고정 인자), + Luau 값 레벨 `...`(vararg)가 파라미터 리스트 맨 끝에만 올 수 있는 것과 + 똑같이 타입 레벨 제네릭 팩도 항상 맨 끝이어야 한다는 사용자 지적으로 + `previous?`를 팩 **앞**(self 바로 다음)으로 옮김 — 이 파일의 (C)는 그 + 잘못된(옛) 순서가 실제로 막히는지 보여주는 음성 대조군, (D)는 정정된 + 순서가 통과하는지 보여주는 양성 대조군. 남은 진짜 불확실성은 (B) 서로 + 다른 타입의 deps 여러 개를 제네릭 팩 하나로 표현 가능한지뿐. + + 실행: luau-analyze 15-type-compute-trailing-deps-typepack.luau + (또는 luau-lsp) +]] + +export type State = { + Get: (self: State) -> T, +} + +-- ===== A) self + 단일 dep(대조군) — 팩 없이 고정 인자로 정확히 좁혀지는지 ===== + +local function ComputeA( + self: State, + fn: (State, State) -> V, + dep: State +): State + return (nil :: any) :: State +end + +local selfState: State = (nil :: any) :: State +local depString: State = (nil :: any) :: State +local depBool: State = (nil :: any) :: State + +local resultA = ComputeA(selfState, function(s, d) + local n: number = s:Get() -- s가 State로 좁혀지는지 + local str: string = d:Get() -- d가 State으로 좁혀지는지 + return n +end, depString) + +-- ===== B) 이형(heterogeneous) 다중 deps — 제네릭 타입 팩(U...)으로 시도 ===== + +-- 관건: dep1(State)과 dep2(State)처럼 서로 다른 타입을 +-- 하나의 제네릭 팩 U...에 넣었을 때, fn 정의부에서 각 원소가 개별 타입으로 +-- 정확히 풀리는가 — 아니면 팩이 "동종 타입 하나"로만 취급돼 못 넣거나 +-- any/unknown으로 뭉개지는가. +local function ComputeB( + self: State, + fn: (State, U...) -> V, + ...: U... +): State + return (nil :: any) :: State +end + +local resultB = ComputeB(selfState, function(s, ...) + local a, b = ... + -- a는 State, b는 State으로 각각 좁혀져야 정확한 것 — + -- 아래 두 줄이 에러 없이 통과하는지가 핵심 확인 지점 + local str: string = a:Get() + local bool: boolean = b:Get() + return s:Get() +end, depString, depBool) + +-- ===== C) [음성 대조군] previous를 팩 뒤에 고정 인자로 — 막혀야 정상 ===== + +-- 처음 제안했던(틀린) 순서. Luau 값 레벨 `...`(vararg)는 파라미터 리스트 +-- 맨 끝에만 올 수 있다는 제약이 있음 — 타입 레벨 제네릭 팩(U...)도 함수 +-- 타입 시그니처에서 같은 제약을 받는다면, 팩 뒤에 고정 타입(V?)이 오는 +-- 아래 정의 자체가 파싱/타입체크 단계에서 막혀야 함. **이 줄이 에러 +-- 없이 통과해버리면 오히려 예상과 다른 결과** — 그러면 원래 "previous를 +-- 마지막에 두는" 안도 다시 검토 대상이 됨. +local function ComputeC( + self: State, + fn: (State, U..., V?) -> V, -- <- 팩 U... 뒤에 고정 V?가 오는(틀린) 시그니처 + ...: U... +): State + return (nil :: any) :: State +end + +-- ===== D) [양성 대조군] previous를 팩 앞, self 바로 다음에 — 통과해야 정상 ===== + +-- 정정된 순서: 고정 인자(previous?)가 먼저, 그 뒤에 팩(U...)이 오는 정상 +-- 모양. Luau 문법상 무리 없이 통과할 것으로 예상됨 — 여기서 막히면 이 +-- 확장 전체를 재검토해야 함(previous 위치를 어디로 옮겨도 안 되는 셈이라). +local function ComputeD( + self: State, + fn: (State, V?, U...) -> V, -- <- previous?가 먼저, 팩 U...은 맨 끝 + previous: V?, + ...: U... +): State + return (nil :: any) :: State +end + +local resultD = ComputeD(selfState, function(s, prev, a, b) + local n: number = s:Get() + local str: string = a:Get() + local bool: boolean = b:Get() + return n +end, nil, depString, depBool) + +print("런타임 실행은 의미 없음 — luau-analyze/luau-lsp 진단만 확인할 것") +print(resultA, resultB, resultD) + +--[[ + 확인 포인트: + 1. A) `s`/`d`가 각각 `State`/`State`으로 정확히 + 좁혀지는가 — 단일 고정 dep는 기존 self 타이핑과 같은 방식이라 + 통과할 것으로 예상(대조군, 실패하면 B/C/D를 볼 것도 없이 기반 + 자체가 문제). + 2. B) `ComputeB` 호출부의 `a:Get()`/`b:Get()` 두 줄이 타입 에러 없이 + 통과하는가 — 통과하면 "이형 다중 deps를 제네릭 팩으로 표현 가능" + 확정, 실패하면(예: `a`/`b`가 `unknown`이나 첫 dep 타입으로만 + 뭉개짐) "제네릭 팩은 동종 타입 전용이라 이형 deps는 못 태운다"는 + 결론. + 3. C) [음성 대조군] `ComputeC` 정의 자체가 파싱/타입체크 에러로 + 막히는가 — **막혀야 예상대로**(previous를 팩 뒤에 두면 안 된다는 + 근거가 되는 줄). 만약 이 줄이 에러 없이 통과해버리면, "previous는 + 무조건 팩 앞"이라는 이번 정정 자체가 불필요했다는 뜻이라 base + 문서를 다시 봐야 함. + 4. D) [양성 대조군] `ComputeD` 정의와 `resultD` 호출부(특히 + `a:Get()`/`b:Get()`가 정확한 타입으로 좁혀지는지)가 전부 에러 없이 + 통과하는가 — 통과하면 `fn(self, previous?, ...deps)` 순서가 + base 문서 확정대로 최종 시그니처로 채택됨. + 5. 결과에 따라 `base/bind-system-plan.md` "trailing deps를 fn에 lazy + positional 인자로도 노출" 절에 실측 결과로 반영할 것: + - B/D 통과, C 실패(예상대로) → 지금 base 문서에 적힌 + `fn(self, previous?, ...deps)` 시그니처 그대로 최종 확정. + - B 실패 → 위치 인자 노출은 동종 타입 dep 1개(A 방식)로만 한정하거나, + `U...`를 `...any`로 열어 런타임 계약으로만 관리하는 방향 재검토. + - C가 예상과 달리 통과 → previous 위치 제약 자체를 재검토(D도 같이 + 통과한다면 둘 다 되는 셈이라 어느 쪽이든 상관없다는 뜻). +]] diff --git a/.claude/luau-test/README.md b/.claude/luau-test/README.md index 8826776..31fd9a1 100644 --- a/.claude/luau-test/README.md +++ b/.claude/luau-test/README.md @@ -24,7 +24,7 @@ ROADMAP 항목 근거인지, 어떻게 실행하는지, 실행 후 뭘 확인해 | 환경 | 필요한 것 | 해당 파일 | |---|---|---| | **순수 Luau CLI** (`luau`) | [luau-lang/luau 릴리즈](https://github.com/luau-lang/luau/releases)의 `luau` 인터프리터, 또는 `lune` | 01, 02, 03, 04, 05, 06(런타임 부분), 07, 11, 13(런타임 부분) | -| **Luau 타입체커** (`luau-analyze` 또는 `luau-lsp`) | 같은 릴리즈에 포함된 `luau-analyze`, 또는 `luau-lsp analyze`/에디터 인라인 진단 | 06(타입 부분), 08, 09, 12, 13(타입 부분), 14 | +| **Luau 타입체커** (`luau-analyze` 또는 `luau-lsp`) | 같은 릴리즈에 포함된 `luau-analyze`, 또는 `luau-lsp analyze`/에디터 인라인 진단 | 06(타입 부분), 08, 09, 12, 13(타입 부분), 14, 15 | | **Roblox Studio** | 별도 계정으로 로그인(`HUMAN_TODO.md` 1번, `SAFETY.md` 준수) | 10 | **12/13/14는 특히 `luau-lsp`로 확인해달라고 요청받은 것들** — `luau-analyze`도 @@ -55,6 +55,7 @@ ROADMAP 항목 근거인지, 어떻게 실행하는지, 실행 후 뭘 확인해 | `12-type-attribute-generic-key-narrowing.luau` (타입체크 전용) | `[Attribute<> "name"] = value`처럼 제네릭 DI 키를 쓸 때 `value`의 타입이 실제로 `T`로 좁혀지는지 — base 문서 자신이 "미검증"이라 명시한 항목 | `attribute-plan.md` "[실측 필요, M0/M10]" (2026-08-09 열한 번째 세션 신설) | | `13-type-ref-preref-subtype.luau` | (A, 타입) `PreRef`가 `Ref`를 구조적으로 만족하는지, (B, 런타임) `isRef`/`isPreRef` 합성이 재정정대로 동작하는지(`isRef(preRefInstance)`가 이제 `true`) + Leaf 핸들러가 `isRef(v) and not isPreRef(v)`로 명시적으로 좁혀야 하는 이유 | `bind-system-plan.md`의 `Brand` 절(2026-08-09 열한 번째 세션 재정정) | | `14-type-nilable-default-overload.luau` (타입체크 전용) | `Source(default)`/`Ref(default)`의 `default` 생략이 `T`가 nilable일 때만 안전하다는 캐비엇을, 함수 오버로드(교차 타입)로 실제로 타입 레벨에서 막을 수 있는지 | `bind-system-plan.md` "[보강, 2026-08-09 열한 번째 세션]" 절 | +| `15-type-compute-trailing-deps-typepack.luau` (타입체크 전용) | `:Compute(fn, ...)`의 trailing deps를 `fn`에 위치 인자(lazy State 핸들)로도 노출하는 확장, 최종 시그니처 `fn(self, previous?, ...deps)` — 이형(heterogeneous) 다중 deps를 제네릭 타입 팩(`U...`)으로 표현 가능한지, `previous?`가 팩 앞(정정된 순서)에서만 통과하고 팩 뒤(옛 순서)에서는 막히는지 | `bind-system-plan.md` "trailing deps를 fn에 lazy positional 인자로도 노출" 절(2026-08-11 후속 세션, 순서는 같은 날 세 번째 세션에 정정) | ## 갱신 이력 @@ -87,6 +88,19 @@ ROADMAP 항목 근거인지, 어떻게 실행하는지, 실행 후 뭘 확인해 이동, git 추적 대상으로 전환. 내용 변경 없음 — 경로 참조하는 문구만 동기화. +**4차 (2026-08-11, `15` 신규)**: `:Compute(fn, ...)`의 trailing deps를 +`fn`의 위치 인자로도 노출하는 확장(같은 날 두 번째 세션) — 이형 다중 +deps의 제네릭 타입 팩 표현 가능 여부와 `previous?`를 팩 뒤에 붙일 수 +있는지가 base 문서 자신이 "실측 필요"로 명시한 새 항목이라 추가. + +**5차 (2026-08-11, 같은 날 세 번째 세션, `previous` 순서 정정)**: +4차에서 "previous를 팩 뒤에"로 적었던 순서가 틀렸음이 드러남 — Luau +값 레벨 `...`가 파라미터 리스트 맨 끝이어야 하는 것과 같은 제약으로 +`previous?`는 deps 팩 **앞**(self 바로 다음)에 와야 함. `15`의 (C)를 +"틀린 순서가 막히는지 보는 음성 대조군"으로 재정의하고, 정정된 순서를 +검증하는 (D) 양성 대조군을 신규 추가 — 이제 진짜 불확실성은 (B) +이형 다중 deps의 제네릭 팩 표현 가능 여부 하나뿐. + ## 결과 확인 후 할 일 각 파일 결과를 알려주면, 실제로 걸리는 부분이 있는지 보고 필요하면 diff --git a/CLAUDE.md b/CLAUDE.md index a0fef13..0063848 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -2735,3 +2735,91 @@ Tween 제거, `None` 센티널 절 예시 갱신, Ref/Brand 절 문구 정정), **다음 세션이 할 일**: 안 바뀜(`ROADMAP.md` M0부터, luau-test 결과 확인 우선) — 이번 세션도 순수 설계 확정이라 M0 착수 우선순위 자체는 그대로. + +## 2026-08-11 두 번째 세션 — trailing deps를 `fn`에 위치 인자로도 노출, +`.claude/luau-test/15` 신규 + +같은 날 바로 이어진 세션. 사용자가 방금 확정된 `:Compute(fn, ...)` +trailing-args sugar를 한 단계 더 밀어붙임 — trailing args `a,b,c`가 +이미 구독 등록용으로 넘어간다면 `fn(self, a, b, c)`처럼 그 값 자체도 +위치 인자로 노출해도 되지 않느냐는 제안. `:With`가 그렇게 안 하는 이유 +(체인이 여러 호출에 걸쳐 길어지면 순서 추적이 복잡해짐)는 `:Compute`의 +trailing args처럼 한 호출문 안에 로컬하게 다 보이는 경우엔 안 걸린다는 +것도 사용자가 직접 짚음 — 검증 결과 정확함, 채택. + +- **`:With`의 회피 근거가 이 케이스엔 안 걸림** — `:With` 체인은 여러 + 호출/스코프에 걸쳐 누적될 수 있어 최종 위치 매핑을 코드 한 줄만 보고 + 못 읽는 게 문제였는데, `:Compute(fn, a, b, c)`는 그 호출의 인자 목록 + 자체가 곧 최종 순서라 누적 문제가 원천적으로 없음. +- **커링 패턴에서의 중복/드리프트 위험도 같이 해소됨** — 지금 설계(값은 + closure로 재획득)로 커링 스타일을 쓰면 `a,b`를 두 번(클로저 캡처용 + + `:Compute`의 trailing args용) 써야 해서, 리팩터링 중 한쪽만 바뀌면 + "구독은 a에 걸려있는데 실제로 읽는 값은 다른 것"인 조용한 버그가 생길 + 수 있음 — 위치 인자로 노출하면 trailing args 목록 하나가 "무엇을 + 구독하는가"와 "fn이 몇 번째로 뭘 받는가" 둘 다의 유일한 소스가 됨. +- **`self`가 이미 raw 값이 아니라 lazy 핸들로 넘어가는 원칙을 그대로 + 적용** — `fn(self: State, dep1: State, ...)`, 각 + `depN:Get()`을 실제로 부를 때만 계산 트리거. +- **새로 드러난 문제 — `previous`(2026-08-06 확정)와의 위치 충돌.** + `previous`를 dep 개수와 무관하게 항상 마지막 인자로 고정 + (`fn(self, dep1, ..., depN, previous?)`, N=0이면 기존 시그니처로 + 축약돼 하위 호환)하는 안을 제안했으나, 이건 "제네릭 타입 팩(`...U`) + 뒤에 고정 인자가 오는" 모양이라 Luau가 실제로 타입체크 가능하게 + 표현해주는지가 불확실 — **사용자가 직접 이 지점을 짚어 실측 필요로 + 남김. [정정, 같은 날 세 번째 세션] 이 순서 자체가 틀림 — `previous?`는 + 팩 앞이어야 함, 아래 절 참고.** + +`base/bind-system-plan.md`(":Compute(fn, ...)" 절 바로 뒤에 신규 소절)/ +`ROADMAP.md`(M3)/`.claude/luau-test/README.md` 반영 완료. +`.claude/luau-test/15-type-compute-trailing-deps-typepack.luau` 신규 — +(A) 단일 dep 대조군, (B) 이형 다중 deps가 제네릭 팩으로 개별 타입으로 +풀리는지, (C) 팩 뒤에 `previous?` 고정 인자를 붙인 시그니처 자체가 +파싱/타입체크되는지 세 가지 확인(**[정정, 같은 날 세 번째 세션]** C의 +순서가 틀렸던 것으로 드러나 D 대조군이 추가됨 — 아래 절 참고). 다른 +luau-test 파일들과 마찬가지로 **에이전트가 직접 실행 못 함** — +`luau`/`luau-analyze` 바이너리가 이 환경에 없어서, 사용자가 직접 +돌려보고 결과를 알려줘야 함. + +**다음 세션이 할 일**: 안 바뀜(`ROADMAP.md` M0부터, luau-test 결과 확인 +우선) — `15`가 luau-test 결과 확인 목록에 하나 추가됨(정정된 순서는 +아래 세 번째 세션 참고). 남은 진짜 불확실성은 B(이형 다중 deps를 +제네릭 팩으로 표현 가능한지)뿐 — 실패하면 위치 인자 노출을 동종 타입 +dep 1개로 한정. + +## 2026-08-11 세 번째 세션 — `previous`는 팩 앞(`fn(self, previous?, +...deps)`)으로 순서 정정, "이걸 안 할 이유"였는데 살아남음 + +바로 이어진 짧은 세션. 사용자가 위 두 번째 세션에서 제안했던 +"`previous`는 dep 개수와 무관하게 항상 마지막"(`fn(self, dep1, ..., +depN, previous?)`)을 직접 정정 — "애초에 `fn(self, prev, ...)`이긴 +해야할듯. 아니면 이걸 하지 말던가." + +**정정 채택 — `previous?`는 deps 팩 **앞**(self 바로 다음)에 와야 함, +`fn(self, previous?, dep1, ..., depN)`.** 이건 단순 선호가 아니라 거의 +확실한 Luau 문법 제약에서 나오는 결론: 값 레벨 `...`(vararg)가 파라미터 +리스트 맨 끝에만 올 수 있는 것과 마찬가지로, 타입 레벨 제네릭 팩(`...U`)도 +함수 타입 시그니처에서 항상 맨 끝이어야 할 가능성이 매우 높음(팩이 +"나머지 자리를 전부 채운다"는 개념이라 그 뒤에 고정 타입이 하나 더 +오는 걸 문법 자체가 지원 안 할 것으로 추정) — 위 두 번째 세션에서 제안한 +"previous를 팩 뒤에" 순서는 이 제약과 정면으로 부딪혀 애초에 파싱/타입 +체크가 안 될 가능성이 높았음. `previous?`를 팩 **앞**에 두면 "고정 인자 +다음에 팩"이라는 정상적인 모양이 되어 이 제약과 안 부딪힘 — **구조적으로 +유일하게 안전한 순서라 이걸로 확정**. + +**트레이드오프 — deps만 쓰고 싶어도 `previous` 자리를 비워둬야 함.** +`fn(self, previous?, dep1, dep2)`이므로, `previous`가 필요 없는 흔한 +호출도 `function(self, _, dep1, dep2) ... end`처럼 안 쓰는 두 번째 자리를 +이름으로라도 채워야 함 — Luau 문법 제약상 다른 선택지가 없어서 받아들이는 +비용. 사용자가 "아니면 이걸 하지 말던가"로 던진 양자택일에서, 이 정정으로 +구조적으로 안전한 순서를 찾았으므로 **확장 자체는 폐기하지 않고 이 순서로 +유지.** + +`base/bind-system-plan.md`(위 절의 "previous와의 위치 충돌" 소절 전면 +정정)/`ROADMAP.md`(M3)/`.claude/luau-test/15-type-compute-trailing-deps- +typepack.luau`(C를 "막혀야 정상인 음성 대조군"으로 재정의, D를 정정된 +순서의 "통과해야 정상인 양성 대조군"으로 신규 추가)/`.claude/luau-test/ +README.md` 반영 완료. + +**다음 세션이 할 일**: 안 바뀜(`ROADMAP.md` M0부터, luau-test 결과 확인 +우선) — `15`의 C/D가 예상대로 나오는지(C는 막히고 D는 통과)까지 같이 +확인해줄 것, 예상과 다르면 이 순서 결정 자체를 재검토. diff --git a/ROADMAP.md b/ROADMAP.md index 7d3b180..548ca39 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -154,6 +154,15 @@ Luau 코드로 부딪혀본 적 없는 세 가지**를 던지는 코드로 검 자신에 구독만 추가)로 끝나야 함, 새 노드 생성 없이 구현되는지 M0/M3 스파이크에서 확인. `Effect`/`Observer`는 대칭 sugar 없이 `:With` 명시 유지(의도적 비대칭, 같은 절 참고) +- [ ] trailing deps를 `fn`에 lazy positional 인자로도 노출(`fn(self, + previous?, dep1, ..., depN)` — 순서는 Luau 값 레벨 `...`가 파라미터 + 리스트 맨 끝이어야 하는 것과 같은 이유로 `previous?`가 deps 팩 + **앞**에 와야 함, 2026-08-11 후속 세션 제안 → 같은 날 세 번째 + 세션에 순서 정정, `base/bind-system-plan.md` "trailing deps를 fn에 + lazy positional 인자로도 노출" 절) — 방향/순서는 확정, + `.claude/luau-test/15-type-compute-trailing-deps-typepack.luau`로 + 이형 다중 deps를 제네릭 타입 팩으로 표현 가능한지만 실측 필요(안 + 되면 동종 타입 dep 1개로 한정) - [ ] `Blocker.luau`(`base/blocker-plan.md` 참고 — 여러 Source를 한꺼번에 바꿔도 파생값 재계산/재대입이 한 번만 되게 하는 primitive, State와 밀접히 연관돼 있어 같은 마일스톤에서 개발)