Skip to content

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 implementation
Effect.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 of BigDecimal.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!