@zio.dev/zio-blocks 0.0.33 → 0.0.55

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (215) hide show
  1. package/adr/2026-07-18-data-migration.md +123 -0
  2. package/guides/async-getting-started.md +687 -0
  3. package/guides/compile-time-resource-safety-with-scope.md +21 -16
  4. package/guides/getting-started-with-mux.md +1395 -0
  5. package/guides/query-dsl-extending.md +161 -102
  6. package/guides/query-dsl-fluent-builder.md +217 -157
  7. package/guides/query-dsl-reified-optics.md +12 -10
  8. package/guides/query-dsl-sql.md +640 -165
  9. package/guides/sql-checked-interpolation.md +173 -0
  10. package/guides/sql-transactions.md +286 -0
  11. package/guides/telemetry-guide.md +1130 -0
  12. package/guides/zio-schema-migration.md +29 -22
  13. package/index.md +248 -389
  14. package/package.json +1 -1
  15. package/plans/config-follow-up-prs.md +188 -0
  16. package/plans/config-pr-assessment-roadmap.md +310 -0
  17. package/reference/MuxDataFlow.jsx +250 -0
  18. package/reference/async.md +1499 -0
  19. package/reference/chunk.md +3533 -308
  20. package/reference/codegen/case-class.md +436 -0
  21. package/reference/codegen/emitter-config.md +383 -0
  22. package/reference/codegen/examples.md +664 -0
  23. package/reference/codegen/field.md +316 -0
  24. package/reference/codegen/index.md +317 -0
  25. package/reference/codegen/scala-emitter.md +392 -0
  26. package/reference/codegen/scala-file.md +276 -0
  27. package/reference/codegen/sealed-trait.md +408 -0
  28. package/reference/codegen/type-definition.md +340 -0
  29. package/reference/codegen/type-ref.md +201 -0
  30. package/reference/combinators.md +347 -117
  31. package/reference/config/config-decoder.md +460 -0
  32. package/reference/config/config-source.md +489 -0
  33. package/reference/config/errors.md +278 -0
  34. package/reference/config/flags.md +369 -0
  35. package/reference/config/formats.md +314 -0
  36. package/reference/config/index.md +304 -0
  37. package/reference/config/rollout.md +336 -0
  38. package/reference/context.md +9 -52
  39. package/reference/data-migration.md +269 -0
  40. package/reference/datastar/attributes.md +302 -0
  41. package/reference/datastar/events.md +234 -0
  42. package/reference/datastar/index.md +256 -0
  43. package/reference/datastar/signals.md +230 -0
  44. package/reference/datastar/sse.md +295 -0
  45. package/reference/datastar.md +346 -0
  46. package/reference/docs.md +1461 -345
  47. package/reference/endpoint/auth-type.md +146 -0
  48. package/reference/endpoint/bulk-creation.md +96 -0
  49. package/reference/endpoint/endpoint.md +297 -0
  50. package/reference/endpoint/http-codec.md +249 -0
  51. package/reference/endpoint/index.md +745 -0
  52. package/reference/endpoint/path-codec.md +225 -0
  53. package/reference/endpoint/route-pattern.md +194 -0
  54. package/reference/endpoint/route-tree.md +111 -0
  55. package/reference/endpoint/segment-codec.md +199 -0
  56. package/reference/html.md +1424 -0
  57. package/reference/htmx/attribute-values.md +359 -0
  58. package/reference/htmx/hx-encoding.md +111 -0
  59. package/reference/htmx/hx-params.md +204 -0
  60. package/reference/htmx/hx-swap.md +276 -0
  61. package/reference/htmx/hx-sync.md +251 -0
  62. package/reference/htmx/hx-target.md +314 -0
  63. package/reference/htmx/hx-trigger.md +457 -0
  64. package/reference/htmx/hx-url-update.md +239 -0
  65. package/reference/htmx/index.md +807 -0
  66. package/reference/htmx/response-headers.md +240 -0
  67. package/reference/http-model/headers.md +735 -0
  68. package/reference/http-model/index.md +49 -0
  69. package/reference/http-model/model.md +1517 -0
  70. package/reference/http-model/schema-codecs.md +522 -0
  71. package/reference/http-model/schema.md +750 -0
  72. package/reference/http-model/server-sent-event.md +341 -0
  73. package/reference/jwt.md +195 -0
  74. package/reference/maybe.md +943 -0
  75. package/reference/media-type.md +2 -2
  76. package/reference/mux.md +254 -0
  77. package/reference/mux.mdx +828 -0
  78. package/reference/openapi.md +1351 -0
  79. package/reference/projection.md +654 -0
  80. package/reference/resource-management/defer-handle.md +1 -1
  81. package/reference/resource-management/resource.md +31 -98
  82. package/reference/resource-management/scope.md +28 -220
  83. package/reference/resource-management/wire.md +5 -55
  84. package/reference/ringbuffer/MpmcDiagram.jsx +717 -0
  85. package/reference/ringbuffer/MpscDiagram.jsx +618 -0
  86. package/reference/ringbuffer/SpmcDiagram.jsx +680 -0
  87. package/reference/ringbuffer/SpscDiagram.jsx +677 -0
  88. package/reference/ringbuffer/advanced.mdx +109 -0
  89. package/reference/ringbuffer/index.mdx +145 -0
  90. package/reference/ringbuffer/mpmc.mdx +185 -0
  91. package/reference/ringbuffer/mpsc.mdx +164 -0
  92. package/reference/ringbuffer/spmc.mdx +108 -0
  93. package/reference/ringbuffer/spsc.mdx +416 -0
  94. package/reference/{allows.md → schema/allows.md} +4 -100
  95. package/reference/{binding-resolver.md → schema/binding-resolver.md} +1 -1
  96. package/reference/{binding.md → schema/binding.md} +3 -4
  97. package/reference/schema/built-in-codecs/avro.md +451 -0
  98. package/reference/schema/built-in-codecs/bson.md +510 -0
  99. package/reference/schema/built-in-codecs/csv.md +564 -0
  100. package/reference/schema/built-in-codecs/index.md +77 -0
  101. package/reference/schema/built-in-codecs/json/index.md +295 -0
  102. package/reference/schema/built-in-codecs/json/json-config.md +217 -0
  103. package/reference/{json-patch.md → schema/built-in-codecs/json/json-patch.md} +5 -5
  104. package/reference/{json-schema.md → schema/built-in-codecs/json/json-schema.md} +14 -47
  105. package/reference/schema/built-in-codecs/json/json-selection.md +322 -0
  106. package/reference/{json.md → schema/built-in-codecs/json/json.md} +32 -64
  107. package/reference/schema/built-in-codecs/messagepack.md +508 -0
  108. package/reference/schema/built-in-codecs/thrift.md +433 -0
  109. package/reference/schema/built-in-codecs/toon.md +1078 -0
  110. package/reference/{xml.md → schema/built-in-codecs/xml.md} +13 -9
  111. package/reference/schema/built-in-codecs/yaml.md +552 -0
  112. package/reference/{codec.md → schema/codec.md} +11 -11
  113. package/reference/{dynamic-optic.md → schema/dynamic-optic.md} +196 -5
  114. package/reference/{dynamic-schema.md → schema/dynamic-schema.md} +8 -8
  115. package/reference/schema/format.md +92 -0
  116. package/reference/schema/index.md +52 -0
  117. package/reference/schema/migration.md +297 -0
  118. package/reference/{modifier.md → schema/modifier.md} +58 -7
  119. package/reference/{optics.md → schema/optics.md} +2 -2
  120. package/reference/{patch.md → schema/patch.md} +1 -1
  121. package/{path-interpolator.md → reference/schema/path-interpolator.md} +167 -72
  122. package/reference/schema/reflect-transformer.md +140 -0
  123. package/reference/{schema-evolution → schema/schema-evolution}/as.md +8 -8
  124. package/reference/{schema-evolution → schema/schema-evolution}/index.md +2 -2
  125. package/reference/{schema-evolution → schema/schema-evolution}/into.md +8 -8
  126. package/reference/{schema-expr.md → schema/schema-expr.md} +110 -175
  127. package/reference/schema/schema-search.md +263 -0
  128. package/reference/{schema.md → schema/schema.md} +22 -2
  129. package/reference/{structural-types.md → schema/structural-types.md} +1 -1
  130. package/reference/{type-class-derivation.md → schema/type-class-derivation.md} +63 -1
  131. package/reference/smithy.md +1032 -0
  132. package/reference/sql/db-codec-deriver.md +71 -0
  133. package/reference/sql/db-codec.md +687 -0
  134. package/reference/sql/db-con.md +271 -0
  135. package/reference/sql/db-connection.md +153 -0
  136. package/reference/sql/db-param-writer.md +77 -0
  137. package/reference/sql/db-param.md +66 -0
  138. package/reference/sql/db-result-reader.md +148 -0
  139. package/reference/sql/db-tx.md +114 -0
  140. package/reference/sql/db-value.md +41 -0
  141. package/reference/sql/ddl.md +85 -0
  142. package/reference/sql/frag.md +288 -0
  143. package/reference/sql/index.md +341 -0
  144. package/reference/sql/repo.md +600 -0
  145. package/reference/sql/sql-dialect.md +73 -0
  146. package/reference/sql/sql-logger.md +62 -0
  147. package/reference/sql/sql-name-mapper.md +70 -0
  148. package/reference/sql/table-metadata.md +134 -0
  149. package/reference/sql/table.md +448 -0
  150. package/reference/sql/transactor-zio.md +399 -0
  151. package/reference/sql/transactor.md +363 -0
  152. package/reference/sql-zio.md +112 -0
  153. package/reference/streams/core/index.md +32 -0
  154. package/reference/streams/core/pipeline.md +854 -0
  155. package/reference/streams/core/sink.md +1404 -0
  156. package/reference/streams/core/stream.md +3236 -0
  157. package/reference/streams/execution-and-compatibility/async-execution.md +822 -0
  158. package/reference/streams/execution-and-compatibility/index.md +35 -0
  159. package/reference/streams/execution-and-compatibility/platform-differences.md +297 -0
  160. package/reference/streams/execution-and-compatibility/scala-2-compatibility.md +88 -0
  161. package/reference/streams/execution-and-compatibility/zero-boxing.md +393 -0
  162. package/reference/streams/index.md +726 -0
  163. package/reference/streams/primitives/index.md +30 -0
  164. package/reference/streams/primitives/reader.md +1992 -0
  165. package/reference/streams/primitives/writer.md +1201 -0
  166. package/reference/telemetry/common/any-value.md +90 -0
  167. package/reference/telemetry/common/attribute-key.md +87 -0
  168. package/reference/telemetry/common/attributes.md +118 -0
  169. package/reference/telemetry/common/index.md +39 -0
  170. package/reference/telemetry/common/instrumentation-scope.md +24 -0
  171. package/reference/telemetry/common/resource.md +34 -0
  172. package/reference/telemetry/index.md +311 -0
  173. package/reference/telemetry/logging/index.md +197 -0
  174. package/reference/telemetry/logging/log-enrichment.md +72 -0
  175. package/reference/telemetry/logging/log-formatter.md +100 -0
  176. package/reference/telemetry/logging/log-record-processor.md +56 -0
  177. package/reference/telemetry/logging/log-record.md +44 -0
  178. package/reference/telemetry/logging/log-writer.md +64 -0
  179. package/reference/telemetry/logging/logger-provider.md +142 -0
  180. package/reference/telemetry/logging/logger.md +83 -0
  181. package/reference/telemetry/logging/severity.md +62 -0
  182. package/reference/telemetry/metrics/index.md +150 -0
  183. package/reference/telemetry/metrics/instruments.md +183 -0
  184. package/reference/telemetry/metrics/labeled-instruments.md +74 -0
  185. package/reference/telemetry/metrics/meter-provider.md +76 -0
  186. package/reference/telemetry/metrics/meter.md +98 -0
  187. package/reference/telemetry/metrics/metric-data.md +57 -0
  188. package/reference/telemetry/otel/custom-exporter.md +216 -0
  189. package/reference/telemetry/otel/index.md +212 -0
  190. package/reference/telemetry/tracing/index.md +155 -0
  191. package/reference/telemetry/tracing/sampler.md +89 -0
  192. package/reference/telemetry/tracing/span-builder.md +57 -0
  193. package/reference/telemetry/tracing/span-context.md +39 -0
  194. package/reference/telemetry/tracing/span-data.md +32 -0
  195. package/reference/telemetry/tracing/span-kind.md +55 -0
  196. package/reference/telemetry/tracing/span-processor.md +53 -0
  197. package/reference/telemetry/tracing/span-status.md +47 -0
  198. package/reference/telemetry/tracing/span.md +117 -0
  199. package/reference/telemetry/tracing/tracer-provider.md +91 -0
  200. package/reference/telemetry/tracing/tracer.md +52 -0
  201. package/reference/typeid.md +5 -83
  202. package/sidebars.js +376 -43
  203. package/undocumented-report.md +528 -270
  204. package/reference/formats.md +0 -694
  205. package/reference/http-model.md +0 -1716
  206. package/reference/streams.md +0 -989
  207. package/ringbuffer.md +0 -249
  208. /package/reference/{json-differ.md → schema/built-in-codecs/json/json-differ.md} +0 -0
  209. /package/reference/{dynamic-value.md → schema/dynamic-value.md} +0 -0
  210. /package/reference/{lazy.md → schema/lazy.md} +0 -0
  211. /package/reference/{reflect.md → schema/reflect.md} +0 -0
  212. /package/reference/{registers.md → schema/registers.md} +0 -0
  213. /package/reference/{schema-error.md → schema/schema-error.md} +0 -0
  214. /package/reference/{syntax.md → schema/syntax.md} +0 -0
  215. /package/reference/{validation.md → schema/validation.md} +0 -0
