decide(bind-system): PreRef is single-use, no cancel concept, reuse errors
PreRef never enters the normal retract dispatch chain (consumed via None in the pre-pass), so "cancel" was never structurally possible. The real hazard is reuse across constructions (stale .Value silently firing callbacks) — guard it with an explicit error on re-fire instead. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
parent
e6880e518c
commit
cdbbdba4fb
4 changed files with 120 additions and 14 deletions
|
|
@ -1148,6 +1148,37 @@ flatten된 값은 해시 파트(프로퍼티 키)로 존재하게 되고, Store
|
|||
Handler가 실제로
|
||||
매치되는 경우는 오직 "타입이 막았어야 했는데 어떻게든 동적으로
|
||||
새어들어온" 버그 케이스뿐 — 그래서 no-op이 아니라 즉시 `error`.
|
||||
- **PreRef는 "취소"라는 개념이 없다 — 1회용, 재사용은 즉시 error
|
||||
(2026-08-12 여섯 번째 세션, 사용자 제안 채택).** `Ref`가 "다른 값으로
|
||||
교체되면 `retract`로 취소됨"이라는 의미의 취소를 가질 수 있는 건 정상
|
||||
우선순위 스캔의 `(inst,k)` 디스패치 체인에 실제로 참여해서임 —
|
||||
`Dispatch.retractUnder`가 그 체인을 대상으로 동작함. `PreRef`는 애초에
|
||||
그 체인에 올라간 적이 없음(pre-pass에서 fire와 동시에 `None`으로
|
||||
소진되고 정상 두 패스는 건드리지 않음, 위 "호이스팅의 실제 구현" 절) —
|
||||
그래서 "취소 가능 여부" 자체가 성립할 토대가 없었던 게 구조적으로
|
||||
이미 사실이었음, 이번 세션은 그걸 명문화한 것뿐. 진짜 위험은 취소가
|
||||
아니라 **재사용**: 이미 한 번 fire된 `PreRef` 객체를 두 번째
|
||||
construction의 children 배열에 다시 놓으면, 거기서 등록하는
|
||||
`:Callback(fn)`이 "이미 채워져 있으면 즉시 1회 호출"이라는 규칙(위
|
||||
"Ref 일반화" 절) 때문에 **의도한 새 인스턴스가 아니라 첫 번째 fire
|
||||
때 남은 stale `.Value`로 조용히 호출**됨 — 에러도 안 나고 엉뚱한
|
||||
값을 들고 실행되는, 디버깅하기 아주 어려운 버그. `State`/`:With`를
|
||||
"clone 빌더가 아니라 매번 새 노드"로 확정했던 원칙(2026-08-07 세
|
||||
번째 세션, "`:With`도 새 State 노드")과 같은 클래스의 문제이자 같은
|
||||
해법.
|
||||
- **구현**: pre-pass가 첫 fire 때 해당 `PreRef` 객체에 내부 플래그
|
||||
(`_fired = true`)를 세팅. pre-pass가 배열을 훑다 `isPreRef(v)`인
|
||||
슬롯을 만났는데 그 객체가 이미 `_fired`면, fire하지 않고 그 자리에서
|
||||
즉시 `error("PreRef는 1회용 — 이미 다른 construction에 쓰인
|
||||
PreRef를 재사용할 수 없음, 매번 새로 만들 것")`. 위 "동적 경로 가드"
|
||||
Handler(정상 두 패스에서 매치)와는 별개 코드 경로 — 이 가드는
|
||||
pre-pass 자신 안에, `_fired`가 아닌 정상 fire는 그대로 통과.
|
||||
- **관용구**: `Slot:List`의 `updateFn`처럼 반복 호출되는 자리에서
|
||||
`PreRef`가 필요하면 **호출마다 새 `PreRef()`를 만들 것** — 클로저에
|
||||
캡처해 여러 construction에 걸쳐 재사용하지 말 것. (참고: `Slot`
|
||||
자체는 요소 타입으로 `Ref`/`PreRef`를 이미 금지하고 있어(위
|
||||
"요소 타입 제약" 절, `slot-plan.md`) 이 관용구가 실제로 문제되는
|
||||
자리는 `updateFn` 안에서 호출하는 컴포넌트 함수 내부뿐임.)
|
||||
- **일반 `Ref`는 계속 Modifier/Store 어디든 자유롭게
|
||||
들어감** — Store를 통해 나중에 도착하는 Ref는 그냥 도착한 그 순간
|
||||
처리하면 됨, phase 개념 자체가 필요 없음("만난 순간 처리"로 충분).
|
||||
|
|
|
|||
|
|
@ -207,11 +207,15 @@ additional-primitives-plan.md`의 "문서화 백로그" 절이 원자료)**:
|
|||
값으로 교체되면 `retract`로 취소됨)에 가깝고, `PreRef`는 그 스캔 밖의
|
||||
고정 pre-pass라는 의미에서 "pre-hook"(항상 최우선 고정, 순서/취소
|
||||
개념 자체가 다름)에 가깝다는 구분 — quadnomicon 에세이로 쓸 때 이
|
||||
"hook"/"pre-hook" 용어 자체를 채택할지부터 먼저 확인 필요(복수 `PreRef`
|
||||
"hook"/"pre-hook" 용어 자체를 채택할지만 아직 열려있음(복수 `PreRef`
|
||||
간 순서는 2026-08-07 아홉 번째 세션에서 해소됨 — 배열 index 순서
|
||||
그대로, 별도 규칙 없음, `bind-system-plan.md` "PreRef" 절 참고. 취소
|
||||
가능 여부는 여전히 미정 — PreRef는 fire와 동시에 소진되는 1회성
|
||||
pre-pass 참가자라 "취소"라는 개념 자체가 성립하는지부터 다시 볼 것).
|
||||
그대로, 별도 규칙 없음, `bind-system-plan.md` "PreRef" 절 참고).
|
||||
**[해소됨, 2026-08-12 여섯 번째 세션]** 취소 가능 여부 — PreRef는
|
||||
구조적으로 `retract` 체인에 아예 안 올라가므로 취소 개념 자체가 없고,
|
||||
대신 이미 fire된 PreRef를 재사용하면(두 번째 construction에 다시
|
||||
놓으면) 즉시 `error` — "1회용, 재할당 불가"로 확정. `bind-system-plan.md`
|
||||
"동적 경로로 도착한 PreRef는 런타임에도 명시적으로 에러" 절 바로 아래
|
||||
"PreRef는 '취소'라는 개념이 없다" 항목 참고.
|
||||
|
||||
7. **왜 `Compute(fn, ...)`는 여러 의존성을 편하게 받고 `Effect`/`Observer`는
|
||||
안 받는가** (2026-08-11 세션 원자료, `bind-system-plan.md` "`:Compute(fn,
|
||||
|
|
@ -259,17 +263,18 @@ additional-primitives-plan.md`의 "문서화 백로그" 절이 원자료)**:
|
|||
완전히 확정(2026-08-09 열한 번째 세션엔 `Extract(index, newElement?)`로
|
||||
더 확장). `research/additional-primitives-plan.md`는 더 이상 열린
|
||||
항목 없음, 배경 자료로만 유지.
|
||||
- **"hook"/"pre-hook" 용어 채택 여부 + `PreRef`의 취소 가능성** (2026-08-07,
|
||||
위 심화 후보 6번 참고) — `bind-system-plan.md`는 `PreRef`가 위치 무관
|
||||
호이스팅이라는 것과 일반 `Ref`가 우선순위 스캔에 참여한다는 것까지는
|
||||
확정해뒀고(복수 `PreRef` 간 순서=배열 index 순서, 동적 경로로 도착한
|
||||
PreRef는 전용 Handler가 즉시 error — 둘 다 아홉 번째 세션에서 추가
|
||||
확정), "hook 대 pre-hook"이라는 용어 자체를 문서화 시 채택할지와
|
||||
`PreRef`의 취소 가능성(애초에 fire와 동시에 소진되는 1회성이라
|
||||
"취소"가 의미 있는 개념인지부터)만 아직 미정.
|
||||
- **"hook"/"pre-hook" 용어 채택 여부** (2026-08-07, 위 심화 후보 6번 참고)
|
||||
— `bind-system-plan.md`는 `PreRef`가 위치 무관 호이스팅이라는 것과 일반
|
||||
`Ref`가 우선순위 스캔에 참여한다는 것까지는 확정해뒀고(복수 `PreRef`
|
||||
간 순서=배열 index 순서, 동적 경로로 도착한 PreRef는 전용 Handler가
|
||||
즉시 error — 둘 다 아홉 번째 세션에서 추가 확정), **`PreRef`의 취소
|
||||
가능성은 2026-08-12 여섯 번째 세션에 "취소 개념 없음, 재사용은 error"로
|
||||
해소됨**(위 심화 후보 6번, `bind-system-plan.md` 참고) — "hook 대
|
||||
pre-hook"이라는 용어 자체를 문서화 시 채택할지만 아직 미정.
|
||||
|
||||
이 항목들은 `.claude/question.md`에도 이미 열린 질문으로 잡혀있음 — 여기선
|
||||
"확정 전엔 문서화 대상 아님"이라는 표시만 겸함.
|
||||
`PreRef` 취소 가능성 항목은 실제로는 `.claude/question.md`에 별도로
|
||||
잡혀있던 적이 없었음(이전 서술이 부정확했음, 이번에 확인·수정) — 순수
|
||||
용어 선택인 "hook"/"pre-hook" 채택 여부만 여기서 계속 추적.
|
||||
|
||||
## 다음 단계
|
||||
|
||||
|
|
|
|||
59
.claude/session/2026-08-12-07-preref-single-use-no-cancel.md
Normal file
59
.claude/session/2026-08-12-07-preref-single-use-no-cancel.md
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
# 2026-08-12 일곱 번째 세션 — PreRef는 1회용, 취소 개념 없음, 재사용은 error
|
||||
|
||||
## 배경
|
||||
|
||||
이전 세션(대화 초반)에서 사용자가 "지금 확정이긴 한데 다른 의견 나오면
|
||||
뒤집힐 만한 부분이 뭐가 있을지" 물어봐서 서브에이전트로 `.claude/`
|
||||
전체를 감사했음. 그 결과 중 하나가 `research/documentation-content-map.md`
|
||||
262-269행에 **명시적으로 미정으로 표시돼 있던 항목**: `PreRef`의 취소
|
||||
가능성 — "PreRef는 fire와 동시에 소진되는 1회성 pre-pass 참가자라
|
||||
'취소'라는 개념 자체가 성립하는지부터 다시 볼 것"이라고만 적혀 있고
|
||||
실제 결론은 없었음.
|
||||
|
||||
## 논의
|
||||
|
||||
사용자 제안: PreRef는 취소 불가능하게 두는 게 나을 듯 — 동적으로 쓰일
|
||||
수 있는 예외적 경우(Slot의 `updateFn`처럼 반복 호출되는 자리에서
|
||||
컴포넌트가 내부적으로 PreRef를 만드는 경우)도 State/`:With`처럼 "매번
|
||||
새로 만드는" 패턴을 따르는 게 맞다. 이전 콜백이 다시 실행되는 건 코드가
|
||||
꼬이고 디버깅이 어려워지는 원인이 될 것. Ref와는 다른 결 — use only once.
|
||||
|
||||
Claude가 확인한 구조적 근거: `Ref`가 "다른 값으로 교체되면 `retract`로
|
||||
취소됨"이라는 취소 개념을 가질 수 있는 건 정상 우선순위 스캔의
|
||||
`(inst,k)` 디스패치 체인에 실제로 참여하기 때문(`Dispatch.retractUnder`가
|
||||
그 체인 대상으로 동작). `PreRef`는 pre-pass에서 fire와 동시에
|
||||
`flattened[i] = None`으로 소진되고 정상 두 패스는 아예 건드리지 않음
|
||||
(`bind-system-plan.md` "호이스팅의 실제 구현" 절 — 기존에 이미 확정돼
|
||||
있던 메커니즘) — 그래서 "취소"가 애초에 도달할 방법이 없는 값이었음,
|
||||
사용자의 직관이 기존 구조와 정확히 들어맞음.
|
||||
|
||||
더 나아가 진짜 위험은 "취소"가 아니라 **재사용**이라는 걸 짚음: 이미
|
||||
fire된 PreRef 객체를 두 번째 construction에 다시 놓으면, 거기서 등록하는
|
||||
`:Callback(fn)`이 "이미 채워져 있으면 즉시 1회 호출"이라는 기존 규칙
|
||||
때문에 의도한 새 인스턴스가 아니라 첫 번째 fire 때 남은 stale `.Value`로
|
||||
조용히 호출됨 — 에러 없이 엉뚱한 값을 들고 실행되는, 디버깅하기 아주
|
||||
어려운 버그. 사용자가 이 진단에 동의, "에러 가드 구현이 거의 공짜니까
|
||||
재할당을 아예 불가능하게 두자"로 확정.
|
||||
|
||||
## 결정
|
||||
|
||||
1. **PreRef는 취소 개념이 없음** — 구조적으로 이미 그랬던 사실을 명문화.
|
||||
2. **이미 fire된 PreRef를 재사용(두 번째 construction에 다시 놓기)하면
|
||||
pre-pass가 즉시 `error`** — 내부 `_fired` 플래그로 구현, 위 "동적
|
||||
경로 가드" Handler와는 별개의 코드 경로(pre-pass 자신 안에 위치).
|
||||
3. **관용구**: `Slot:List`의 `updateFn`처럼 반복 호출되는 자리에서
|
||||
PreRef가 필요하면 호출마다 새 `PreRef()`를 만들 것 — 클로저 캡처로
|
||||
재사용 금지. (Slot 자체는 이미 요소 타입으로 Ref/PreRef를 금지하고
|
||||
있어, 이 관용구가 실제로 문제되는 자리는 `updateFn` 안에서 호출하는
|
||||
컴포넌트 함수 내부뿐.)
|
||||
|
||||
## 반영
|
||||
|
||||
- `base/bind-system-plan.md` — "동적 경로로 도착한 PreRef는 런타임에도
|
||||
명시적으로 에러" 절 바로 아래에 새 항목 추가(구조적 근거, 재사용
|
||||
위험 시나리오, 구현 메커니즘, 관용구 전부 포함).
|
||||
- `research/documentation-content-map.md` — 심화 후보 6번과 "문서화 아직
|
||||
보류" 목록 두 곳에서 취소 가능성 항목을 [해소됨]으로 정정, "hook"/
|
||||
"pre-hook" 용어 채택 여부만 계속 열린 채로 남김. 두 항목 다 "이미
|
||||
question.md에도 열려있음"이라던 서술이 실제로는 부정확했던 것도 같이
|
||||
발견·수정(grep 결과 question.md엔 해당 항목이 없었음).
|
||||
11
CLAUDE.md
11
CLAUDE.md
|
|
@ -499,3 +499,14 @@ bind-system-plan.md`) `local addTax = Sum(a,b)`처럼 만든 값을 `:Compute`
|
|||
오버엔지니어링). 이걸로 열린 설계 질문이 없어져 `research/tween-plan.md`를
|
||||
`base/tween-plan.md`로 승격, 라이브 크로스레퍼런스 전부 갱신(session/
|
||||
과거 기록은 원문 보존을 위해 그대로 둠).
|
||||
|
||||
**2026-08-12 일곱 번째 세션 — `PreRef`는 취소 개념 없음, 재사용은 error**
|
||||
(`session/2026-08-12-07-preref-single-use-no-cancel.md`)
|
||||
`documentation-content-map.md`에 미정으로 남아있던 "`PreRef` 취소 가능성"
|
||||
해소: `PreRef`는 pre-pass에서 fire와 동시에 소진돼 정상 `retract` 체인에
|
||||
아예 안 올라가므로 취소 개념 자체가 없음(사용자 직관과 기존 구조가
|
||||
정확히 일치). 진짜 위험은 취소가 아니라 재사용(stale `.Value`로 콜백이
|
||||
조용히 잘못 호출됨)이라고 판단해, 이미 fire된 `PreRef`를 다시 놓으면
|
||||
pre-pass가 즉시 `error`하는 가드 확정(`_fired` 플래그, 거의 공짜 구현) —
|
||||
1회용, use only once. `Slot:List`의 `updateFn`처럼 반복 호출되는 자리에선
|
||||
매번 새 `PreRef()`를 만들라는 관용구도 같이 명문화.
|
||||
|
|
|
|||
Loading…
Reference in a new issue