Effect 3.11 (Release)
Effect 3.11 has been released! This release includes a number of new features and improvements. Here’s a summary of what’s new:
The Effect.fn
API allows you to create a function that is automatically traced, and also
attaches the location where the function was called to any error traces, helping you track the source of failures.
Example (Creating a traced function)
import { Effect } from "effect"
const logExample = Effect.fn("example")(function* <N extends number>( n: N) { yield* Effect.annotateCurrentSpan("n", n) yield* Effect.logInfo(`got: ${n}`) yield* Effect.fail(new Error())})
Let’s see it in action by exporting the traces to the console:
Example (Exporting traces to the console)
In the output below, you can see the location where the function was called.
import { Effect } from "effect"import { NodeSdk } from "@effect/opentelemetry"import { ConsoleSpanExporter, BatchSpanProcessor} from "@opentelemetry/sdk-trace-base"
const logExample = Effect.fn("example")(function* <N extends number>( n: N) { yield* Effect.annotateCurrentSpan("n", n) yield* Effect.logInfo(`got: ${n}`) yield* Effect.fail(new Error())})
const program = logExample(100).pipe( Effect.catchAllCause(Effect.logError))
const NodeSdkLive = NodeSdk.layer(() => ({ resource: { serviceName: "example" }, // Export span data to the console spanProcessor: new BatchSpanProcessor(new ConsoleSpanExporter())}))
Effect.runFork(program.pipe(Effect.provide(NodeSdkLive)))/*Output:{ resource: { attributes: { 'service.name': 'example', 'telemetry.sdk.language': 'nodejs', 'telemetry.sdk.name': '@effect/opentelemetry', 'telemetry.sdk.version': '1.28.0' } }, instrumentationScope: { name: 'example', version: undefined, schemaUrl: undefined }, traceId: 'f9ffd211c2f6efc66e11d8aab3466e3f', parentId: undefined, traceState: undefined, name: 'example', id: '2949fa70d80fe0f4', kind: 0, timestamp: 1733217197993387.8, duration: 5023.958, attributes: { n: 100, 'code.stacktrace': 'at <anonymous> (/path/to/file/index.ts:16:17)' }, status: { code: 2, message: 'An error has occurred' }, events: [ { name: 'got: 100', attributes: { 'effect.fiberId': '#0', 'effect.logLevel': 'INFO' }, time: [ 1733217197, 995354750 ], droppedAttributesCount: 0 }, { name: 'exception', attributes: { 'exception.type': 'Error', 'exception.message': 'An error has occurred', 'exception.stacktrace': 'Error: An error has occurred\n' + ' at <anonymous> (/path/to/file/index.ts:13:22)\n' + ' at example (/path/to/file/index.ts:16:17)' }, time: [ 1733217197, 998411750 ], droppedAttributesCount: 0 } ], links: []}*/
The Effect.fn
API also acts as a pipe
function, allowing you to create a pipeline after the function definition.
Example (Creating a traced function with a pipeline)
In this example, Effect.fn
is used not only to define the traced function but also to create a pipeline by chaining Effect.delay
after the function, adding a delay of “1 second” to the execution.
import { Effect } from "effect"
const logExample = Effect.fn("example")( function* <N extends number>(n: N) { yield* Effect.annotateCurrentSpan("n", n) yield* Effect.logInfo(`got: ${n}`) yield* Effect.fail(new Error()) }, // Add a delay to the effect Effect.delay("1 second"))
You can now create a tag with a default value.
Example (Declaring a Tag with a default value)
In this example, you don’t have to explicitly provide the SpecialNumber
implementation.
The default value is automatically used when the service is accessed.
import { Context, Effect } from "effect"
class SpecialNumber extends Context.Reference<SpecialNumber>()( "SpecialNumber", { defaultValue: () => 2048 }) {}
// ┌─── Effect<void, never, never>// ▼const program = Effect.gen(function* () { const specialNumber = yield* SpecialNumber console.log(`The special number is ${specialNumber}`)})
// No need to provide the SpecialNumber implementationEffect.runPromise(program)// Output: The special number is 2048
Example (Overriding the default value)
In this example, the default value is overridden by providing a different implementation for the SpecialNumber
service.
import { Context, Effect } from "effect"
class SpecialNumber extends Context.Reference<SpecialNumber>()( "SpecialNumber", { defaultValue: () => 2048 }) {}
const program = Effect.gen(function* () { const specialNumber = yield* SpecialNumber console.log(`The special number is ${specialNumber}`)})
Effect.runPromise(program.pipe(Effect.provideService(SpecialNumber, -1)))// Output: The special number is -1
Effect.scopedWith
allows you to create and use a Scope
without adding it to the Effect’s requirements.
import { Effect, Scope } from "effect"
// ┌─── Effect<void, never, never>// ▼const program = Effect.scopedWith((scope) => Scope.addFinalizer(scope, Effect.log("finalized")))
Cron expressions using the Cron
module now support time zones. You can specify a time zone
when creating a cron instance when using Cron.make
or Cron.parse
.
BigDecimal.toExponential
added - format a BigDecimal as a string in exponential notation.BigDecimal.fromNumber
has been deprecated in favour ofBigDecimal.unsafeFromNumber
.
Micro execution is now using a fiber-runtime based model. This results in the following benefits:
- Improved performance
- Improved interruption model
- Consistency with the Effect data type
Env
& EnvRef
have been removed in favor of Context.Reference
.
The MicroExit
data type is no longer based on Either
:
Before:
type MicroExit<A, E> = Either<A, MicroCause<E>>
After:
type MicroExit<A, E> = MicroExit.Success<A, E> | MicroExit.Failure<A, E>
fiber.join
has been replaced with Micro.fiberJoin
.
import { Micro } from "effect"
const fib = (n: number): Micro.Micro<number> => n < 2 ? Micro.succeed(n) : Micro.zipWith(fib(n - 1), fib(n - 2), (a, b) => a + b)
const fib10Fiber = Micro.fork(fib(10))
const program = Micro.gen(function* () { const fiber = yield* fib10Fiber // Join the fiber and get the result const n = yield* fiber.join const n = yield* Micro.fiberJoin(fiber) console.log(n)})
Micro.runPromise(program)// Output: 55
fiber.await
has been replaced with Micro.fiberAwait
.
import { Micro } from "effect"
const fib = (n: number): Micro.Micro<number> => n < 2 ? Micro.succeed(n) : Micro.zipWith(fib(n - 1), fib(n - 2), (a, b) => a + b)
const fib10Fiber = Micro.fork(fib(10))
const program = Micro.gen(function* () { const fiber = yield* fib10Fiber // Await its completion and get the MicroExit result const exit = yield* fiber.await const exit = yield* Micro.fiberAwait(fiber) console.log(exit)})
Micro.runPromise(program)/*Output:{ "_id": "MicroExit", "_tag": "Success", "value": 55}*/
fiber.interrupt
has been replaced with Micro.fiberInterrupt
.
import { Micro } from "effect"
const program = Micro.gen(function* () { const fiber = yield* Micro.fork( Micro.forever( Micro.sync(() => console.log("Hi!")).pipe(Micro.delay(10)) ) ) yield* Micro.sleep(30) // Interrupt the fiber yield* fiber.interrupt yield* Micro.fiberInterrupt(fiber)})
Micro.runPromise(program)/*Output:Hi!Hi!*/
There were several other smaller changes made. Take a look through the CHANGELOG to see them all: CHANGELOG.
Don’t forget to join our Discord Community to follow the last updates and discuss every tiny detail!