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

5.5 KiB

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" 관용구를 일반 원칙으로 추가 — 나중에 비슷한 콤비네이터를 또 만들 때 케이스마다 재논의 안 해도 되게.

구현은 여전히 착수 안 함(맨 마지막 우선순위 유지) — 이번 논의는 순전히 설계/문서 정정.