@@ -0,0 +1,943 @@
1
+ ---
2
+ id: maybe
3
+ title: "Maybe"
4
+ ---
5
+
6
+ `Maybe[A]` is a **low-allocation alternative to `Option[A]`** that uses a top-level `Absent` sentinel object to represent the absence of a value. On Scala 3, it is an opaque type alias for `A | Absent.type | Present[A]`, where `Present[A]` is a public wrapper allocated whenever a present value would otherwise be indistinguishable from absence — nested `Maybe`s (`Maybe.present(Maybe.absent)` → `Present(Absent)`) and `null` values (`Maybe.present(null)` → `Present(null)`). On Scala 2.13, it is a sealed trait (`Present[A]` | `Absent`). Core types: `Maybe[A]`, `Present[A]`.
7
+
8
+ Here's the type definition and basic construction:
9
+
10
+ ```scala
11
+ // Scala 3
12
+ final class Present[+A](val value: A) // manual companion: apply + unapply that matches both raw values and wrappers
13
+ object Absent
14
+ opaque type Maybe[+A] = A | Absent.type | Present[A]
15
+
16
+ val present: Maybe[Int] = Maybe.present(42)
17
+ val absent: Maybe[Int] = Maybe.absent
18
+ ```
19
+
20
+ ## Allocation Profile
21
+
22
+ The new encoding minimizes allocation by using the raw value when possible:
23
+
24
+ | Case | Example | Allocations |
25
+ |------|---------|-------------|
26
+ | Flat present | `Maybe.present(42)` | **0** (raw `42`) |
27
+ | Flat apply | `Maybe(42)` | **0** (raw `42`) |
28
+ | Flat fromOption | `Maybe.fromOption(Some(42))` | **0** (raw `42`) |
29
+ | Absent | `Maybe.absent` | **0** (`Absent` singleton) |
30
+ | Present-of-absent | `Maybe.present(Maybe.absent)` | **1** (`Present(Absent)`) |
31
+ | Present null | `Maybe.fromOption(Some(null))` | **1** (`Present(null)`) |
32
+
33
+ The `Present[A]` wrapper is allocated **only** for present-of-absent cases: wrapping a nested `Maybe` that is itself absent (`Present(Absent)`) and wrapping a `null` value (`Present(null)`). All other flat cases store the value raw with zero allocation overhead.
34
+
35
+ ### Nesting Depth
36
+
37
+ Nested `Maybe` values reduce wrapper depth by 1 compared to `Option`:
38
+
39
+ ```scala
40
+ import zio.blocks.maybe._
41
+
42
+ // Option: Some(None) = 2 wrappers
43
+ val optNested: Option[Option[Int]] = Some(None)
44
+
45
+ // Maybe: Present(Absent) = 1 wrapper (Present) + Absent singleton
46
+ val maybeNested: Maybe[Maybe[Int]] = Maybe.present(Maybe.absent[Int])
47
+ // maybeNested matches Present(Absent), not Absent
48
+
49
+ // Flattening removes the Present wrapper
50
+ val flat: Maybe[Int] = maybeNested.flatten
51
+ // flat is absent (Absent)
52
+ ```
53
+
54
+ ## Motivation
55
+
56
+ When working with optional values, you face a choice: `Option[A]` provides type safety and functional composition but allocates a wrapper object for every value. `Maybe[A]` provides an alternative with different trade-offs depending on your Scala version.
57
+
58
+ **On Scala 3:** `Maybe[A]` eliminates allocation overhead by leveraging union types and a top-level `Absent` sentinel. The type is an opaque alias for `A | Absent.type | Present[A]`, where `Present[A]` is a public wrapper allocated whenever a present value would otherwise be indistinguishable from absence. Flat values (non-nested, non-null) are stored raw with zero allocation; only present-of-absent values allocate a `Present` wrapper (`Maybe.present(Maybe.absent)` → `Present(Absent)`, `Maybe.present(null)` → `Present(null)`). This gives you a dedicated API (`map`, `flatMap`, `filter`, etc.) with minimal runtime overhead. `case Absent` is a stable-identifier pattern that works directly.
59
+
60
+ **On Scala 2.13:** `Maybe[A]` is implemented as a sealed trait (`Present[A]` | `Absent`). Present values allocate a wrapper, so the allocation savings versus `Option` are less pronounced. However, the unified API and interoperability benefits still apply.
61
+
62
+ ### Why Maybe over Option?
63
+
64
+ - **Zero allocation (flat case)**: Non-nested `Maybe` values are either the raw value itself or the `Absent` singleton—no wrapper objects
65
+ - **Sound nesting**: Nested `Maybe[Maybe[A]]` is now sound via `Present[A]`, without requiring a compile-time guard
66
+ - **Familiar API**: All your favorite `Option` combinators (`map`, `flatMap`, `fold`, etc.)
67
+ - **Type safety**: The opaque type prevents accidentally mixing nullable and non-nullable values
68
+ - **Interoperable**: Seamless conversion to/from `Option` with `toOption` and `Maybe#fromOption`
69
+
70
+ ## Cross-Version Parity
71
+
72
+ The API surface is consistent across Scala 2.13 and Scala 3, but the underlying encoding differs:
73
+
74
+ | Aspect | Scala 3 | Scala 2.13 |
75
+ |--------|---------|------------|
76
+ | Encoding | `opaque type Maybe[+A] = A \| Absent.type \| Present[A]` | Sealed trait `Present[A]` \| `Absent` |
77
+ | Flat allocation | **0** (raw value or `Absent` singleton) | **1** (wrapper object) |
78
+ | Nested allocation | **1** (`Present(Absent)` only) | **1** (wrapper object) |
79
+ | `Present` type | Public `final class Present[+A]` with manual companion (`apply`/`unapply`) | `MaybeValue.Present[A]` (sealed) |
80
+ | Nesting guard | **Removed** (nesting now sound via `Present`) | N/A (never existed) |
81
+
82
+ ### Behavior Differences
83
+
84
+ - **`Maybe.present(null)`**: On Scala 3, produces `Present(null)` (present-of-absent). On Scala 2.13, produces `MaybeValue.Present(null)` (also present-of-absent). Both are distinguishable from `Maybe.absent`.
85
+ - **`Maybe.fromOption(Some(null))`**: Routes through `present`, so same behavior as above.
86
+ - **Pattern matching**: On Scala 3, `Present(v)` matches both present shapes (a raw value and a `Present(...)` wrapper); absent matches `case Absent` (stable-identifier pattern, no `case _` required inside the package; external two-case matches also compile without exhaustivity warning). On Scala 2.13, `MaybeValue.Present(v)` / `MaybeValue.Absent` are the compiler-checked native patterns.
87
+
88
+ ## Pattern Matching
89
+
90
+ On Scala 3, a `Maybe[A]` value can take three runtime shapes. The `Present` companion's `unapply` collapses the two present shapes into one pattern; absent is now a real top-level singleton object:
91
+
92
+ | Shape | Pattern | Meaning |
93
+ |-------|---------|---------|
94
+ | `Absent` object | `case Absent` | absent (stable-identifier pattern) |
95
+ | `Present(v)` wrapper | `case Present(v)` | present-of-absent (a nested `Maybe`) |
96
+ | raw `v` | `case Present(v)` | present, zero allocation |
97
+
98
+ (Note: the `Present` companion's `unapply` collapses both present shapes — one `Some` allocation per present match.)
99
+
100
+ ```scala
101
+ import zio.blocks.maybe._
102
+
103
+ val maybe: Maybe[Int] = Maybe.present(42)
104
+
105
+ val description: String = maybe match {
106
+ case Present(v) => s"present ($v)"
107
+ case Absent => "absent"
108
+ }
109
+ ```
110
+
111
+ `case Absent` is a stable-identifier pattern that works because `Absent` is a plain top-level object (not a case object). The two-case match `case Present(v); case Absent` compiles without exhaustivity warning even from an external package — verified under this build's `-Xfatal-warnings` settings (`WildcardImportSpec`). Note that this is weaker than the sealed-hierarchy guarantee on Scala 2.13 below: exhaustivity here depends on the compiler decomposing the opaque union, so prefer `fold` when you need a guarantee that is independent of compiler behavior. `Present(v)` matches both a raw value and a `Present(...)` wrapper, at the cost of one `Some` allocation per present match (inherent to the `Option`-returning extractor protocol).
112
+
113
+ For production code, prefer `fold`, which is exhaustive, warning-free, and zero-allocation:
114
+
115
+ ```scala
116
+ import zio.blocks.maybe._
117
+
118
+ val maybe: Maybe[Int] = Maybe.present(42)
119
+ val description: String = maybe.fold("absent")(v => s"present ($v)")
120
+ ```
121
+
122
+ Scala 2.13 parity: on Scala 2.13, absent is the non-null case object `MaybeValue.Absent`, so it **can** be matched explicitly — the sealed `MaybeValue` trait (`Present` | `Absent`) gives compiler-checked exhaustivity:
123
+
124
+ ```scala
125
+ import zio.blocks.maybe._
126
+
127
+ val maybe: Maybe[Int] = Maybe.present(42)
128
+ val description: String = maybe match {
129
+ case MaybeValue.Present(v) => s"present ($v)"
130
+ case MaybeValue.Absent => "absent"
131
+ }
132
+ ```
133
+
134
+ So on Scala 2.13 the sealed `MaybeValue` form is the compiler-checked native idiom; on Scala 3 the opaque union with `Absent` object enables `case Absent` while preserving the zero-allocation encoding.
135
+
136
+ ## Installation
137
+
138
+ Add the `zio-blocks-maybe` module to your build:
139
+
140
+ ```scala
141
+ libraryDependencies += "dev.zio" %% "zio-blocks-maybe" % "0.0.55"
142
+ ```
143
+
144
+ For Scala.js:
145
+
146
+ ```scala
147
+ libraryDependencies += "dev.zio" %%% "zio-blocks-maybe" % "0.0.55"
148
+ ```
149
+
150
+ Supported Scala versions: 2.13.x and 3.x
151
+
152
+ ## Overview
153
+
154
+ `Maybe[A]` provides a complete set of operations for working with optional values:
155
+
156
+ - **Constructors**: `Maybe.apply`, `Maybe.present`, `Maybe.absent`, `Maybe.fromOption`
157
+ - **Predicates**: `Maybe#isAbsent`, `isPresent`, `isEmpty`, `isDefined`, `nonEmpty`
158
+ - **Access**: `get`, `Maybe#getOrElse`, `orElse`, `orNull`
159
+ - **Transformations**: `map`, `flatMap`, `flatten`
160
+ - **Filtering**: `filter`, `filterNot`, `collect`
161
+ - **Logical**: `exists`, `forall`, `contains`
162
+ - **Conversions**: `toOption`, `toList`, `toSeq`, `iterator`, `toRight`, `toLeft`
163
+ - **Composition**: `Maybe#zip`, `unzip`, `unzip3`, `fold`, `foreach`
164
+
165
+ ## How It Works
166
+
167
+ The workflow is straightforward: create a `Maybe` from a value or `None`, transform and filter it using functional operations, extract the result with safe accessors, and fall back to defaults when needed:
168
+
169
+ ```
170
+ Value ─→ Maybe.apply (wrap) ─→ map / flatMap (transform)
171
+ ↓ ↓
172
+ [null] ←─────── isAbsent (test) ← filter / collect (refine)
173
+ ↓ ↓
174
+ absent ─→ orElse (fallback) ─→ get / getOrElse (extract)
175
+ ```
176
+
177
+ ### Typical Data Flow
178
+
179
+ 1. **Create** a `Maybe` using `Maybe.apply`, `Maybe.present`, `Maybe.fromOption`, or explicitly with `Maybe.absent` for no value
180
+ 2. **Transform** values using `map` or `flatMap` — operations on absent values short-circuit and remain absent
181
+ 3. **Filter** with `filter` or `collect` to refine the value or produce absence based on a predicate
182
+ 4. **Compose** with other `Maybe` values using `Maybe#zip` or flatMap chains to build complex workflows
183
+ 5. **Extract** the result using `get` (throws on absence), `Maybe#getOrElse` (provides a default), `toOption` (convert to `Option`), or `fold` (handle both branches)
184
+
185
+ ## Common Patterns
186
+
187
+ Here are key patterns for working effectively with `Maybe`:
188
+
189
+ ### Present and Absent States
190
+
191
+ Every `Maybe` is either present (holds a value, possibly `null`) or absent (the `Absent` singleton). Test the state with predicates:
192
+
193
+ ```scala
194
+ import zio.blocks.maybe._
195
+
196
+ val value: Maybe[Int] = Maybe.present(42)
197
+
198
+ if (value.isPresent) {
199
+ println(value.get) // Safe: we know it's present
200
+ } else {
201
+ println("No value")
202
+ }
203
+ ```
204
+
205
+ ### Safe Extraction with Defaults
206
+
207
+ Use `Maybe#getOrElse` to provide a fallback when the value is absent, avoiding the exception risk of `get`:
208
+
209
+ ```scala
210
+ import zio.blocks.maybe._
211
+
212
+ val userId: Maybe[String] = Maybe.absent
213
+ val name = userId.getOrElse("anonymous")
214
+ ```
215
+
216
+ ### Chaining Transformations
217
+
218
+ Combine `map` and `flatMap` to thread operations through optional values. Absence propagates automatically:
219
+
220
+ ```scala
221
+ import zio.blocks.maybe._
222
+
223
+ val userId: Maybe[Int] = Maybe.present(123)
224
+
225
+ val greeting = userId
226
+ .map(id => s"User $id")
227
+ .map(msg => s"Hello, $msg!")
228
+
229
+ println(greeting) // Maybe[String] containing "Hello, User 123!"
230
+ ```
231
+
232
+ ### Filtering with Predicates
233
+
234
+ Use `filter` to keep a value only if it satisfies a condition, or `collect` with a partial function for both filtering and transformation:
235
+
236
+ ```scala
237
+ import zio.blocks.maybe._
238
+
239
+ val age: Maybe[Int] = Maybe.present(25)
240
+
241
+ val isAdult = age.filter(_ >= 18) // Maybe[Int] containing 25
242
+ val isChild = age.filter(_ < 18) // Maybe[Int] absent
243
+ ```
244
+
245
+ ### Combining Multiple Maybes
246
+
247
+ Use `Maybe#zip` to combine two `Maybe` values into a tuple, or chain multiple operations with `flatMap`:
248
+
249
+ ```scala
250
+ import zio.blocks.maybe._
251
+
252
+ val firstName: Maybe[String] = Maybe.present("Alice")
253
+ val lastName: Maybe[String] = Maybe.present("Smith")
254
+
255
+ val fullName = firstName.zip(lastName).map { case (f, l) => s"$f $l" }
256
+ ```
257
+
258
+ ### Converting to and from Option
259
+
260
+ Interoperate seamlessly with `Option` using `toOption` and `Maybe#fromOption`:
261
+
262
+ ```scala
263
+ import zio.blocks.maybe._
264
+
265
+ val opt: Option[String] = Some("value")
266
+ val m: Maybe[String] = Maybe.fromOption(opt)
267
+ val backToOpt = m.toOption
268
+ ```
269
+
270
+ ### Handling Errors with Either
271
+
272
+ Convert a `Maybe` to an `Either` to propagate errors in fail-fast computations:
273
+
274
+ ```scala
275
+ import zio.blocks.maybe._
276
+
277
+ val value: Maybe[Int] = Maybe.present(10)
278
+
279
+ val result: Either[String, Int] = value.toRight("Value not found")
280
+ ```
281
+
282
+ ## Integration Points
283
+
284
+ `Maybe[A]` integrates with the broader Scala and ZIO ecosystem:
285
+
286
+ - **Option interoperability**: Convert bidirectionally with `toOption` and `Maybe#fromOption`; a `Conversion[Option[A], Maybe[A]]` is provided for implicit conversions
287
+ - **Schema support**: Schema codecs use private unsafe methods (`unsafeIsAbsent`, `unsafeGet`, `unsafeWrap`) for efficient serialization and deserialization
288
+ - **For-comprehensions**: Supports `withFilter` for guard clauses in for-expressions
289
+ - **Collections**: Convert to `List`, `Seq`, or `Iterator` for bulk operations
290
+ - **Pattern matching**: Works naturally in match expressions, though testing with predicates is more common
291
+
292
+ ### Example: For-Comprehension with Guards
293
+
294
+ Combine multiple `Maybe` values with filter guards:
295
+
296
+ ```scala
297
+ import zio.blocks.maybe._
298
+
299
+ val maybeX: Maybe[Int] = Maybe.present(5)
300
+ val maybeY: Maybe[Int] = Maybe.present(10)
301
+
302
+ val result = for {
303
+ x <- maybeX
304
+ y <- maybeY
305
+ if x + y > 10
306
+ } yield x + y
307
+
308
+ println(result) // Maybe[Int] containing 15
309
+ ```
310
+
311
+ ## Operations Reference
312
+
313
+ All `Maybe` operations are organized by category. Each subsection documents a group of related methods with examples.
314
+
315
+ ### Constructors
316
+
317
+ Create `Maybe` values using these factory methods:
318
+
319
+ #### Maybe.apply
320
+
321
+ Wraps a value in `Maybe`, treating `null` as `Maybe.absent`:
322
+
323
+ ```scala
324
+ import zio.blocks.maybe._
325
+
326
+ val present: Maybe[Int] = Maybe(42) // Maybe[Int] containing 42
327
+ val absent: Maybe[String] = Maybe(null.asInstanceOf[String]) // Maybe.absent (the Absent object)
328
+ ```
329
+
330
+ > **Note:** `Maybe.apply` collapses `null` to `Maybe.absent`. Use `Maybe.present` when you need to preserve present-ness even for `null` values (e.g., in nested `Maybe`s).
331
+
332
+ #### Maybe.present
333
+
334
+ Explicitly wraps a value, preserving present-ness even for `null`:
335
+
336
+ ```scala
337
+ import zio.blocks.maybe._
338
+
339
+ val value: Maybe[Int] = Maybe.present(100)
340
+
341
+ // Nested case: present of an absent Maybe produces Present(Absent), not absence
342
+ val nested: Maybe[Maybe[Int]] = Maybe.present(Maybe.absent[Int])
343
+ // nested is Present(Absent), distinguishable from Maybe.absent
344
+ ```
345
+
346
+ > **Note:** Unlike `Maybe.apply`, `Maybe.present` preserves present-ness even for `null`. A non-null value is returned as-is (zero allocation). A `null` value is wrapped in `Present(null)`, which is distinguishable from `Maybe.absent` (the `Absent` object). This is what makes nested `Maybe`s sound.
347
+
348
+ #### Maybe.absent
349
+
350
+ Creates an absent value for any type:
351
+
352
+ ```scala
353
+ import zio.blocks.maybe._
354
+
355
+ val empty: Maybe[String] = Maybe.absent
356
+ ```
357
+
358
+ #### Maybe.empty
359
+
360
+ Alias for `Maybe.absent`:
361
+
362
+ ```scala
363
+ import zio.blocks.maybe._
364
+
365
+ val empty: Maybe[Double] = Maybe.empty
366
+ ```
367
+
368
+ #### Maybe.fromOption
369
+
370
+ Converts an `Option` to `Maybe`:
371
+
372
+ ```scala
373
+ import zio.blocks.maybe._
374
+
375
+ val fromSome = Maybe.fromOption(Some(5)) // Maybe[Int] containing 5
376
+ val fromNone = Maybe.fromOption(None) // Maybe[Nothing] absent
377
+ ```
378
+
379
+ ### State Testing
380
+
381
+ Test whether a `Maybe` is present or absent:
382
+
383
+ #### isPresent, isDefined, nonEmpty
384
+
385
+ All three are equivalent—return true if the value is non-null:
386
+
387
+ ```scala
388
+ import zio.blocks.maybe._
389
+
390
+ val value: Maybe[Int] = Maybe.present(42)
391
+ println(value.isPresent) // true
392
+ println(value.isDefined) // true
393
+ println(value.nonEmpty) // true
394
+ ```
395
+
396
+ #### isAbsent, isEmpty
397
+
398
+ Both return true if the value is `null`:
399
+
400
+ ```scala
401
+ import zio.blocks.maybe._
402
+
403
+ val empty: Maybe[Int] = Maybe.absent
404
+ println(empty.isAbsent) // true
405
+ println(empty.isEmpty) // true
406
+ ```
407
+
408
+ ### Access
409
+
410
+ Retrieve the value or provide a fallback:
411
+
412
+ #### get
413
+
414
+ Unwraps the value, throwing `NoSuchElementException` if absent:
415
+
416
+ ```scala
417
+ import zio.blocks.maybe._
418
+
419
+ val value: Maybe[Int] = Maybe.present(10)
420
+ println(value.get) // 10
421
+
422
+ try {
423
+ Maybe.absent[Int].get
424
+ } catch {
425
+ case e: NoSuchElementException => println(e.getMessage) // "Maybe.absent.get"
426
+ }
427
+ ```
428
+
429
+ #### getOrElse
430
+
431
+ Returns the value if present, or evaluates the default:
432
+
433
+ ```scala
434
+ import zio.blocks.maybe._
435
+
436
+ val value: Maybe[Int] = Maybe.absent
437
+ val withDefault: Int = value.getOrElse(99)
438
+ println(withDefault) // 99
439
+ ```
440
+
441
+ #### orElse
442
+
443
+ Returns the current `Maybe` if present, or another `Maybe`:
444
+
445
+ ```scala
446
+ import zio.blocks.maybe._
447
+
448
+ val first: Maybe[Int] = Maybe.absent
449
+ val second: Maybe[Int] = Maybe.present(42)
450
+ val result = first.orElse(second)
451
+ println(result.get) // 42
452
+ ```
453
+
454
+ #### orNull
455
+
456
+ Converts to nullable, returning the value or `null`:
457
+
458
+ ```scala
459
+ import zio.blocks.maybe._
460
+
461
+ val value: Maybe[String] = Maybe.absent
462
+ val nullable: String = value.orNull
463
+ println(nullable) // null
464
+ ```
465
+
466
+ ### Transformations
467
+
468
+ Transform values or short-circuit on absence:
469
+
470
+ #### map
471
+
472
+ Applies a function if present, remains absent otherwise:
473
+
474
+ ```scala
475
+ import zio.blocks.maybe._
476
+
477
+ val value: Maybe[Int] = Maybe.present(5)
478
+ val doubled: Maybe[Int] = value.map(_ * 2)
479
+ val absent: Maybe[String] = Maybe.absent.map(_ => "never runs")
480
+ println(doubled.get) // 10
481
+ println(absent.isAbsent) // true
482
+ ```
483
+
484
+ #### flatMap
485
+
486
+ Chains operations that return `Maybe`, flattening the result:
487
+
488
+ ```scala
489
+ import zio.blocks.maybe._
490
+
491
+ def safeDivide(a: Int, b: Int): Maybe[Int] =
492
+ if (b == 0) Maybe.absent else Maybe(a / b)
493
+
494
+ val result = Maybe.present(10).flatMap(safeDivide(_, 2))
495
+ println(result.get) // 5
496
+
497
+ val divideByZero = Maybe.present(10).flatMap(safeDivide(_, 0))
498
+ println(divideByZero.isAbsent) // true
499
+ ```
500
+
501
+ #### flatten
502
+
503
+ Unwraps a nested `Maybe`:
504
+
505
+ ```scala
506
+ import zio.blocks.maybe._
507
+
508
+ val nested: Maybe[Maybe[Int]] = Maybe.fromOption(Some(Maybe.fromOption(Some(42))))
509
+ val flat: Maybe[Int] = nested.flatten
510
+ println(flat.get) // 42
511
+
512
+ // Nested present-of-absent flattens to absent
513
+ val nestedAbsent: Maybe[Maybe[Int]] = Maybe.present(Maybe.absent[Int])
514
+ val flatAbsent: Maybe[Int] = nestedAbsent.flatten
515
+ println(flatAbsent.isAbsent) // true
516
+ ```
517
+
518
+ ### Filtering
519
+
520
+ Keep or discard values based on predicates:
521
+
522
+ #### filter
523
+
524
+ Keeps the value only if the predicate is true:
525
+
526
+ ```scala
527
+ import zio.blocks.maybe._
528
+
529
+ val value: Maybe[Int] = Maybe.present(10)
530
+ val even = value.filter(_ % 2 == 0) // Present: 10
531
+ val odd = value.filter(_ % 2 != 0) // Absent
532
+ println(even.get) // 10
533
+ println(odd.isAbsent) // true
534
+ ```
535
+
536
+ #### filterNot
537
+
538
+ Inverse of `filter`—keeps the value if the predicate is false:
539
+
540
+ ```scala
541
+ import zio.blocks.maybe._
542
+
543
+ val value: Maybe[Int] = Maybe.present(5)
544
+ val notOdd: Maybe[Int] = value.filterNot(_ % 2 != 0)
545
+ println(notOdd.isAbsent) // true
546
+ ```
547
+
548
+ #### collect
549
+
550
+ Combines filtering with transformation using a partial function:
551
+
552
+ ```scala
553
+ import zio.blocks.maybe._
554
+
555
+ val value: Maybe[Int] = Maybe.present(8)
556
+ val result = value.collect { case n if n % 2 == 0 => s"even: $n" }
557
+ println(result.get) // "even: 8"
558
+
559
+ val odd: Maybe[Int] = Maybe.present(7)
560
+ val noMatch: Maybe[String] = odd.collect { case n if n % 2 == 0 => s"even: $n" }
561
+ println(noMatch.isAbsent) // true
562
+ ```
563
+
564
+ ### Logical Predicates
565
+
566
+ Test conditions without extracting the value:
567
+
568
+ #### contains
569
+
570
+ Returns true if the value equals the given element:
571
+
572
+ ```scala
573
+ import zio.blocks.maybe._
574
+
575
+ val value: Maybe[Int] = Maybe.present(42)
576
+ println(value.contains(42)) // true
577
+ println(value.contains(41)) // false
578
+ println(Maybe.absent[Int].contains(42)) // false
579
+ ```
580
+
581
+ #### exists
582
+
583
+ Returns true if the value satisfies the predicate:
584
+
585
+ ```scala
586
+ import zio.blocks.maybe._
587
+
588
+ val value: Maybe[Int] = Maybe.present(10)
589
+ println(value.exists(_ > 5)) // true
590
+ println(value.exists(_ > 20)) // false
591
+ println(Maybe.absent[Int].exists(_ => true)) // false
592
+ ```
593
+
594
+ #### forall
595
+
596
+ Returns true if the value satisfies the predicate, or if absent (vacuous truth):
597
+
598
+ ```scala
599
+ import zio.blocks.maybe._
600
+
601
+ val value: Maybe[Int] = Maybe.present(10)
602
+ println(value.forall(_ > 5)) // true
603
+ println(value.forall(_ > 20)) // false
604
+ println(Maybe.absent[Int].forall(_ => false)) // true (absent = vacuously true)
605
+ ```
606
+
607
+ ### Iteration and Conversion
608
+
609
+ Convert `Maybe` to other types or iterate its contents:
610
+
611
+ #### foreach
612
+
613
+ Executes a side effect if present:
614
+
615
+ ```scala
616
+ import zio.blocks.maybe._
617
+
618
+ val value: Maybe[Int] = Maybe.present(42)
619
+ value.foreach(x => println(s"Value: $x"))
620
+
621
+ Maybe.absent[Int].foreach(_ => println("not called"))
622
+ ```
623
+
624
+ #### toOption
625
+
626
+ Converts to `Option[A]`:
627
+
628
+ ```scala
629
+ import zio.blocks.maybe._
630
+
631
+ val value: Maybe[Int] = Maybe.present(7)
632
+ val some: Option[Int] = value.toOption
633
+ val noneVal: Option[Int] = Maybe.absent[Int].toOption
634
+ println(some) // Some(7)
635
+ println(noneVal) // None
636
+ ```
637
+
638
+ #### toList
639
+
640
+ Converts to `List[A]`:
641
+
642
+ ```scala
643
+ import zio.blocks.maybe._
644
+
645
+ val value: Maybe[Int] = Maybe.present(5)
646
+ val list: List[Int] = value.toList
647
+ val emptyList: List[Int] = Maybe.absent[Int].toList
648
+ println(list) // List(5)
649
+ println(emptyList) // List()
650
+ ```
651
+
652
+ #### toSeq
653
+
654
+ Converts to `Seq[A]`:
655
+
656
+ ```scala
657
+ import zio.blocks.maybe._
658
+
659
+ val value: Maybe[String] = Maybe.present("hello")
660
+ val seq: Seq[String] = value.toSeq
661
+ println(seq) // Seq("hello")
662
+ ```
663
+
664
+ #### iterator
665
+
666
+ Creates an iterator over the value:
667
+
668
+ ```scala
669
+ import zio.blocks.maybe._
670
+
671
+ val value: Maybe[Int] = Maybe.present(42)
672
+ val it = value.iterator
673
+ println(it.toList) // List(42)
674
+
675
+ Maybe.absent[Int].iterator.toList // List()
676
+ ```
677
+
678
+ ### Either Conversion
679
+
680
+ Convert `Maybe` to `Either` for error handling:
681
+
682
+ #### toRight
683
+
684
+ Converts to `Either[X, A]` with a left error value:
685
+
686
+ ```scala
687
+ import zio.blocks.maybe._
688
+
689
+ val value: Maybe[Int] = Maybe.present(10)
690
+ val right: Either[String, Int] = value.toRight("not found")
691
+ println(right) // Right(10)
692
+
693
+ val absent: Maybe[Int] = Maybe.absent
694
+ val left: Either[String, Int] = absent.toRight("not found")
695
+ println(left) // Left("not found")
696
+ ```
697
+
698
+ #### toLeft
699
+
700
+ Converts to `Either[A, X]` with a right success value:
701
+
702
+ ```scala
703
+ import zio.blocks.maybe._
704
+
705
+ val value: Maybe[String] = Maybe.present("error")
706
+ val left: Either[String, Unit] = value.toLeft(())
707
+ println(left) // Left("error")
708
+
709
+ val absent: Maybe[String] = Maybe.absent
710
+ val right: Either[String, Unit] = absent.toLeft(())
711
+ println(right) // Right(())
712
+ ```
713
+
714
+ ### Composition
715
+
716
+ Combine multiple `Maybe` values:
717
+
718
+ #### zip
719
+
720
+ Combines two `Maybe` values into a tuple:
721
+
722
+ ```scala
723
+ import zio.blocks.maybe._
724
+
725
+ val x: Maybe[Int] = Maybe.present(1)
726
+ val y: Maybe[String] = Maybe.present("a")
727
+ val tuple = x.zip(y)
728
+ println(tuple.get) // (1, "a")
729
+
730
+ val absent: Maybe[String] = Maybe.absent
731
+ val zipped = x.zip(absent)
732
+ println(zipped.isAbsent) // true
733
+ ```
734
+
735
+ #### unzip
736
+
737
+ Splits a `Maybe[(A, B)]` into a tuple of `Maybe[A]` and `Maybe[B]`:
738
+
739
+ ```scala
740
+ import zio.blocks.maybe._
741
+
742
+ val tuple: Maybe[(Int, String)] = Maybe.present((42, "answer"))
743
+ val (x, y) = tuple.unzip
744
+ println(x.get) // 42
745
+ println(y.get) // "answer"
746
+
747
+ val absent: Maybe[(Int, String)] = Maybe.absent
748
+ val (ax, ay) = absent.unzip
749
+ println(ax.isAbsent && ay.isAbsent) // true
750
+ ```
751
+
752
+ #### unzip3
753
+
754
+ Splits a `Maybe[(A, B, C)]` into three `Maybe` values:
755
+
756
+ ```scala
757
+ import zio.blocks.maybe._
758
+
759
+ val triple: Maybe[(Int, String, Double)] = Maybe.present((1, "one", 1.0))
760
+ val (a, b, c) = triple.unzip3
761
+ println(a.get) // 1
762
+ println(b.get) // "one"
763
+ println(c.get) // 1.0
764
+ ```
765
+
766
+ ### Folding
767
+
768
+ Reduce a `Maybe` to a value by handling both branches:
769
+
770
+ #### fold
771
+
772
+ Applies one of two functions based on presence:
773
+
774
+ ```scala
775
+ import zio.blocks.maybe._
776
+
777
+ val value: Maybe[Int] = Maybe.present(10)
778
+ val result = value.fold(-1)((x: Int) => x * 2)
779
+ println(result) // 20
780
+
781
+ val absent: Maybe[Int] = Maybe.absent
782
+ val fallback = absent.fold(-1)((x: Int) => x * 2)
783
+ println(fallback) // -1
784
+ ```
785
+
786
+ ### For-Comprehensions
787
+
788
+ Use `withFilter` to support guard clauses:
789
+
790
+ #### withFilter
791
+
792
+ Enables guarded for-expressions:
793
+
794
+ ```scala
795
+ import zio.blocks.maybe._
796
+
797
+ val x: Maybe[Int] = Maybe.present(5)
798
+ val y: Maybe[Int] = Maybe.present(15)
799
+
800
+ val result = for {
801
+ a <- x
802
+ b <- y
803
+ if a + b > 15
804
+ } yield a + b
805
+
806
+ println(result.get) // 20
807
+ ```
808
+
809
+ ## Running Examples
810
+
811
+ End-to-end workflows combining multiple `Maybe` operations:
812
+
813
+ ### Example: Parsing and Transforming User Input
814
+
815
+ Build a pipeline that parses user input, validates it, and transforms the result:
816
+
817
+ ```scala
818
+ import zio.blocks.maybe._
819
+
820
+ case class User(id: Int, name: String, age: Int)
821
+
822
+ def parseId(input: String): Maybe[Int] =
823
+ Maybe.fromOption(input.toIntOption)
824
+
825
+ def validateAge(age: Int): Maybe[Int] =
826
+ Maybe.absent[Int].orElse(
827
+ if (age >= 0 && age <= 150) Maybe.present(age) else Maybe.absent
828
+ )
829
+
830
+ val input = "42"
831
+ val processedAge = parseId(input)
832
+ .flatMap(id => Maybe.present(User(id, "Alice", 30)))
833
+ .map(_.age)
834
+ .flatMap(validateAge)
835
+
836
+ println(processedAge.getOrElse(-1)) // 30
837
+ ```
838
+
839
+ ### Example: Chaining Optional Database Results
840
+
841
+ Work with nullable database results, filtering and transforming as needed:
842
+
843
+ ```scala
844
+ import zio.blocks.maybe._
845
+
846
+ case class Product(id: Int, name: String, price: Double, inStock: Boolean)
847
+
848
+ def findProduct(id: Int): Maybe[Product] =
849
+ if (id > 0) Maybe.present(Product(id, s"Product $id", 99.99, true))
850
+ else Maybe.absent
851
+
852
+ def applyDiscount(product: Product, percent: Int): Maybe[Product] =
853
+ if (percent >= 0 && percent <= 100)
854
+ Maybe.present(product.copy(price = product.price * (1 - percent / 100.0)))
855
+ else Maybe.absent
856
+
857
+ val discountedProduct = findProduct(123)
858
+ .filter(_.inStock)
859
+ .flatMap(applyDiscount(_, 20))
860
+ .map(p => s"${p.name} now costs $$${p.price}")
861
+
862
+ println(discountedProduct.getOrElse("Product not available"))
863
+ ```
864
+
865
+ ### Example: Combining Multiple Maybes with Fallbacks
866
+
867
+ Handle scenarios where multiple optional values need to be combined:
868
+
869
+ ```scala
870
+ import zio.blocks.maybe._
871
+
872
+ case class Config(host: Maybe[String], port: Maybe[Int], timeout: Maybe[Long])
873
+
874
+ def buildConnection(config: Config): String = {
875
+ val host = config.host.getOrElse("localhost")
876
+ val port = config.port.getOrElse(8080)
877
+ val timeout = config.timeout.getOrElse(5000L)
878
+ s"Connection to $host:$port with timeout $timeout ms"
879
+ }
880
+
881
+ val config = Config(
882
+ host = Maybe.present("api.example.com"),
883
+ port = Maybe.absent,
884
+ timeout = Maybe.present(10000L)
885
+ )
886
+
887
+ println(buildConnection(config))
888
+ ```
889
+
890
+ ### Example: Error Handling with Either Conversion
891
+
892
+ Convert `Maybe` to `Either` for composable error handling in result types:
893
+
894
+ ```scala
895
+ import zio.blocks.maybe._
896
+
897
+ case class Request(id: Maybe[String], method: Maybe[String])
898
+
899
+ def validateRequest(req: Request): Either[String, (String, String)] = {
900
+ for {
901
+ id <- req.id.toRight("Missing request ID")
902
+ method <- req.method.toRight("Missing HTTP method")
903
+ } yield (id, method)
904
+ }
905
+
906
+ val validReq = Request(
907
+ id = Maybe.present("req-123"),
908
+ method = Maybe.present("GET")
909
+ )
910
+
911
+ val invalidReq = Request(
912
+ id = Maybe.absent,
913
+ method = Maybe.present("POST")
914
+ )
915
+
916
+ println(validateRequest(validReq)) // Right((req-123, GET))
917
+ println(validateRequest(invalidReq)) // Left(Missing request ID)
918
+ ```
919
+
920
+ ### Example: Building a Computation Pipeline
921
+
922
+ Chain multiple transformations with fallback at each step:
923
+
924
+ ```scala
925
+ import zio.blocks.maybe._
926
+
927
+ def getUser(id: Int): Maybe[String] =
928
+ if (id > 0) Maybe.present(s"user_$id") else Maybe.absent
929
+
930
+ def getUserEmail(username: String): Maybe[String] =
931
+ if (username.nonEmpty) Maybe.present(s"$username@example.com") else Maybe.absent
932
+
933
+ def getUserProfile(id: Int): Maybe[String] = {
934
+ val username = getUser(id)
935
+ username
936
+ .flatMap(getUserEmail)
937
+ .map(email => s"Profile for $email")
938
+ .orElse(Maybe.present("Guest user"))
939
+ }
940
+
941
+ println(getUserProfile(42)) // Profile for user_42@example.com
942
+ println(getUserProfile(-1)) // Guest user
943
+ ```