에러 누적 — zipOrAccumulate & 친구들
검증에는 첫 번째 에러가 아니라 모든 에러가 필요합니다. 여기
있는 연산들은 모든 분기를 실행하고 실패를
NonEmptyList로 이어 붙입니다 — 별도의
Validated 타입을 대체하는 Arrow 2.x의 방식입니다.
강의
either<Nel<E>, _>(...) 안에서 — 에러 타입이
NonEmptyList인 모든 스코프에서 — 스코프는 누적 어휘를
갖게 됩니다:
r.zipOrAccumulate2..5(branches…, combine)— 서로 독립적인 분기 N개를 실행하고, 모든 실패를 보고하고, 성공들을 합칩니다.r.accumulate((acc) { … })— 일반형입니다.acc.accumulating(block)으로 분기를 실행한 뒤 각 결과의.value를 읽습니다. 하나라도 실패했다면 값을 읽는 순간(또는 블록 끝에 도달하는 순간) 전체 에러 목록이 raise됩니다.r.mapOrAccumulate(items, transform)— 컬렉션 전체를 fail-slow로 검증합니다.r.bindNel(eitherNel)—EitherNel을 풀어내면서 그 안의 모든 에러를 한꺼번에 raise합니다.someEither.toEitherNel()은 fail-fast 값을 누적 스코프로 이어 줍니다.
계약은 Arrow의 것 그대로입니다. 모든 분기가 실행되고(에러는 분기 순서대로 이어 붙습니다), raise 대신 throw한 분기는 누적보다 우선하며, 첫 에러가 난 뒤로는 성공 결과를 더 이상 보관하지 않습니다 — 남은 에러를 모으기 위해 순회만 계속할 뿐입니다.
데모 1 · zipOrAccumulate2
데모 2 · accumulate — 일반형
데모 3 · mapOrAccumulate, bindNel & toEitherNel
데모 4 · dependent — 형제 값을 읽는 규칙
브랜치는 형제의 Accumulated.value를 읽는 순간 폭발합니다 —
누적의 단 하나뿐인 강한 규칙입니다. 하지만 실제 검증에는 의존 규칙이
있습니다("지출에는 양수 금액이 필요하다"). 그래서
폼들은 번번이 수동 if (!acc.hasErrors) 가드로
내려가곤 했습니다. acc.dependent(block)은 그 가드에 이름을
붙인 것입니다. 블록은 지금까지의 모든 브랜치가 성공했을 때만 실행되고 —
그래서 그 안의 형제 .value 읽기는 구조적으로 안전합니다 —
아니면 통째로 건너뜁니다. (Arrow에는 대응물이 없습니다. Arrow
사용자들도 같은 가드를 손으로 굴립니다.)
직접 해 보기
연습: signup의 두 분기를 완성해서 두 번째 호출이
두 실패를 모두 보고하도록 만들어 보세요.
NonEmptyList — 에러를 실어 나르는 그릇 ·
Either × 파이프라인 — fx() 체인 위에서 동시성과 함께 하는 fail-slow 검증 ·
either & Raise — 이 기능이 확장하는 fail-fast 스코프 ·
타입 있는 에러 — 전체 가이드