Skip to content
Effect 4.0 is here. One ecosystem. Zero dependencies.

Effect 3.11 (Release)

New Effect release featuring Effect.fn, Micro improvements, and more!

Effect 3.11 has been released! This release includes a number of new features and improvements. Here’s a summary of what’s new:

Effect.fn

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: []
}
*/

Effect.fn with a pipeline

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"),
)

Context.Reference

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

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")),
)

Time zone support in Cron

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 updates

  • BigDecimal.toExponential added - format a BigDecimal as a string in exponential notation.
  • BigDecimal.fromNumber has been deprecated in favour of BigDecimal.unsafeFromNumber.

Micro runtime changes

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

Env & EnvRef have been removed in favor of Context.Reference.

MicroExit

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>

Joining Fibers

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

Awaiting Fibers

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
}
*/

Interrupting Fibers

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!
*/

Other changes

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!

Share

Last updated

// Effect Community

Join the conversation on Discord

Meet engineers running Effect in production.