quad/.claude/session/2026-08-12-05-operator-sugar-plan.md
qwreey 9909aca227
decide(operator): add Operator sugar plan, unify combinators on :Apply
Sketch Sum/Not/Product-style :Compute/:Apply sugar (research/operator-sugar-plan.md,
implementation deferred). Reusable curried combinators (Sum, Animate) must
go through :Apply, not :Compute — quad rejected implicit auto-tracking, so
a factory's closed-over deps only register if the factory re-declares them
via self:Compute(...) internally; plugging a pre-built factory straight
into :Compute silently drops reactivity. Flip Animate's call site
accordingly in tween-plan.md, add the convention to bind-system-plan.md's
:Apply section, and fix a stale two-arg Animate signature comment in
architecture.md.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-12 11:54:09 +09:00

89 lines
5.5 KiB
Markdown

# 2026-08-12 다섯 번째 세션 — Operator 콤비네이터 슈가 신설
## 배경
사용자가 `reduceMotion:Compute(function(r) return not r:Get() end)`
람다가 정말 단순한 연산(not, +, ...)에도 매번 필요한 게 번거롭다고
지적 — 기본 연산(산술/논리/비트)을 미리 콤비네이터로 만들어두면
`:Compute(Not)`, `:Apply(Sum(a, b))`처럼 짧게 쓸 수 있고, 결합해서
표현하면 가독성/유지보수도 좋아진다는 제안.
## 논의
메커니즘 자체는 새로 필요한 게 없다는 걸 바로 확인함 — 2026-08-12
두 번째 세션에서 확정된 `Animate(info)` 패턴(`function(self)...end`을
반환해 `:Compute`/`:Apply`의 self-lazy-핸들 계약에 바로 꽂히는 것)과
정확히 같은 모양:
- 단항(`Not`)은 그냥 `fn(self)``:Compute(Not)`로 바로 씀.
- 다항(`Sum(a, b, ...)`)은 `factory(self)`를 반환해 `:Apply(Sum(a,b))`
self까지 포함해서 결합.
각 함수가 서로 독립(공유 상태/의존 없음)이라 부분적으로 나중에
추가하거나 하나만 고쳐도 다른 함수에 영향이 없다는 것도 확인 — 이게
우선순위를 맨 마지막으로 미뤄도 되는 근거. 사용자도 "이건 문서화 준비는
미리 적어두는 게 맞지만 구현 자체는 정말 맨 마지막에 해도 된다"고 직접
우선순위를 명시.
막힌 지점은 네이밍뿐이었음 — `Not`/`Sum`/`And`처럼 흔한 이름을 top-level에
그냥 두면 충돌 위험이 커서 `Tag`/`Attribute`처럼 네임스페이스가 필요한데,
사용자가 예시로 든 `Operator.Not`이 마음에 드는지 스스로도 확신이 없다고
언급. 코퍼스 전체에 `Operator`/`Op` 이름 충돌이 없는지 grep으로 확인(충돌
없음), `Combinator`는 코퍼스 전반에서 `:Apply` 패턴을 설명할 때 이미
일반명사로 자주 쓰여서("Apply 콤비네이터") 네임스페이스 이름으로 쓰면
헷갈릴 수 있어 후보에서 제외 — 최종 이름은 `Operator`/`Op`/`Ops` 중
미정으로 남김.
## 결과 (1차)
`research/operator-sugar-plan.md` 신설(동기/메커니즘/패키지 배치(quad-base,
엔진 종속 없음)/열린 질문 두 가지(네임스페이스 이름, 포함 범위·`Sum`이
self를 포함하는 형태가 맞는지) 전부 기록). `README.md` research 표,
`question.md` 3번(낮은 우선순위)에 반영. 구현은 착수 안 함 — 사용자가
직접 맨 마지막 우선순위로 지정했으므로 이 세션은 설계/문서화만.
이때 초안은 "0항(`Not`)은 `:Compute`에 바로, N항(`Sum`)만 `:Apply`
팩토리"로 나눠서 썼음 — 아래 후속 논의에서 뒤집힘.
## 후속 논의 — `:Compute` vs `:Apply`, 어느 쪽이 맞는가
사용자가 바로 재검토를 요청: 가독성상 `:Apply(Sum(a,b,c))`가 나아
보이고, `local SumFn = Sum(...)`처럼 만들어 여러 곳에서 `:Apply(SumFn)`
재사용하는 게 커링의 실제 이점인데 `:Compute`만 쓰면 그게 무색해진다는
지적. `Animate`도 같은 이유로 `:Compute`보다 `:Apply`가 맞지 않냐는
질문도 같이 나옴 — "값에 Tween을 적용한다"는 맥락이지 "계산한다"는
맥락이 아니라는 의미론적 근거, `:Compute`가 v1/Fusion류 "매 스텝 능동
갱신"처럼 읽힐 수 있다는 우려도 제기.
검토 결과 **스타일이 아니라 진짜 정합성 문제였음**을 확인: quad는 Vide식
암묵적 자동 추적을 이미 기각했으므로(`bind-system-plan.md` "암묵적 자동
추적 기각"), `local addTax = Sum(tax, shipping)`처럼 만든 값을
`price:Compute(addTax)`에 바로 꽂으면 `addTax`가 클로저로 캡처한
`tax`/`shipping`이 `:Compute`의 구독 목록(그 호출문의 trailing args만
등록됨)에 안 걸려 조용히 재계산이 멈추는 버그가 됨 — 이게 바로
2026-08-11 세션이 "trailing deps를 fn 위치 인자로 노출"하게 만든 것과
같은 클래스의 중복/드리프트 문제. `:Apply`는 factory가 내부에서
`self:Compute(fn, tax, shipping)`을 스스로 다시 전달해 이 문제가
원천적으로 없음 — 재사용 가능한 이름 붙은 콤비네이터는 `:Apply`
유일하게 안전한 경로. 기존 문서(`bind-system-plan.md`의 `:Apply` 절이
이미 `state:Apply(makeFormatter("ko-KR"))`를 정석 예시로 들어둔 것)와도
맞아떨어짐 — 오히려 `Animate`가 애초에 `:Compute`를 골랐던 게 이 관용구의
예외였다는 게 드러남.
## 결과 (최종)
- `research/operator-sugar-plan.md`: "0항은 Compute, N항은 Apply" 분기를
버리고 **전부 `factory(self) -> State` + `:Apply`로 통일**("왜 `:Apply`인가"
절 신설, 정합성 근거 전문 기록).
- `research/tween-plan.md`: `Animate(info)`를 `function(self) return
self:Compute(...) end`로 바꿔 `:Apply` 전용으로 정정("왜 `:Apply`
정정됐는가" 절 신설), 모든 사용 예시(`:Compute(Animate{...})` →
`:Apply(Animate{...})`)와 커스텀 조건 이스케이프 예시까지 같이 수정.
`Animate` 자체의 시그니처/동작(옵션이 deps로 안 걸리는 것 등)은 그대로 —
바뀐 건 호출 경로뿐.
- `base/bind-system-plan.md``:Apply` 절에 "이름 붙여 재사용하는
콤비네이터는 항상 `:Apply`" 관용구를 일반 원칙으로 추가 — 나중에
비슷한 콤비네이터를 또 만들 때 케이스마다 재논의 안 해도 되게.
구현은 여전히 착수 안 함(맨 마지막 우선순위 유지) — 이번 논의는 순전히
설계/문서 정정.