decide(bind-system): :Compute trailing deps as fn positional args, fix previous ordering

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 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BBBW9GakG8J3CFumTSJ8bw
This commit is contained in:
qwreey 2026-08-11 10:52:20 +09:00
parent 9370acd394
commit b668109eaf
Signed by: qwreey
GPG key ID: D28DB79297A214BD
5 changed files with 317 additions and 1 deletions

View file

@ -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<T>, dep1: State<U1>, dep2:
State<U2>, ...)`, 각 `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`의 결과가 그 자체로 무겁고 재생성 비용이 큰 엔진

View file

@ -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<T> 핸들로
넘어가는 게 확정돼 있고(2026-08-07), trailing deps도 같은 원칙(State<U>
핸들)으로 `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<T> = {
Get: (self: State<T>) -> T,
}
-- ===== A) self + 단일 dep(대조군) — 팩 없이 고정 인자로 정확히 좁혀지는지 =====
local function ComputeA<T, U, V>(
self: State<T>,
fn: (State<T>, State<U>) -> V,
dep: State<U>
): State<V>
return (nil :: any) :: State<V>
end
local selfState: State<number> = (nil :: any) :: State<number>
local depString: State<string> = (nil :: any) :: State<string>
local depBool: State<boolean> = (nil :: any) :: State<boolean>
local resultA = ComputeA(selfState, function(s, d)
local n: number = s:Get() -- s가 State<number>로 좁혀지는지
local str: string = d:Get() -- d가 State<string>으로 좁혀지는지
return n
end, depString)
-- ===== B) 이형(heterogeneous) 다중 deps — 제네릭 타입 팩(U...)으로 시도 =====
-- 관건: dep1(State<string>)과 dep2(State<boolean>)처럼 서로 다른 타입을
-- 하나의 제네릭 팩 U...에 넣었을 때, fn 정의부에서 각 원소가 개별 타입으로
-- 정확히 풀리는가 — 아니면 팩이 "동종 타입 하나"로만 취급돼 못 넣거나
-- any/unknown으로 뭉개지는가.
local function ComputeB<T, V, U...>(
self: State<T>,
fn: (State<T>, U...) -> V,
...: U...
): State<V>
return (nil :: any) :: State<V>
end
local resultB = ComputeB(selfState, function(s, ...)
local a, b = ...
-- a는 State<string>, b는 State<boolean>으로 각각 좁혀져야 정확한 것 —
-- 아래 두 줄이 에러 없이 통과하는지가 핵심 확인 지점
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<T, V, U...>(
self: State<T>,
fn: (State<T>, U..., V?) -> V, -- <- 팩 U... 뒤에 고정 V?가 오는(틀린) 시그니처
...: U...
): State<V>
return (nil :: any) :: State<V>
end
-- ===== D) [양성 대조군] previous를 팩 앞, self 바로 다음에 — 통과해야 정상 =====
-- 정정된 순서: 고정 인자(previous?)가 먼저, 그 뒤에 팩(U...)이 오는 정상
-- 모양. Luau 문법상 무리 없이 통과할 것으로 예상됨 — 여기서 막히면 이
-- 확장 전체를 재검토해야 함(previous 위치를 어디로 옮겨도 안 되는 셈이라).
local function ComputeD<T, V, U...>(
self: State<T>,
fn: (State<T>, V?, U...) -> V, -- <- previous?가 먼저, 팩 U...은 맨 끝
previous: V?,
...: U...
): State<V>
return (nil :: any) :: State<V>
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<number>`/`State<string>`으로 정확히
좁혀지는가 — 단일 고정 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도 같이
통과한다면 둘 다 되는 셈이라 어느 쪽이든 상관없다는 뜻).
]]

View file

@ -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<<T>> "name"] = value`처럼 제네릭 DI 키를 쓸 때 `value`의 타입이 실제로 `T`로 좁혀지는지 — base 문서 자신이 "미검증"이라 명시한 항목 | `attribute-plan.md` "[실측 필요, M0/M10]" (2026-08-09 열한 번째 세션 신설) |
| `13-type-ref-preref-subtype.luau` | (A, 타입) `PreRef<T>``Ref<T>`를 구조적으로 만족하는지, (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의 제네릭 팩 표현 가능 여부 하나뿐.
## 결과 확인 후 할 일
각 파일 결과를 알려주면, 실제로 걸리는 부분이 있는지 보고 필요하면

View file

@ -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<T>, dep1: State<U1>, ...)`, 각
`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는 통과)까지 같이
확인해줄 것, 예상과 다르면 이 순서 결정 자체를 재검토.

View file

@ -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와 밀접히 연관돼 있어 같은 마일스톤에서 개발)