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 2048Example (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 -1Effect.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.toExponentialadded - format a BigDecimal as a string in exponential notation.BigDecimal.fromNumberhas 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: 55fiber.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!