1# Effect subtyping changes in v4
2
3In v3, many types were structural subtypes of `Effect`. They carried the
4Effect type ID at runtime and could be used anywhere an `Effect` was expected.
5This included `Ref`, `Deferred`, `Fiber`, `FiberRef`, `Config`, `Option`,
6`Either`, `Context.Tag`, and others.
7
8While convenient, this could cause subtle bugs. For example, passing a `Ref`
9to an Effect combinator would read its value instead of treating the ref itself
10as a value.
11
12In v4, several of these types are no longer Effect subtypes. Use explicit
13conversion or module functions to obtain an Effect. There is no `Yieldable`
14trait that makes non-Effect values usable in `Effect.gen`.
15
16## Option and Result require conversion
17
18`Option` and `Result` (the replacement for v3's `Either`) are plain values,
19not Effects. They do not have an `.asEffect()` method, and cannot be yielded
20directly in `Effect.gen`. Their iterators work with `Option.gen` and
21`Result.gen`, respectively, not with `Effect.gen`.
22
23Use `Effect.fromOption` or `Effect.fromResult` in Effect code:
24
25- `Effect.fromOption` succeeds with the contained value for `Some` and fails
26 with `Cause.NoSuchElementError` for `None`. An optional `onNone` callback
27 supplies a custom error.
28- `Effect.fromResult` preserves the success value or failure error.
29
30**v3**: Option is an Effect subtype, so this compiles:
31
32```ts
33import { Effect, Option } from "effect"
34
35// Option<number> is assignable to Effect<number, NoSuchElementError>
36const program = Effect.map(Option.some(42), (n) => n + 1)
37```
38
39**v4**: Convert explicitly, both in combinators and in generators:
40
41```ts
42import { Effect, Option, Result } from "effect"
43
44// Effect<number, Cause.NoSuchElementError>
45const program = Effect.map(Effect.fromOption(Option.some(42)), (n) => n + 1)
46
47// Effect<number, Cause.NoSuchElementError>
48const program2 = Effect.gen(function*() {
49 const n = yield* Effect.fromOption(Option.some(42))
50 return n + 1
51})
52
53// Effect<number, never>
54const program3 = Effect.gen(function*() {
55 const n = yield* Effect.fromResult(Result.succeed(123))
56 return n + 1
57})
58
59// Effect<never, string>
60const failed = Effect.fromResult(Result.fail("failed"))
61
62// Effect<never, string>
63const missing = Effect.fromOption(Option.none(), () => "missing")
64```
65
66## Config and services remain Effects
67
68`Config` and `Context.Service` remain Effect subtypes. You can yield them
69in `Effect.gen` or pass them directly to Effect combinators without conversion.
70
71```ts
72import { Config, Context, Effect } from "effect"
73
74class Greeting extends Context.Service<Greeting, { readonly message: string }>()("Greeting") {}
75
76const program = Effect.gen(function*() {
77 const name = yield* Config.String("NAME").pipe(Config.withDefault("world"))
78 const greeting = yield* Greeting
79 return `${greeting.message}, ${name}!`
80}).pipe(Effect.provideService(Greeting, { message: "Hello" }))
81
82const port = Effect.map(Config.Port("PORT").pipe(Config.withDefault(3000)), (n) => n + 1)
83```
84
85`Effectable.Class.asEffect()` is a mechanism for defining custom Effects,
86not a conversion method available on every iterable value.
87
88## Types no longer subtypes of Effect
89
90Several types that extended `Effect` in v3 no longer do so in v4. Use the
91appropriate module functions instead.
92
93**v3**: `Ref` extends `Effect<A>`, yielding the current value:
94
95```ts
96import { Effect, Ref } from "effect"
97
98const program = Effect.gen(function*() {
99 const ref = yield* Ref.make(0)
100 const value = yield* ref // Ref is an Effect<number>
101})
102```
103
104**v4**: `Ref` is a plain value, use `Ref.get`:
105
106```ts
107import { Effect, Ref } from "effect"
108
109const program = Effect.gen(function*() {
110 const ref = yield* Ref.make(0)
111 const value = yield* Ref.get(ref)
112 return value
113})
114```
115
116**v3**: `Deferred` extends `Effect<A, E>`, resolving when completed:
117
118```ts
119import { Deferred, Effect } from "effect"
120
121const program = Effect.gen(function*() {
122 const deferred = yield* Deferred.make<string, never>()
123 const value = yield* deferred // Deferred is an Effect<string>
124})
125```
126
127**v4**: `Deferred` is a plain value, use `Deferred.await`:
128
129```ts
130import { Deferred, Effect } from "effect"
131
132const program = Effect.gen(function*() {
133 const deferred = yield* Deferred.make<string, never>()
134 yield* Deferred.succeed(deferred, "done")
135 const value = yield* Deferred.await(deferred)
136 return value
137})
138```
139
140**v3**: `Fiber` extends `Effect<A, E>`, joining on yield:
141
142```ts
143import { Effect, Fiber } from "effect"
144
145const program = Effect.gen(function*() {
146 const fiber = yield* Effect.fork(task)
147 const result = yield* fiber // Fiber is an Effect<A, E>
148})
149```
150
151**v4**: `Fiber` is a plain value, use `Fiber.join`:
152
153```ts
154import { Effect, Fiber } from "effect"
155
156const program = Effect.gen(function*() {
157 const task = Effect.succeed(42)
158 const fiber = yield* Effect.forkChild(task)
159 const result = yield* Fiber.join(fiber)
160 return result
161})
162```
163
164## Why this changed
165
166The v3 subtyping approach meant the type system could not distinguish between
167"I have a Ref" and "I have an Effect that reads the Ref." For example,
168`Effect.all` could accept an array of refs and silently read all of them.
169
170In v4, explicit functions such as `Ref.get`, `Deferred.await`, `Fiber.join`,
171`Effect.fromOption`, and `Effect.fromResult` make these operations visible
172and prevent those values from being passed accidentally to Effect combinators.