| 1 | # Cause: Flattened Structure |
| 2 | |
| 3 | In v3, `Cause<E>` was a recursive tree with six variants: |
| 4 | |
| 5 | ``` |
| 6 | Empty | Fail<E> | Die | Interrupt | Sequential<E> | Parallel<E> |
| 7 | ``` |
| 8 | |
| 9 | The `Sequential` and `Parallel` variants composed causes into a tree to |
| 10 | represent errors from finalizers or concurrent operations. |
| 11 | |
| 12 | In v4, `Cause<E>` has been flattened to a simple wrapper around an array of |
| 13 | `Reason` values: |
| 14 | |
| 15 | ```ts |
| 16 | interface Cause<E> { |
| 17 | readonly reasons: ReadonlyArray<Reason<E>> |
| 18 | } |
| 19 | |
| 20 | type Reason<E> = Fail<E> | Die | Interrupt |
| 21 | ``` |
| 22 | |
| 23 | There are only three reason variants — `Fail`, `Die`, and `Interrupt`. The |
| 24 | `Empty`, `Sequential`, and `Parallel` variants have been removed. An empty |
| 25 | cause is represented by an empty `reasons` array. Multiple failures (from |
| 26 | concurrent or sequential composition) are collected into a flat array. |
| 27 | |
| 28 | ## Accessing Reasons |
| 29 | |
| 30 | **v3** — pattern match on the recursive tree structure: |
| 31 | |
| 32 | ```ts |
| 33 | import { Cause } from "effect" |
| 34 | |
| 35 | const handle = (cause: Cause.Cause<string>) => { |
| 36 | switch (cause._tag) { |
| 37 | case "Fail": |
| 38 | return cause.error |
| 39 | case "Die": |
| 40 | return cause.defect |
| 41 | case "Empty": |
| 42 | return undefined |
| 43 | case "Sequential": |
| 44 | return handle(cause.left) |
| 45 | case "Parallel": |
| 46 | return handle(cause.left) |
| 47 | case "Interrupt": |
| 48 | return cause.fiberId |
| 49 | } |
| 50 | } |
| 51 | ``` |
| 52 | |
| 53 | **v4** — iterate over the flat `reasons` array: |
| 54 | |
| 55 | ```ts |
| 56 | import { Cause } from "effect" |
| 57 | |
| 58 | const handle = (cause: Cause.Cause<string>) => { |
| 59 | for (const reason of cause.reasons) { |
| 60 | switch (reason._tag) { |
| 61 | case "Fail": |
| 62 | return reason.error |
| 63 | case "Die": |
| 64 | return reason.defect |
| 65 | case "Interrupt": |
| 66 | return reason.fiberId |
| 67 | } |
| 68 | } |
| 69 | } |
| 70 | ``` |
| 71 | |
| 72 | ## Reason Guards |
| 73 | |
| 74 | The v3 type-level guards (`isFailType`, `isDieType`, `isInterruptType`, etc.) |
| 75 | have been replaced by reason-level guards: |
| 76 | |
| 77 | | v3 | v4 | |
| 78 | | ------------------------------- | --------------------------------- | |
| 79 | | `Cause.isEmptyType(cause)` | `cause.reasons.length === 0` | |
| 80 | | `Cause.isFailType(cause)` | `Cause.isFailReason(reason)` | |
| 81 | | `Cause.isDieType(cause)` | `Cause.isDieReason(reason)` | |
| 82 | | `Cause.isInterruptType(cause)` | `Cause.isInterruptReason(reason)` | |
| 83 | | `Cause.isSequentialType(cause)` | Removed | |
| 84 | | `Cause.isParallelType(cause)` | Removed | |
| 85 | |
| 86 | ## Cause-Level Predicates |
| 87 | |
| 88 | | v3 | v4 | |
| 89 | | -------------------------------- | -------------------------------- | |
| 90 | | `Cause.isFailure(cause)` | `Cause.hasFails(cause)` | |
| 91 | | `Cause.isDie(cause)` | `Cause.hasDies(cause)` | |
| 92 | | `Cause.isInterrupted(cause)` | `Cause.hasInterrupts(cause)` | |
| 93 | | `Cause.isInterruptedOnly(cause)` | `Cause.hasInterruptsOnly(cause)` | |
| 94 | |
| 95 | ## Constructors |
| 96 | |
| 97 | | v3 | v4 | |
| 98 | | ------------------------------- | ---------------------------- | |
| 99 | | `Cause.empty` | `Cause.empty` | |
| 100 | | `Cause.fail(error)` | `Cause.fail(error)` | |
| 101 | | `Cause.die(defect)` | `Cause.die(defect)` | |
| 102 | | `Cause.interrupt(fiberId)` | `Cause.interrupt(fiberId)` | |
| 103 | | `Cause.sequential(left, right)` | `Cause.combine(left, right)` | |
| 104 | | `Cause.parallel(left, right)` | `Cause.combine(left, right)` | |
| 105 | |
| 106 | In v4, `Cause.combine` concatenates the `reasons` arrays of two causes. The |
| 107 | distinction between sequential and parallel composition is no longer |
| 108 | represented in the data structure. |
| 109 | |
| 110 | ## Extractors |
| 111 | |
| 112 | | v3 | v4 | |
| 113 | | ------------------------------ | ------------------------------------------ | |
| 114 | | `Cause.failureOption(cause)` | `Cause.findErrorOption(cause)` | |
| 115 | | `Cause.failureOrCause(cause)` | `Cause.findError(cause)` | |
| 116 | | `Cause.dieOption(cause)` | `Cause.findDefect(cause)` | |
| 117 | | `Cause.interruptOption(cause)` | `Cause.findInterrupt(cause)` | |
| 118 | | `Cause.failures(cause)` | `cause.reasons.filter(Cause.isFailReason)` | |
| 119 | | `Cause.defects(cause)` | `cause.reasons.filter(Cause.isDieReason)` | |
| 120 | | `Cause.interruptors(cause)` | `Cause.interruptors(cause)` | |
| 121 | |
| 122 | Note: `findError` and `findDefect` return `Result.Result` instead of `Option`. |
| 123 | Use `findErrorOption` for the `Option`-based variant. |
| 124 | |
| 125 | ## Error Classes |
| 126 | |
| 127 | All `*Exception` classes have been renamed to `*Error`: |
| 128 | |
| 129 | | v3 | v4 | |
| 130 | | -------------------------------------- | ----------------------------- | |
| 131 | | `Cause.NoSuchElementException` | `Cause.NoSuchElementError` | |
| 132 | | `Cause.TimeoutException` | `Cause.TimeoutError` | |
| 133 | | `Cause.IllegalArgumentException` | `Cause.IllegalArgumentError` | |
| 134 | | `Cause.ExceededCapacityException` | `Cause.ExceededCapacityError` | |
| 135 | | `Cause.UnknownException` | `Cause.UnknownError` | |
| 136 | | `Cause.RuntimeException` | Removed | |
| 137 | | `Cause.InterruptedException` | Removed | |
| 138 | | `Cause.InvalidPubSubCapacityException` | Removed | |
| 139 | |
| 140 | The corresponding guards follow the same pattern: |
| 141 | |
| 142 | | v3 | v4 | |
| 143 | | -------------------------------------- | ---------------------------------- | |
| 144 | | `Cause.isNoSuchElementException(u)` | `Cause.isNoSuchElementError(u)` | |
| 145 | | `Cause.isTimeoutException(u)` | `Cause.isTimeoutError(u)` | |
| 146 | | `Cause.isIllegalArgumentException(u)` | `Cause.isIllegalArgumentError(u)` | |
| 147 | | `Cause.isExceededCapacityException(u)` | `Cause.isExceededCapacityError(u)` | |
| 148 | | `Cause.isUnknownException(u)` | `Cause.isUnknownError(u)` | |
| 149 | |
| 150 | ## New in v4 |
| 151 | |
| 152 | - **`Cause.fromReasons(reasons)`** — construct a `Cause` from an array of |
| 153 | `Reason` values. |
| 154 | - **`Cause.makeFailReason(error)`**, **`Cause.makeDieReason(defect)`**, |
| 155 | **`Cause.makeInterruptReason(fiberId)`** — construct individual `Reason` |
| 156 | values. |
| 157 | - **`Cause.annotate(cause, annotations)`** — attach annotations to a `Cause`. |
| 158 | - **`Cause.findFail(cause)`**, **`Cause.findDie(cause)`**, |
| 159 | **`Cause.findInterrupt(cause)`** — extract specific reason types using |
| 160 | the `Result` module. |
| 161 | - **`Cause.filterInterruptors(cause)`** — extract interrupting fiber IDs as |
| 162 | a `Result`. |
| 163 | - **`Cause.Done`** — a graceful completion signal for queues and streams. |