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:
parent
9370acd394
commit
b668109eaf
5 changed files with 317 additions and 1 deletions
|
|
@ -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`의 결과가 그 자체로 무겁고 재생성 비용이 큰 엔진
|
||||
|
|
|
|||
140
.claude/luau-test/15-type-compute-trailing-deps-typepack.luau
Normal file
140
.claude/luau-test/15-type-compute-trailing-deps-typepack.luau
Normal 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도 같이
|
||||
통과한다면 둘 다 되는 셈이라 어느 쪽이든 상관없다는 뜻).
|
||||
]]
|
||||
|
|
@ -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의 제네릭 팩 표현 가능 여부 하나뿐.
|
||||
|
||||
## 결과 확인 후 할 일
|
||||
|
||||
각 파일 결과를 알려주면, 실제로 걸리는 부분이 있는지 보고 필요하면
|
||||
|
|
|
|||
88
CLAUDE.md
88
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<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는 통과)까지 같이
|
||||
확인해줄 것, 예상과 다르면 이 순서 결정 자체를 재검토.
|
||||
|
|
|
|||
|
|
@ -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와 밀접히 연관돼 있어 같은 마일스톤에서 개발)
|
||||
|
|
|
|||
Loading…
Reference in a new issue