This is the abridged developer documentation for Effect.
# Start of Effect documentation
# [Batching](https://effect.website/docs/batching/)
## Overview import { Aside } from "@astrojs/starlight/components" In typical application development, when interacting with external APIs, databases, or other data sources, we often define functions that perform requests and handle their results or failures accordingly. ### Simple Model Setup Here's a basic model that outlines the structure of our data and possible errors: ```ts twoslash // ------------------------------ // Model // ------------------------------ interface User { readonly _tag: "User" readonly id: number readonly name: string readonly email: string } class GetUserError { readonly _tag = "GetUserError" } interface Todo { readonly _tag: "Todo" readonly id: number readonly message: string readonly ownerId: number } class GetTodosError { readonly _tag = "GetTodosError" } class SendEmailError { readonly _tag = "SendEmailError" } ``` ### Defining API Functions Let's define functions that interact with an external API, handling common operations such as fetching todos, retrieving user details, and sending emails. ```ts twoslash collapse={7-31} import { Effect } from "effect" // ------------------------------ // Model // ------------------------------ interface User { readonly _tag: "User" readonly id: number readonly name: string readonly email: string } class GetUserError { readonly _tag = "GetUserError" } interface Todo { readonly _tag: "Todo" readonly id: number readonly message: string readonly ownerId: number } class GetTodosError { readonly _tag = "GetTodosError" } class SendEmailError { readonly _tag = "SendEmailError" } // ------------------------------ // API // ------------------------------ // Fetches a list of todos from an external API const getTodos = Effect.tryPromise({ try: () => fetch("https://api.example.demo/todos").then( (res) => res.json() as Promise> ), catch: () => new GetTodosError() }) // Retrieves a user by their ID from an external API const getUserById = (id: number) => Effect.tryPromise({ try: () => fetch(`https://api.example.demo/getUserById?id=${id}`).then( (res) => res.json() as Promise ), catch: () => new GetUserError() }) // Sends an email via an external API const sendEmail = (address: string, text: string) => Effect.tryPromise({ try: () => fetch("https://api.example.demo/sendEmail", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ address, text }) }).then((res) => res.json() as Promise), catch: () => new SendEmailError() }) // Sends an email to a user by fetching their details first const sendEmailToUser = (id: number, message: string) => getUserById(id).pipe( Effect.andThen((user) => sendEmail(user.email, message)) ) // Notifies the owner of a todo by sending them an email const notifyOwner = (todo: Todo) => getUserById(todo.ownerId).pipe( Effect.andThen((user) => sendEmailToUser(user.id, `hey ${user.name} you got a todo!`) ) ) ``` While this approach is straightforward and readable, it may not be the most efficient. Repeated API calls, especially when many todos share the same owner, can significantly increase network overhead and slow down your application. ### Using the API Functions While these functions are clear and easy to understand, their use may not be the most efficient. For example, notifying todo owners involves repeated API calls which can be optimized. ```ts twoslash collapse={7-31,37-82} import { Effect } from "effect" // ------------------------------ // Model // ------------------------------ interface User { readonly _tag: "User" readonly id: number readonly name: string readonly email: string } class GetUserError { readonly _tag = "GetUserError" } interface Todo { readonly _tag: "Todo" readonly id: number readonly message: string readonly ownerId: number } class GetTodosError { readonly _tag = "GetTodosError" } class SendEmailError { readonly _tag = "SendEmailError" } // ------------------------------ // API // ------------------------------ // Fetches a list of todos from an external API const getTodos = Effect.tryPromise({ try: () => fetch("https://api.example.demo/todos").then( (res) => res.json() as Promise> ), catch: () => new GetTodosError() }) // Retrieves a user by their ID from an external API const getUserById = (id: number) => Effect.tryPromise({ try: () => fetch(`https://api.example.demo/getUserById?id=${id}`).then( (res) => res.json() as Promise ), catch: () => new GetUserError() }) // Sends an email via an external API const sendEmail = (address: string, text: string) => Effect.tryPromise({ try: () => fetch("https://api.example.demo/sendEmail", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ address, text }) }).then((res) => res.json() as Promise), catch: () => new SendEmailError() }) // Sends an email to a user by fetching their details first const sendEmailToUser = (id: number, message: string) => getUserById(id).pipe( Effect.andThen((user) => sendEmail(user.email, message)) ) // Notifies the owner of a todo by sending them an email const notifyOwner = (todo: Todo) => getUserById(todo.ownerId).pipe( Effect.andThen((user) => sendEmailToUser(user.id, `hey ${user.name} you got a todo!`) ) ) // Orchestrates operations on todos, notifying their owners const program = Effect.gen(function* () { const todos = yield* getTodos yield* Effect.forEach(todos, (todo) => notifyOwner(todo), { concurrency: "unbounded" }) }) ``` This implementation performs an API call for each todo to fetch the owner's details and send an email. If multiple todos have the same owner, this results in redundant API calls. ## Batching Let's assume that `getUserById` and `sendEmail` can be batched. This means that we can send multiple requests in a single HTTP call, reducing the number of API requests and improving performance. **Step-by-Step Guide to Batching** 1. **Declaring Requests:** We'll start by transforming our requests into structured data models. This involves detailing input parameters, expected outputs, and possible errors. Structuring requests this way not only helps in efficiently managing data but also in comparing different requests to understand if they refer to the same input parameters. 2. **Declaring Resolvers:** Resolvers are designed to handle multiple requests simultaneously. By leveraging the ability to compare requests (ensuring they refer to the same input parameters), resolvers can execute several requests in one go, maximizing the utility of batching. 3. **Defining Queries:** Finally, we'll define queries that utilize these batch-resolvers to perform operations. This step ties together the structured requests and their corresponding resolvers into functional components of the application. ### Declaring Requests We'll design a model using the concept of a `Request` that a data source might support: ```ts showLineNumbers=false Request ``` A `Request` is a construct representing a request for a value of type `Value`, which might fail with an error of type `Error`. Let's start by defining a structured model for the types of requests our data sources can handle. ```ts twoslash collapse={7-31} import { Request } from "effect" // ------------------------------ // Model // ------------------------------ interface User { readonly _tag: "User" readonly id: number readonly name: string readonly email: string } class GetUserError { readonly _tag = "GetUserError" } interface Todo { readonly _tag: "Todo" readonly id: number readonly message: string readonly ownerId: number } class GetTodosError { readonly _tag = "GetTodosError" } class SendEmailError { readonly _tag = "SendEmailError" } // ------------------------------ // Requests // ------------------------------ // Define a request to get multiple Todo items which might // fail with a GetTodosError interface GetTodos extends Request.Request, GetTodosError> { readonly _tag: "GetTodos" } // Create a tagged constructor for GetTodos requests const GetTodos = Request.tagged("GetTodos") // Define a request to fetch a User by ID which might // fail with a GetUserError interface GetUserById extends Request.Request { readonly _tag: "GetUserById" readonly id: number } // Create a tagged constructor for GetUserById requests const GetUserById = Request.tagged("GetUserById") // Define a request to send an email which might // fail with a SendEmailError interface SendEmail extends Request.Request { readonly _tag: "SendEmail" readonly address: string readonly text: string } // Create a tagged constructor for SendEmail requests const SendEmail = Request.tagged("SendEmail") ``` Each request is defined with a specific data structure that extends from a generic `Request` type, ensuring that each request carries its unique data requirements along with a specific error type. By using tagged constructors like `Request.tagged`, we can easily instantiate request objects that are recognizable and manageable throughout the application. ### Declaring Resolvers After defining our requests, the next step is configuring how Effect resolves these requests using `RequestResolver`: ```ts showLineNumbers=false RequestResolver ``` A `RequestResolver` requires an environment `R` and is capable of executing requests of type `A`. In this section, we'll create individual resolvers for each type of request. The granularity of your resolvers can vary, but typically, they are divided based on the batching capabilities of the corresponding API calls. ```ts twoslash collapse={7-31,37-65} import { Effect, Request, RequestResolver } from "effect" // ------------------------------ // Model // ------------------------------ interface User { readonly _tag: "User" readonly id: number readonly name: string readonly email: string } class GetUserError { readonly _tag = "GetUserError" } interface Todo { readonly _tag: "Todo" readonly id: number readonly message: string readonly ownerId: number } class GetTodosError { readonly _tag = "GetTodosError" } class SendEmailError { readonly _tag = "SendEmailError" } // ------------------------------ // Requests // ------------------------------ // Define a request to get multiple Todo items which might // fail with a GetTodosError interface GetTodos extends Request.Request, GetTodosError> { readonly _tag: "GetTodos" } // Create a tagged constructor for GetTodos requests const GetTodos = Request.tagged("GetTodos") // Define a request to fetch a User by ID which might // fail with a GetUserError interface GetUserById extends Request.Request { readonly _tag: "GetUserById" readonly id: number } // Create a tagged constructor for GetUserById requests const GetUserById = Request.tagged("GetUserById") // Define a request to send an email which might // fail with a SendEmailError interface SendEmail extends Request.Request { readonly _tag: "SendEmail" readonly address: string readonly text: string } // Create a tagged constructor for SendEmail requests const SendEmail = Request.tagged("SendEmail") // ------------------------------ // Resolvers // ------------------------------ // Assuming GetTodos cannot be batched, we create a standard resolver const GetTodosResolver = RequestResolver.fromEffect( (_: GetTodos): Effect.Effect => Effect.tryPromise({ try: () => fetch("https://api.example.demo/todos").then( (res) => res.json() as Promise> ), catch: () => new GetTodosError() }) ) // Assuming GetUserById can be batched, we create a batched resolver const GetUserByIdResolver = RequestResolver.makeBatched( (requests: ReadonlyArray) => Effect.tryPromise({ try: () => fetch("https://api.example.demo/getUserByIdBatch", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ users: requests.map(({ id }) => ({ id })) }) }).then((res) => res.json()) as Promise>, catch: () => new GetUserError() }).pipe( Effect.andThen((users) => Effect.forEach(requests, (request, index) => Request.completeEffect(request, Effect.succeed(users[index]!)) ) ), Effect.catchAll((error) => Effect.forEach(requests, (request) => Request.completeEffect(request, Effect.fail(error)) ) ) ) ) // Assuming SendEmail can be batched, we create a batched resolver const SendEmailResolver = RequestResolver.makeBatched( (requests: ReadonlyArray) => Effect.tryPromise({ try: () => fetch("https://api.example.demo/sendEmailBatch", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ emails: requests.map(({ address, text }) => ({ address, text })) }) }).then((res) => res.json() as Promise), catch: () => new SendEmailError() }).pipe( Effect.andThen( Effect.forEach(requests, (request) => Request.completeEffect(request, Effect.void) ) ), Effect.catchAll((error) => Effect.forEach(requests, (request) => Request.completeEffect(request, Effect.fail(error)) ) ) ) ) ``` In this configuration: - **GetTodosResolver** handles the fetching of multiple `Todo` items. It's set up as a standard resolver since we assume it cannot be batched. - **GetUserByIdResolver** and **SendEmailResolver** are configured as batched resolvers. This setup is based on the assumption that these requests can be processed in batches, enhancing performance and reducing the number of API calls. ### Defining Queries Now that we've set up our resolvers, we're ready to tie all the pieces together to define our This step will enable us to perform data operations effectively within our application. ```ts twoslash collapse={7-31,37-65,71-142} import { Effect, Request, RequestResolver } from "effect" // ------------------------------ // Model // ------------------------------ interface User { readonly _tag: "User" readonly id: number readonly name: string readonly email: string } class GetUserError { readonly _tag = "GetUserError" } interface Todo { readonly _tag: "Todo" readonly id: number readonly message: string readonly ownerId: number } class GetTodosError { readonly _tag = "GetTodosError" } class SendEmailError { readonly _tag = "SendEmailError" } // ------------------------------ // Requests // ------------------------------ // Define a request to get multiple Todo items which might // fail with a GetTodosError interface GetTodos extends Request.Request, GetTodosError> { readonly _tag: "GetTodos" } // Create a tagged constructor for GetTodos requests const GetTodos = Request.tagged("GetTodos") // Define a request to fetch a User by ID which might // fail with a GetUserError interface GetUserById extends Request.Request { readonly _tag: "GetUserById" readonly id: number } // Create a tagged constructor for GetUserById requests const GetUserById = Request.tagged("GetUserById") // Define a request to send an email which might // fail with a SendEmailError interface SendEmail extends Request.Request { readonly _tag: "SendEmail" readonly address: string readonly text: string } // Create a tagged constructor for SendEmail requests const SendEmail = Request.tagged("SendEmail") // ------------------------------ // Resolvers // ------------------------------ // Assuming GetTodos cannot be batched, we create a standard resolver const GetTodosResolver = RequestResolver.fromEffect( (_: GetTodos): Effect.Effect => Effect.tryPromise({ try: () => fetch("https://api.example.demo/todos").then( (res) => res.json() as Promise> ), catch: () => new GetTodosError() }) ) // Assuming GetUserById can be batched, we create a batched resolver const GetUserByIdResolver = RequestResolver.makeBatched( (requests: ReadonlyArray) => Effect.tryPromise({ try: () => fetch("https://api.example.demo/getUserByIdBatch", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ users: requests.map(({ id }) => ({ id })) }) }).then((res) => res.json()) as Promise>, catch: () => new GetUserError() }).pipe( Effect.andThen((users) => Effect.forEach(requests, (request, index) => Request.completeEffect(request, Effect.succeed(users[index]!)) ) ), Effect.catchAll((error) => Effect.forEach(requests, (request) => Request.completeEffect(request, Effect.fail(error)) ) ) ) ) // Assuming SendEmail can be batched, we create a batched resolver const SendEmailResolver = RequestResolver.makeBatched( (requests: ReadonlyArray) => Effect.tryPromise({ try: () => fetch("https://api.example.demo/sendEmailBatch", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ emails: requests.map(({ address, text }) => ({ address, text })) }) }).then((res) => res.json() as Promise), catch: () => new SendEmailError() }).pipe( Effect.andThen( Effect.forEach(requests, (request) => Request.completeEffect(request, Effect.void) ) ), Effect.catchAll((error) => Effect.forEach(requests, (request) => Request.completeEffect(request, Effect.fail(error)) ) ) ) ) // ------------------------------ // Queries // ------------------------------ // Defines a query to fetch all Todo items const getTodos: Effect.Effect< Array, GetTodosError > = Effect.request(GetTodos({}), GetTodosResolver) // Defines a query to fetch a user by their ID const getUserById = (id: number) => Effect.request(GetUserById({ id }), GetUserByIdResolver) // Defines a query to send an email to a specific address const sendEmail = (address: string, text: string) => Effect.request(SendEmail({ address, text }), SendEmailResolver) // Composes getUserById and sendEmail to send an email to a specific user const sendEmailToUser = (id: number, message: string) => getUserById(id).pipe( Effect.andThen((user) => sendEmail(user.email, message)) ) // Uses getUserById to fetch the owner of a Todo and then sends them an email notification const notifyOwner = (todo: Todo) => getUserById(todo.ownerId).pipe( Effect.andThen((user) => sendEmailToUser(user.id, `hey ${user.name} you got a todo!`) ) ) ``` By using the `Effect.request` function, we integrate the resolvers with the request model effectively. This approach ensures that each query is optimally resolved using the appropriate resolver. Although the code structure looks similar to earlier examples, employing resolvers significantly enhances efficiency by optimizing how requests are handled and reducing unnecessary API calls. ```ts {4} showLineNumbers=false const program = Effect.gen(function* () { const todos = yield* getTodos yield* Effect.forEach(todos, (todo) => notifyOwner(todo), { batching: true }) }) ``` In the final setup, this program will execute only **3** queries to the APIs, regardless of the number of todos. This contrasts sharply with the traditional approach, which would potentially execute **1 + 2n** queries, where **n** is the number of todos. This represents a significant improvement in efficiency, especially for applications with a high volume of data interactions. ### Disabling Batching Batching can be locally disabled using the `Effect.withRequestBatching` utility in the following way: ```ts {6} showLineNumbers=false const program = Effect.gen(function* () { const todos = yield* getTodos yield* Effect.forEach(todos, (todo) => notifyOwner(todo), { concurrency: "unbounded" }) }).pipe(Effect.withRequestBatching(false)) ``` ### Resolvers with Context In complex applications, resolvers often need access to shared services or configurations to handle requests effectively. However, maintaining the ability to batch requests while providing the necessary context can be challenging. Here, we'll explore how to manage context in resolvers to ensure that batching capabilities are not compromised. When creating request resolvers, it's crucial to manage the context carefully. Providing too much context or providing varying services to resolvers can make them incompatible for batching. To prevent such issues, the context for the resolver used in `Effect.request` is explicitly set to `never`. This forces developers to clearly define how the context is accessed and used within resolvers. Consider the following example where we set up an HTTP service that the resolvers can use to execute API calls: ```ts twoslash collapse={7-31,37-65} import { Effect, Context, RequestResolver, Request } from "effect" // ------------------------------ // Model // ------------------------------ interface User { readonly _tag: "User" readonly id: number readonly name: string readonly email: string } class GetUserError { readonly _tag = "GetUserError" } interface Todo { readonly _tag: "Todo" readonly id: number readonly message: string readonly ownerId: number } class GetTodosError { readonly _tag = "GetTodosError" } class SendEmailError { readonly _tag = "SendEmailError" } // ------------------------------ // Requests // ------------------------------ // Define a request to get multiple Todo items which might // fail with a GetTodosError interface GetTodos extends Request.Request, GetTodosError> { readonly _tag: "GetTodos" } // Create a tagged constructor for GetTodos requests const GetTodos = Request.tagged("GetTodos") // Define a request to fetch a User by ID which might // fail with a GetUserError interface GetUserById extends Request.Request { readonly _tag: "GetUserById" readonly id: number } // Create a tagged constructor for GetUserById requests const GetUserById = Request.tagged("GetUserById") // Define a request to send an email which might // fail with a SendEmailError interface SendEmail extends Request.Request { readonly _tag: "SendEmail" readonly address: string readonly text: string } // Create a tagged constructor for SendEmail requests const SendEmail = Request.tagged("SendEmail") // ------------------------------ // Resolvers With Context // ------------------------------ class HttpService extends Context.Tag("HttpService")< HttpService, { fetch: typeof fetch } >() {} const GetTodosResolver = // we create a normal resolver like we did before RequestResolver.fromEffect((_: GetTodos) => Effect.andThen(HttpService, (http) => Effect.tryPromise({ try: () => http .fetch("https://api.example.demo/todos") .then((res) => res.json() as Promise>), catch: () => new GetTodosError() }) ) ).pipe( // we list the tags that the resolver can access RequestResolver.contextFromServices(HttpService) ) ``` We can see now that the type of `GetTodosResolver` is no longer a `RequestResolver` but instead it is: ```ts showLineNumbers=false const GetTodosResolver: Effect< RequestResolver, never, HttpService > ``` which is an effect that access the `HttpService` and returns a composed resolver that has the minimal context ready to use. Once we have such effect we can directly use it in our query definition: ```ts showLineNumbers=false const getTodos: Effect.Effect = Effect.request(GetTodos({}), GetTodosResolver) ``` We can see that the Effect correctly requires `HttpService` to be provided. Alternatively you can create `RequestResolver`s as part of layers direcly accessing or closing over context from construction. **Example** ```ts twoslash collapse={7-31,37-65,71-91} import { Effect, Context, RequestResolver, Request, Layer } from "effect" // ------------------------------ // Model // ------------------------------ interface User { readonly _tag: "User" readonly id: number readonly name: string readonly email: string } class GetUserError { readonly _tag = "GetUserError" } interface Todo { readonly _tag: "Todo" readonly id: number readonly message: string readonly ownerId: number } class GetTodosError { readonly _tag = "GetTodosError" } class SendEmailError { readonly _tag = "SendEmailError" } // ------------------------------ // Requests // ------------------------------ // Define a request to get multiple Todo items which might // fail with a GetTodosError interface GetTodos extends Request.Request, GetTodosError> { readonly _tag: "GetTodos" } // Create a tagged constructor for GetTodos requests const GetTodos = Request.tagged("GetTodos") // Define a request to fetch a User by ID which might // fail with a GetUserError interface GetUserById extends Request.Request { readonly _tag: "GetUserById" readonly id: number } // Create a tagged constructor for GetUserById requests const GetUserById = Request.tagged("GetUserById") // Define a request to send an email which might // fail with a SendEmailError interface SendEmail extends Request.Request { readonly _tag: "SendEmail" readonly address: string readonly text: string } // Create a tagged constructor for SendEmail requests const SendEmail = Request.tagged("SendEmail") // ------------------------------ // Resolvers With Context // ------------------------------ class HttpService extends Context.Tag("HttpService")< HttpService, { fetch: typeof fetch } >() {} const GetTodosResolver = // we create a normal resolver like we did before RequestResolver.fromEffect((_: GetTodos) => Effect.andThen(HttpService, (http) => Effect.tryPromise({ try: () => http .fetch("https://api.example.demo/todos") .then((res) => res.json() as Promise>), catch: () => new GetTodosError() }) ) ).pipe( // we list the tags that the resolver can access RequestResolver.contextFromServices(HttpService) ) // ------------------------------ // Layers // ------------------------------ class TodosService extends Context.Tag("TodosService")< TodosService, { getTodos: Effect.Effect, GetTodosError> } >() {} const TodosServiceLive = Layer.effect( TodosService, Effect.gen(function* () { const http = yield* HttpService const resolver = RequestResolver.fromEffect((_: GetTodos) => Effect.tryPromise({ try: () => http .fetch("https://api.example.demo/todos") .then((res) => res.json()), catch: () => new GetTodosError() }) ) return { getTodos: Effect.request(GetTodos({}), resolver) } }) ) const getTodos: Effect.Effect< Array, GetTodosError, TodosService > = Effect.andThen(TodosService, (service) => service.getTodos) ``` This way is probably the best for most of the cases given that layers are the natural primitive where to wire services together. ## Caching While we have significantly optimized request batching, there's another area that can enhance our application's efficiency: caching. Without caching, even with optimized batch processing, the same requests could be executed multiple times, leading to unnecessary data fetching. In the Effect library, caching is handled through built-in utilities that allow requests to be stored temporarily, preventing the need to re-fetch data that hasn't changed. This feature is crucial for reducing the load on both the server and the network, especially in applications that make frequent similar requests. Here's how you can implement caching for the `getUserById` query: ```ts {3} showLineNumbers=false const getUserById = (id: number) => Effect.request(GetUserById({ id }), GetUserByIdResolver).pipe( Effect.withRequestCaching(true) ) ``` ## Final Program Assuming you've wired everything up correctly: ```ts showLineNumbers=false const program = Effect.gen(function* () { const todos = yield* getTodos yield* Effect.forEach(todos, (todo) => notifyOwner(todo), { concurrency: "unbounded" }) }).pipe(Effect.repeat(Schedule.fixed("10 seconds"))) ``` With this program, the `getTodos` operation retrieves the todos for each user. Then, the `Effect.forEach` function is used to notify the owner of each todo concurrently, without waiting for the notifications to complete. The `repeat` function is applied to the entire chain of operations, and it ensures that the program repeats every 10 seconds using a fixed schedule. This means that the entire process, including fetching todos and sending notifications, will be executed repeatedly with a 10-second interval. The program incorporates a caching mechanism, which prevents the same `GetUserById` operation from being executed more than once within a span of 1 minute. This default caching behavior helps optimize the program's execution and reduces unnecessary requests to fetch user data. Furthermore, the program is designed to send emails in batches, allowing for efficient processing and better utilization of resources. ## Customizing Request Caching In real-world applications, effective caching strategies can significantly improve performance by reducing redundant data fetching. The Effect library provides flexible caching mechanisms that can be tailored for specific parts of your application or applied globally. There may be scenarios where different parts of your application have unique caching requirements—some might benefit from a localized cache, while others might need a global cache setup. Let’s explore how you can configure a custom cache to meet these varied needs. ### Creating a Custom Cache Here's how you can create a custom cache and apply it to part of your application. This example demonstrates setting up a cache that repeats a task every 10 seconds, caching requests with specific parameters like capacity and TTL (time-to-live). ```ts showLineNumbers=false const program = Effect.gen(function* () { const todos = yield* getTodos yield* Effect.forEach(todos, (todo) => notifyOwner(todo), { concurrency: "unbounded" }) }).pipe( Effect.repeat(Schedule.fixed("10 seconds")), Effect.provide( Layer.setRequestCache( Request.makeCache({ capacity: 256, timeToLive: "60 minutes" }) ) ) ) ``` ### Direct Cache Application You can also construct a cache using `Request.makeCache` and apply it directly to a specific program using `Effect.withRequestCache`. This method ensures that all requests originating from the specified program are managed through the custom cache, provided that caching is enabled.
# [Configuration](https://effect.website/docs/configuration/)
## Overview import { Aside, Badge } from "@astrojs/starlight/components" Configuration is an essential aspect of any cloud-native application. Effect simplifies the process of managing configuration by offering a convenient interface for configuration providers. The configuration front-end in Effect enables ecosystem libraries and applications to specify their configuration requirements in a declarative manner. It offloads the complex tasks to a `ConfigProvider`, which can be supplied by third-party libraries. Effect comes bundled with a straightforward default `ConfigProvider` that retrieves configuration data from environment variables. This default provider can be used during development or as a starting point before transitioning to more advanced configuration providers. To make our application configurable, we need to understand three essential elements: - **Config Description**: We describe the configuration data using an instance of `Config`. If the configuration data is simple, such as a `string`, `number`, or `boolean`, we can use the built-in functions provided by the `Config` module. For more complex data types like [HostPort](#custom-configuration-types), we can combine primitive configs to create a custom configuration description. - **Config Frontend**: We utilize the instance of `Config` to load the configuration data described by the instance (a `Config` is, in itself, an effect). This process leverages the current `ConfigProvider` to retrieve the configuration. - **Config Backend**: The `ConfigProvider` serves as the underlying engine that manages the configuration loading process. Effect comes with a default config provider as part of its default services. This default provider reads the configuration data from environment variables. If we want to use a custom config provider, we can utilize the `Effect.withConfigProvider` API to configure the Effect runtime accordingly. ## Basic Configuration Types Effect provides several built-in types for configuration values, which you can use right out of the box: | Type | Description | | ---------- | ----------------------------------------------------------------------- | | `string` | Reads a configuration value as a string. | | `number` | Reads a value as a floating-point number. | | `boolean` | Reads a value as a boolean (`true` or `false`). | | `integer` | Reads a value as an integer. | | `date` | Parses a value into a `Date` object. | | `literal` | Reads a fixed literal (\*). | | `logLevel` | Reads a value as a [LogLevel](/docs/observability/logging/#log-levels). | | `duration` | Parses a value as a time duration. | | `redacted` | Reads a **sensitive value**, ensuring it is protected when logged. | | `url` | Parses a value as a valid URL. | (\*) `string | number | boolean | null | bigint` **Example** (Loading Environment Variables) Here's an example of loading a basic configuration using environment variables for `HOST` and `PORT`: ```ts twoslash title="primitives.ts" import { Effect, Config } from "effect" // Define a program that loads HOST and PORT configuration const program = Effect.gen(function* () { const host = yield* Config.string("HOST") // Read as a string const port = yield* Config.number("PORT") // Read as a number console.log(`Application started: ${host}:${port}`) }) Effect.runPromise(program) ``` If you run this without setting the required environment variables: ```sh showLineNumbers=false npx tsx primitives.ts ``` you'll see an error indicating the missing configuration: ```ansi showLineNumbers=false [Error: (Missing data at HOST: "Expected HOST to exist in the process context")] { name: '(FiberFailure) Error', [Symbol(effect/Runtime/FiberFailure)]: Symbol(effect/Runtime/FiberFailure), [Symbol(effect/Runtime/FiberFailure/Cause)]: { _tag: 'Fail', error: { _op: 'MissingData', path: [ 'HOST' ], message: 'Expected HOST to exist in the process context' } } } ``` To run the program successfully, set the environment variables as shown below: ```sh showLineNumbers=false HOST=localhost PORT=8080 npx tsx primitives.ts ``` Output: ```ansi showLineNumbers=false Application started: localhost:8080 ``` ## Using Config with Schema You can define and decode configuration values using a schema. **Example** (Decoding a Configuration Value) ```ts twoslash import { Effect, Schema } from "effect" // Define a config that expects a string with at least 4 characters const myConfig = Schema.Config( "Foo", Schema.String.pipe(Schema.minLength(4)) ) ``` For more information, see the [Schema.Config](/docs/schema/effect-data-types/#config) documentation. ## Providing Default Values Sometimes, you may encounter situations where an environment variable is missing, leading to an incomplete configuration. To address this, Effect provides the `Config.withDefault` function, which allows you to specify a default value. This fallback ensures that your application continues to function even if a required environment variable is not set. **Example** (Using Default Values) ```ts twoslash title="defaults.ts" import { Effect, Config } from "effect" const program = Effect.gen(function* () { const host = yield* Config.string("HOST") // Use default 8080 if PORT is not set const port = yield* Config.number("PORT").pipe(Config.withDefault(8080)) console.log(`Application started: ${host}:${port}`) }) Effect.runPromise(program) ``` Running this program with only the `HOST` environment variable set: ```sh showLineNumbers=false HOST=localhost npx tsx defaults.ts ``` produces the following output: ```ansi showLineNumbers=false Application started: localhost:8080 ``` In this case, even though the `PORT` environment variable is not set, the program continues to run, using the default value of `8080` for the port. This ensures that the application remains functional without requiring every configuration to be explicitly provided. ## Handling Sensitive Values Some configuration values, like API keys, should not be printed in logs. The `Config.redacted` function is used to handle sensitive information safely. It parses the configuration value and wraps it in a `Redacted`, a specialized [data type](/docs/data-types/redacted/) designed to protect secrets. When you log a `Redacted` value using `console.log`, the actual content remains hidden, providing an extra layer of security. To access the real value, you must explicitly use `Redacted.value`. **Example** (Protecting Sensitive Data) ```ts twoslash title="redacted.ts" import { Effect, Config, Redacted } from "effect" const program = Effect.gen(function* () { // ┌─── Redacted // ▼ const redacted = yield* Config.redacted("API_KEY") // Log the redacted value, which won't reveal the actual secret console.log(`Console output: ${redacted}`) // Access the real value using Redacted.value and log it console.log(`Actual value: ${Redacted.value(redacted)}`) }) Effect.runPromise(program) ``` When this program is executed: ```sh showLineNumbers=false API_KEY=my-api-key tsx redacted.ts ``` The output will look like this: ```ansi showLineNumbers=false Console output: Actual value: my-api-key ``` As shown, when logging the `Redacted` value using `console.log`, the output is ``, ensuring that sensitive data remains concealed. However, by using `Redacted.value`, the true value (`"my-api-key"`) can be accessed and displayed, providing controlled access to the secret. ### Wrapping a Config with Redacted By default, when you pass a string to `Config.redacted`, it returns a `Redacted`. You can also pass a `Config` (such as `Config.number`) to ensure that only validated values are accepted. This adds an extra layer of security by ensuring that sensitive data is properly validated before being redacted. **Example** (Redacting and Validating a Number) ```ts twoslash import { Effect, Config, Redacted } from "effect" const program = Effect.gen(function* () { // Wrap the validated number configuration with redaction // // ┌─── Redacted // ▼ const redacted = yield* Config.redacted(Config.number("SECRET")) console.log(`Console output: ${redacted}`) console.log(`Actual value: ${Redacted.value(redacted)}`) }) Effect.runPromise(program) ``` ## Combining Configurations Effect provides several built-in combinators that allow you to define and manipulate configurations. These combinators take a `Config` as input and produce another `Config`, enabling more complex configuration structures. | Combinator | Description | | ---------- | ------------------------------------------------------------------------------------------------------------------- | | `array` | Constructs a configuration for an array of values. | | `chunk` | Constructs a configuration for a sequence of values. | | `option` | Returns an optional configuration. If the data is missing, the result will be `None`; otherwise, it will be `Some`. | | `repeat` | Describes a sequence of values, each following the structure of the given config. | | `hashSet` | Constructs a configuration for a set of values. | | `hashMap` | Constructs a configuration for a key-value map. | Additionally, there are three special combinators for specific use cases: | Combinator | Description | | ---------- | ------------------------------------------------------------------------ | | `succeed` | Constructs a config that contains a predefined value. | | `fail` | Constructs a config that fails with the specified error message. | | `all` | Combines multiple configurations into a tuple, struct, or argument list. | **Example** (Using the `array` combinator) The following example demonstrates how to load an environment variable as an array of strings using the `Config.array` constructor. ```ts twoslash title="index.ts" import { Config, Effect } from "effect" const program = Effect.gen(function* () { const config = yield* Config.array(Config.string(), "MYARRAY") console.log(config) }) Effect.runPromise(program) // Run: // MYARRAY=a,b,c,a npx tsx index.ts // Output: // [ 'a', 'b c', 'd', 'a' ] ``` **Example** (Using the `hashSet` combinator) ```ts twoslash title="index.ts" import { Config, Effect } from "effect" const program = Effect.gen(function* () { const config = yield* Config.hashSet(Config.string(), "MYSET") console.log(config) }) Effect.runPromise(program) // Run: // MYSET=a,"b c",d,a npx tsx index.ts // Output: // { _id: 'HashSet', values: [ 'd', 'a', 'b c' ] } ``` **Example** (Using the `hashMap` combinator) ```ts twoslash title="index.ts" import { Config, Effect } from "effect" const program = Effect.gen(function* () { const config = yield* Config.hashMap(Config.string(), "MYMAP") console.log(config) }) Effect.runPromise(program) // Run: // MYMAP_A=a MYMAP_B=b npx tsx index.ts // Output: // { _id: 'HashMap', values: [ [ 'A', 'a' ], [ 'B', 'b' ] ] } ``` ## Operators Effect provides several built-in operators to work with configurations, allowing you to manipulate and transform them according to your needs. ### Transforming Operators These operators enable you to modify configurations or validate their values: | Operator | Description | | ------------ | --------------------------------------------------------------------------------------------------------- | | `validate` | Ensures that a configuration meets certain criteria, returning a validation error if it does not. | | `map` | Transforms the values of a configuration using a provided function. | | `mapAttempt` | Similar to `map`, but catches any errors thrown by the function and converts them into validation errors. | | `mapOrFail` | Like `map`, but the function can fail. If it does, the result is a validation error. | **Example** (Using `validate` Operator) ```ts twoslash title="validate.ts" import { Effect, Config } from "effect" const program = Effect.gen(function* () { // Load the NAME environment variable and validate its length const config = yield* Config.string("NAME").pipe( Config.validate({ message: "Expected a string at least 4 characters long", validation: (s) => s.length >= 4 }) ) console.log(config) }) Effect.runPromise(program) ``` If we run this program with an invalid `NAME` value: ```sh showLineNumbers=false NAME=foo npx tsx validate.ts ``` The output will be: ```ansi showLineNumbers=false [Error: (Invalid data at NAME: "Expected a string at least 4 characters long")] { name: '(FiberFailure) Error', [Symbol(effect/Runtime/FiberFailure)]: Symbol(effect/Runtime/FiberFailure), [Symbol(effect/Runtime/FiberFailure/Cause)]: { _tag: 'Fail', error: { _op: 'InvalidData', path: [ 'NAME' ], message: 'Expected a string at least 4 characters long' } } } ``` ### Fallback Operators Fallback operators are useful when you want to provide alternative configurations in case of errors or missing data. These operators ensure that your program can still run even if some configuration values are unavailable. | Operator | Description | | ---------- | ----------------------------------------------------------------------------------------------------- | | `orElse` | Attempts to use the primary config first. If it fails or is missing, it falls back to another config. | | `orElseIf` | Similar to `orElse`, but it switches to the fallback config only if the error matches a condition. | **Example** (Using `orElse` for Fallback) In this example, the program requires two configuration values: `A` and `B`. We set up two configuration providers, each containing only one of the required values. Using the `orElse` operator, we combine these providers so the program can retrieve both `A` and `B`. ```ts twoslash title="orElse.ts" import { Config, ConfigProvider, Effect } from "effect" // A program that requires two configurations: A and B const program = Effect.gen(function* () { const A = yield* Config.string("A") // Retrieve config A const B = yield* Config.string("B") // Retrieve config B console.log(`A: ${A}, B: ${B}`) }) // First provider has A but is missing B const provider1 = ConfigProvider.fromMap(new Map([["A", "A"]])) // Second provider has B but is missing A const provider2 = ConfigProvider.fromMap(new Map([["B", "B"]])) // Use `orElse` to fall back from provider1 to provider2 const provider = provider1.pipe(ConfigProvider.orElse(() => provider2)) Effect.runPromise(Effect.withConfigProvider(program, provider)) ``` If we run this program: ```sh showLineNumbers=false npx tsx orElse.ts ``` The output will be: ```ansi showLineNumbers=false A: A, B: B ``` ## Custom Configuration Types Effect allows you to define configurations for custom types by combining primitive configurations using [combinators](#combining-configurations) and [operators](#operators). For example, let's create a `HostPort` class, which has two fields: `host` and `port`. ```ts twoslash class HostPort { constructor(readonly host: string, readonly port: number) {} get url() { return `${this.host}:${this.port}` } } ``` To define a configuration for this custom type, we can combine primitive configs for `string` and `number`: **Example** (Defining a Custom Configuration) ```ts twoslash import { Config } from "effect" class HostPort { constructor(readonly host: string, readonly port: number) {} get url() { return `${this.host}:${this.port}` } } // Combine the configuration for 'HOST' and 'PORT' const both = Config.all([Config.string("HOST"), Config.number("PORT")]) // Map the configuration values into a HostPort instance const config = Config.map( both, ([host, port]) => new HostPort(host, port) ) ``` In this example, `Config.all(configs)` combines two primitive configurations, `Config` and `Config`, into a `Config<[string, number]>`. The `Config.map` operator is then used to transform these values into an instance of the `HostPort` class. **Example** (Using Custom Configuration) ```ts twoslash title="App.ts" import { Effect, Config } from "effect" class HostPort { constructor(readonly host: string, readonly port: number) {} get url() { return `${this.host}:${this.port}` } } // Combine the configuration for 'HOST' and 'PORT' const both = Config.all([Config.string("HOST"), Config.number("PORT")]) // Map the configuration values into a HostPort instance const config = Config.map( both, ([host, port]) => new HostPort(host, port) ) // Main program that reads configuration and starts the application const program = Effect.gen(function* () { const hostPort = yield* config console.log(`Application started: ${hostPort.url}`) }) Effect.runPromise(program) ``` When you run this program, it will try to retrieve the values for `HOST` and `PORT` from your environment variables: ```sh showLineNumbers=false HOST=localhost PORT=8080 npx tsx App.ts ``` If successful, it will print: ```ansi showLineNumbers=false Application started: localhost:8080 ``` ## Nested Configurations We've seen how to define configurations at the top level, whether for primitive or custom types. In some cases, though, you might want to structure your configurations in a more nested way, organizing them under common namespaces for clarity and manageability. For instance, consider the following `ServiceConfig` type: ```ts twoslash class ServiceConfig { constructor( readonly host: string, readonly port: number, readonly timeout: number ) {} get url() { return `${this.host}:${this.port}` } } ``` If you were to use this configuration in your application, it would expect the `HOST`, `PORT`, and `TIMEOUT` environment variables at the top level. But in many cases, you may want to organize configurations under a shared namespace—for example, grouping `HOST` and `PORT` under a `SERVER` namespace, while keeping `TIMEOUT` at the root. To do this, you can use the `Config.nested` operator, which allows you to nest configuration values under a specific namespace. Let's update the previous example to reflect this: ```ts twoslash import { Config } from "effect" class ServiceConfig { constructor( readonly host: string, readonly port: number, readonly timeout: number ) {} get url() { return `${this.host}:${this.port}` } } const serverConfig = Config.all([ Config.string("HOST"), Config.number("PORT") ]) const serviceConfig = Config.map( Config.all([ // Read 'HOST' and 'PORT' from 'SERVER' namespace Config.nested(serverConfig, "SERVER"), // Read 'TIMEOUT' from the root namespace Config.number("TIMEOUT") ]), ([[host, port], timeout]) => new ServiceConfig(host, port, timeout) ) ``` Now, if you run your application with this configuration setup, it will look for the following environment variables: - `SERVER_HOST` for the host value - `SERVER_PORT` for the port value - `TIMEOUT` for the timeout value This structured approach keeps your configuration more organized, especially when dealing with multiple services or complex applications. ## Mocking Configurations in Tests When testing services, there are times when you need to provide specific configurations for your tests. To simulate this, it's useful to mock the configuration backend that reads these values. You can achieve this using the `ConfigProvider.fromMap` constructor. This method allows you to create a configuration provider from a `Map`, where the map represents the configuration data. You can then use this mock provider in place of the default one by calling `Effect.withConfigProvider`. **Example** (Mocking a Config Provider for Testing) ```ts twoslash import { Config, ConfigProvider, Effect } from "effect" class HostPort { constructor(readonly host: string, readonly port: number) {} get url() { return `${this.host}:${this.port}` } } const config = Config.map( Config.all([Config.string("HOST"), Config.number("PORT")]), ([host, port]) => new HostPort(host, port) ) const program = Effect.gen(function* () { const hostPort = yield* config console.log(`Application started: ${hostPort.url}`) }) // Create a mock config provider using a map with test data const mockConfigProvider = ConfigProvider.fromMap( new Map([ ["HOST", "localhost"], ["PORT", "8080"] ]) ) // Run the program using the mock config provider Effect.runPromise(Effect.withConfigProvider(program, mockConfigProvider)) // Output: Application started: localhost:8080 ``` This approach helps you create isolated tests that don't rely on external environment variables, ensuring your tests run consistently with mock configurations. ### Handling Nested Configuration Values For more complex setups, configurations often include nested keys. By default, `ConfigProvider.fromMap` uses `.` as the separator for nested keys. **Example** (Providing Nested Configuration Values) ```ts twoslash import { Config, ConfigProvider, Effect } from "effect" const config = Config.nested(Config.number("PORT"), "SERVER") const program = Effect.gen(function* () { const port = yield* config console.log(`Server is running on port ${port}`) }) // Mock configuration using '.' as the separator for nested keys const mockConfigProvider = ConfigProvider.fromMap( new Map([["SERVER.PORT", "8080"]]) ) Effect.runPromise(Effect.withConfigProvider(program, mockConfigProvider)) // Output: Server is running on port 8080 ``` ### Customizing the Path Delimiter If your configuration data uses a different separator (such as `_`), you can change the delimiter using the `pathDelim` option in `ConfigProvider.fromMap`. **Example** (Using a Custom Path Delimiter) ```ts twoslash import { Config, ConfigProvider, Effect } from "effect" const config = Config.nested(Config.number("PORT"), "SERVER") const program = Effect.gen(function* () { const port = yield* config console.log(`Server is running on port ${port}`) }) // Mock configuration using '_' as the separator const mockConfigProvider = ConfigProvider.fromMap( new Map([["SERVER_PORT", "8080"]]), { pathDelim: "_" } ) Effect.runPromise(Effect.withConfigProvider(program, mockConfigProvider)) // Output: Server is running on port 8080 ``` ## ConfigProvider The `ConfigProvider` module in Effect allows applications to load configuration values from different sources. The default provider reads from environment variables, but you can customize its behavior when needed. ### Loading Configuration from Environment Variables The `ConfigProvider.fromEnv` function creates a `ConfigProvider` that loads values from environment variables. This is the default provider used by Effect unless another is specified. If your application requires a custom delimiter for nested configuration keys, you can configure `ConfigProvider.fromEnv` accordingly. **Example** (Changing the Path Delimiter) The following example modifies the path delimiter (`"__"`) and sequence delimiter (`"|"`) for environment variables. ```ts twoslash title="index.ts" import { Config, ConfigProvider, Effect } from "effect" const program = Effect.gen(function* () { // Read SERVER_HOST and SERVER_PORT as nested configuration values const port = yield* Config.nested(Config.number("PORT"), "SERVER") const host = yield* Config.nested(Config.string("HOST"), "SERVER") console.log(`Application started: ${host}:${port}`) }) Effect.runPromise( Effect.withConfigProvider( program, // Custom delimiters ConfigProvider.fromEnv({ pathDelim: "__", seqDelim: "|" }) ) ) ``` To match the custom delimiter (`"__"`), set environment variables like this: ```sh showLineNumbers=false SERVER__HOST=localhost SERVER__PORT=8080 npx tsx index.ts ``` Output: ```ansi showLineNumbers=false Application started: localhost:8080 ``` ### Loading Configuration from JSON The `ConfigProvider.fromJson` function creates a `ConfigProvider` that loads values from a JSON object. **Example** (Reading Nested Configuration from JSON) ```ts twoslash import { Config, ConfigProvider, Effect } from "effect" const program = Effect.gen(function* () { // Read SERVER_HOST and SERVER_PORT as nested configuration values const port = yield* Config.nested(Config.number("PORT"), "SERVER") const host = yield* Config.nested(Config.string("HOST"), "SERVER") console.log(`Application started: ${host}:${port}`) }) Effect.runPromise( Effect.withConfigProvider( program, ConfigProvider.fromJson( JSON.parse(`{"SERVER":{"PORT":8080,"HOST":"localhost"}}`) ) ) ) // Output: Application started: localhost:8080 ``` ### Using Nested Configuration Namespaces The `ConfigProvider.nested` function allows **grouping configuration values** under a namespace. This is helpful when structuring settings logically, such as grouping `SERVER`-related values. **Example** (Using a Nested Namespace) ```ts twoslash title="index.ts" import { Config, ConfigProvider, Effect } from "effect" const program = Effect.gen(function* () { const port = yield* Config.number("PORT") // Reads SERVER_PORT const host = yield* Config.string("HOST") // Reads SERVER_HOST console.log(`Application started: ${host}:${port}`) }) Effect.runPromise( Effect.withConfigProvider( program, ConfigProvider.fromEnv().pipe( // Uses SERVER as a namespace ConfigProvider.nested("SERVER") ) ) ) ``` Since we defined `"SERVER"` as the namespace, the environment variables must follow this pattern: ```sh showLineNumbers=false SERVER_HOST=localhost SERVER_PORT=8080 npx tsx index.ts ``` Output: ```ansi showLineNumbers=false Application started: localhost:8080 ``` ### Converting Configuration Keys to Constant Case The `ConfigProvider.constantCase` function transforms all configuration keys into constant case (uppercase with underscores). This is useful when adapting environment variables to match different naming conventions. **Example** (Using `constantCase` for Environment Variables) ```ts twoslash title="index.ts" import { Config, ConfigProvider, Effect } from "effect" const program = Effect.gen(function* () { const port = yield* Config.number("Port") // Reads PORT const host = yield* Config.string("Host") // Reads HOST console.log(`Application started: ${host}:${port}`) }) Effect.runPromise( Effect.withConfigProvider( program, // Convert keys to constant case ConfigProvider.fromEnv().pipe(ConfigProvider.constantCase) ) ) ``` Since `constantCase` converts `"Port"` → `"PORT"` and `"Host"` → `"HOST"`, the environment variables must be set as follows: ```sh showLineNumbers=false HOST=localhost PORT=8080 npx tsx index.ts ``` Output: ```ansi showLineNumbers=false Application started: localhost:8080 ``` ## Deprecations ### Secret _Deprecated since version 3.3.0: Please use [Config.redacted](#handling-sensitive-values) for handling sensitive information going forward._ The `Config.secret` function was previously used to secure sensitive information in a similar way to `Config.redacted`. It wraps configuration values in a `Secret` type, which also conceals details when logged but allows access via `Secret.value`. **Example** (Using Deprecated `Config.secret`) ```ts twoslash title="secret.ts" import { Effect, Config, Secret } from "effect" const program = Effect.gen(function* () { const secret = yield* Config.secret("API_KEY") // Log the secret value, which won't reveal the actual secret console.log(`Console output: ${secret}`) // Access the real value using Secret.value and log it console.log(`Actual value: ${Secret.value(secret)}`) }) Effect.runPromise(program) ``` When this program is executed: ```sh showLineNumbers=false API_KEY=my-api-key tsx secret.ts ``` The output will look like this: ```ansi showLineNumbers=false Console output: Secret() Actual value: my-api-key ```
# [Introduction to Runtime](https://effect.website/docs/runtime/)
## Overview The `Runtime` data type represents a runtime system that can **execute effects**. To run an effect, `Effect`, we need a `Runtime` that contains the required resources, denoted by the `R` type parameter. A `Runtime` consists of three main components: - A value of type `Context` - A value of type `FiberRefs` - A value of type `RuntimeFlags` ## What is a Runtime System? When we write an Effect program, we construct an `Effect` using constructors and combinators. Essentially, we are creating a blueprint of a program. An `Effect` is merely a data structure that describes the execution of a concurrent program. It represents a tree-like structure that combines various primitives to define what the effect should do. However, this data structure itself does not perform any actions, it is solely a description of a concurrent program. To execute this program, the Effect runtime system comes into play. The `Runtime.run*` functions (e.g., `Runtime.runPromise`, `Runtime.runFork`) are responsible for taking this blueprint and executing it. When the runtime system runs an effect, it creates a root fiber, initializing it with: - The initial [context](/docs/requirements-management/services/#how-it-works) - The initial `FiberRefs` - The initial effect It then starts a loop, executing the instructions described by the `Effect` step by step. You can think of the runtime as a system that takes an [`Effect`](/docs/getting-started/the-effect-type/) and its associated context `Context` and produces an [`Exit`](/docs/data-types/exit/) result. ```text showLineNumbers=false ┌────────────────────────────────┐ │ Context + Effect │ └────────────────────────────────┘ │ ▼ ┌────────────────────────────────┐ │ Effect Runtime System │ └────────────────────────────────┘ │ ▼ ┌────────────────────────────────┐ │ Exit │ └────────────────────────────────┘ ``` Runtime Systems have a lot of responsibilities: | Responsibility | Description | | ----------------------------- | ------------------------------------------------------------------------------------------------------------------ | | **Executing the program** | The runtime must execute every step of the effect in a loop until the program completes. | | **Handling errors** | It handles both expected and unexpected errors that occur during execution. | | **Managing concurrency** | The runtime spawns new fibers when `Effect.fork` is called to handle concurrent operations. | | **Cooperative yielding** | It ensures fibers don't monopolize resources, yielding control when necessary. | | **Ensuring resource cleanup** | The runtime guarantees finalizers run properly to clean up resources when needed. | | **Handling async callbacks** | The runtime deals with asynchronous operations transparently, allowing you to write async and sync code uniformly. | ## The Default Runtime When we use [functions that run effects](/docs/getting-started/running-effects/) like `Effect.runPromise` or `Effect.runFork`, we are actually using the **default runtime** without explicitly mentioning it. These functions are designed as convenient shortcuts for executing our effects using the default runtime. Each of the `Effect.run*` functions internally calls the corresponding `Runtime.run*` function, passing in the default runtime. For example, `Effect.runPromise` is just an alias for `Runtime.runPromise(defaultRuntime)`. Both of the following executions are functionally equivalent: **Example** (Running an Effect Using the Default Runtime) ```ts twoslash import { Effect, Runtime } from "effect" const program = Effect.log("Application started!") Effect.runPromise(program) /* Output: timestamp=... level=INFO fiber=#0 message="Application started!" */ Runtime.runPromise(Runtime.defaultRuntime)(program) /* Output: timestamp=... level=INFO fiber=#0 message="Application started!" */ ``` In both cases, the program runs using the default runtime, producing the same output. The default runtime includes: - An empty [context](/docs/requirements-management/services/#how-it-works) - A set of `FiberRefs` that include the [default services](/docs/requirements-management/default-services/) - A default configuration for `RuntimeFlags` that enables `Interruption` and `CooperativeYielding` In most scenarios, using the default runtime is sufficient for effect execution. However, there are cases where it's helpful to create a custom runtime, particularly when you need to reuse specific configurations or contexts. For example, in a React app or when executing operations on a server in response to API requests, you might create a `Runtime` by initializing a [layer](/docs/requirements-management/layers/) `Layer`. This allows you to maintain a consistent context across different execution boundaries. ## Locally Scoped Runtime Configuration In Effect, runtime configurations are typically **inherited** from their parent workflows. This means that when we access a runtime configuration or obtain a runtime inside a workflow, we are essentially using the configuration of the parent workflow. However, there are cases where we want to temporarily **override the runtime configuration for a specific part** of our code. This concept is known as locally scoped runtime configuration. Once the execution of that code region is completed, the runtime configuration **reverts** to its original settings. To achieve this, we make use of the `Effect.provide` function, which allow us to provide a new runtime configuration to a specific section of our code. **Example** (Overriding the Logger Configuration) In this example, we create a simple logger using `Logger.replace`, which replaces the default logger with a custom one that logs messages without timestamps or levels. We then use `Effect.provide` to apply this custom logger to the program. ```ts twoslash import { Logger, Effect } from "effect" const addSimpleLogger = Logger.replace( Logger.defaultLogger, // Custom logger implementation Logger.make(({ message }) => console.log(message)) ) const program = Effect.gen(function* () { yield* Effect.log("Application started!") yield* Effect.log("Application is about to exit!") }) // Running with the default logger Effect.runFork(program) /* Output: timestamp=... level=INFO fiber=#0 message="Application started!" timestamp=... level=INFO fiber=#0 message="Application is about to exit!" */ // Overriding the default logger with a custom one Effect.runFork(program.pipe(Effect.provide(addSimpleLogger))) /* Output: [ 'Application started!' ] [ 'Application is about to exit!' ] */ ``` To ensure that the runtime configuration is only applied to a specific part of an Effect application, we should provide the configuration layer exclusively to that particular section. **Example** (Providing a configuration layer to a nested workflow) In this example, we demonstrate how to apply a custom logger configuration only to a specific section of the program. The default logger is used for most of the program, but when we apply the `Effect.provide(addSimpleLogger)` call, it overrides the logger within that specific nested block. After that, the configuration reverts to its original state. ```ts twoslash import { Logger, Effect } from "effect" const addSimpleLogger = Logger.replace( Logger.defaultLogger, // Custom logger implementation Logger.make(({ message }) => console.log(message)) ) const removeDefaultLogger = Logger.remove(Logger.defaultLogger) const program = Effect.gen(function* () { // Logs with default logger yield* Effect.log("Application started!") yield* Effect.gen(function* () { // This log is suppressed yield* Effect.log("I'm not going to be logged!") // Custom logger applied here yield* Effect.log("I will be logged by the simple logger.").pipe( Effect.provide(addSimpleLogger) ) // This log is suppressed yield* Effect.log( "Reset back to the previous configuration, so I won't be logged." ) }).pipe( // Remove the default logger temporarily Effect.provide(removeDefaultLogger) ) // Logs with default logger again yield* Effect.log("Application is about to exit!") }) Effect.runSync(program) /* Output: timestamp=... level=INFO fiber=#0 message="Application started!" [ 'I will be logged by the simple logger.' ] timestamp=... level=INFO fiber=#0 message="Application is about to exit!" */ ``` ## ManagedRuntime When developing an Effect application and using `Effect.run*` functions to execute it, the application is automatically run using the default runtime behind the scenes. While it’s possible to adjust specific parts of the application by providing locally scoped configuration layers using `Effect.provide`, there are scenarios where you might want to **customize the runtime configuration for the entire application** from the top level. In these cases, you can create a top-level runtime by converting a configuration layer into a runtime using the `ManagedRuntime.make` constructor. **Example** (Creating and Using a Custom Managed Runtime) In this example, we first create a custom configuration layer called `appLayer`, which replaces the default logger with a simple one that logs messages to the console. Next, we use `ManagedRuntime.make` to turn this configuration layer into a runtime. ```ts twoslash import { Effect, ManagedRuntime, Logger } from "effect" // Define a configuration layer that replaces the default logger const appLayer = Logger.replace( Logger.defaultLogger, // Custom logger implementation Logger.make(({ message }) => console.log(message)) ) // Create a custom runtime from the configuration layer const runtime = ManagedRuntime.make(appLayer) const program = Effect.log("Application started!") // Execute the program using the custom runtime runtime.runSync(program) // Clean up resources associated with the custom runtime Effect.runFork(runtime.disposeEffect) /* Output: [ 'Application started!' ] */ ``` ### Effect.Tag When working with runtimes that you pass around, `Effect.Tag` can help simplify the access to services. It lets you define a new tag and embed the service shape directly into the static properties of the tag class. **Example** (Defining a Tag for Notifications) ```ts twoslash import { Effect } from "effect" class Notifications extends Effect.Tag("Notifications")< Notifications, { readonly notify: (message: string) => Effect.Effect } >() {} ``` In this setup, the fields of the service (in this case, the `notify` method) are turned into static properties of the `Notifications` class, making it easier to access them. This allows you to interact with the service directly: **Example** (Using the Notifications Tag) ```ts twoslash import { Effect } from "effect" class Notifications extends Effect.Tag("Notifications")< Notifications, { readonly notify: (message: string) => Effect.Effect } >() {} // Create an effect that depends on the Notifications service const action = Notifications.notify("Hello, world!") // ^? const action: Effect ``` In this example, the `action` effect depends on the `Notifications` service. This approach allows you to reference services without manually passing them around. Later, you can create a `Layer` that provides the `Notifications` service and build a `ManagedRuntime` with that layer to ensure the service is available where needed. ### Integrations The `ManagedRuntime` simplifies the integration of services and layers with other frameworks or tools, particularly in environments where Effect is not the primary framework and access to the main entry point is restricted. For example, in environments like React or other frameworks where you have limited control over the main application entry point, `ManagedRuntime` helps manage the lifecycle of services. Here's how to manage a service's lifecycle within an external framework: **Example** (Using `ManagedRuntime` in an External Framework) ```ts twoslash import { Effect, ManagedRuntime, Layer, Console } from "effect" // Define the Notifications service using Effect.Tag class Notifications extends Effect.Tag("Notifications")< Notifications, { readonly notify: (message: string) => Effect.Effect } >() { // Provide a live implementation of the Notifications service static Live = Layer.succeed(this, { notify: (message) => Console.log(message) }) } // Example entry point for an external framework async function main() { // Create a custom runtime using the Notifications layer const runtime = ManagedRuntime.make(Notifications.Live) // Run the effect await runtime.runPromise(Notifications.notify("Hello, world!")) // Dispose of the runtime, cleaning up resources await runtime.dispose() } ```
# [API Reference](https://effect.website/docs/additional-resources/api-reference/)
## Overview - [`effect`](https://effect-ts.github.io/effect/docs/effect) - [`@effect/cli`](https://effect-ts.github.io/effect/docs/cli) ([Getting Started](https://github.com/Effect-TS/effect/blob/main/packages/cli/README.md)) - [`@effect/opentelemetry`](https://effect-ts.github.io/effect/docs/opentelemetry) - [`@effect/platform`](https://effect-ts.github.io/effect/docs/platform) ([Experimental Features](https://github.com/Effect-TS/effect/blob/main/packages/platform/README.md)) - [`@effect/printer`](https://effect-ts.github.io/effect/docs/printer) ([Getting Started](https://github.com/Effect-TS/effect/blob/main/packages/printer/README.md)) - [`@effect/rpc`](https://effect-ts.github.io/effect/docs/rpc) ([Getting Started](https://github.com/Effect-TS/effect/blob/main/packages/rpc/README.md)) - [`@effect/typeclass`](https://effect-ts.github.io/effect/docs/typeclass) ([Getting Started](https://github.com/Effect-TS/effect/blob/main/packages/typeclass/README.md))
# [Coming From ZIO](https://effect.website/docs/additional-resources/coming-from-zio/)
## Overview If you are coming to Effect from ZIO, there are a few differences to be aware of. ## Environment In Effect, we represent the environment required to run an effect workflow as a **union** of services: **Example** (Defining the Environment with a Union of Services) ```ts twoslash "Console | Logger" import { Effect } from "effect" interface IOError { readonly _tag: "IOError" } interface HttpError { readonly _tag: "HttpError" } interface Console { readonly log: (msg: string) => void } interface Logger { readonly log: (msg: string) => void } type Response = Record // `R` is a union of `Console` and `Logger` type Http = Effect.Effect ``` This may be confusing to folks coming from ZIO, where the environment is represented as an **intersection** of services: ```scala showLineNumbers=false type Http = ZIO[Console with Logger, IOError, Response] ``` ## Rationale The rationale for using a union to represent the environment required by an `Effect` workflow boils down to our desire to remove `Has` as a wrapper for services in the environment (similar to what was achieved in ZIO 2.0). To be able to remove `Has` from Effect, we had to think a bit more structurally given that TypeScript is a structural type system. In TypeScript, if you have a type `A & B` where there is a structural conflict between `A` and `B`, the type `A & B` will reduce to `never`. **Example** (Intersection Type Conflict) ```ts twoslash interface A { readonly prop: string } interface B { readonly prop: number } const ab: A & B = { // @ts-expect-error prop: "" } /* Type 'string' is not assignable to type 'never'.ts(2322) */ ``` In previous versions of Effect, intersections were used for representing an environment with multiple services. The problem with using intersections (i.e. `A & B`) is that there could be multiple services in the environment that have functions and properties named in the same way. To remedy this, we wrapped services in the `Has` type (similar to ZIO 1.0), so you would have `Has & Has` in your environment. In ZIO 2.0, the _contravariant_ `R` type parameter of the `ZIO` type (representing the environment) became fully phantom, thus allowing for removal of the `Has` type. This significantly improved the clarity of type signatures as well as removing another "stumbling block" for new users. To facilitate removal of `Has` in Effect, we had to consider how types in the environment compose. By the rule of composition, contravariant parameters composed as an intersection (i.e. with `&`) are equivalent to covariant parameters composed together as a union (i.e. with `|`) for purposes of assignability. Based upon this fact, we decided to diverge from ZIO and make the `R` type parameter _covariant_ given `A | B` does not reduce to `never` if `A` and `B` have conflicts. From our example above: ```ts twoslash interface A { readonly prop: string } interface B { readonly prop: number } // ok const ab: A | B = { prop: "" } ``` Representing `R` as a covariant type parameter containing the union of services required by an `Effect` workflow allowed us to remove the requirement for `Has`. ## Type Aliases In Effect, there are no predefined type aliases such as `UIO`, `URIO`, `RIO`, `Task`, or `IO` like in ZIO. The reason for this is that type aliases are lost as soon as you compose them, which renders them somewhat useless unless you maintain **multiple** signatures for **every** function. In Effect, we have chosen not to go down this path. Instead, we utilize the `never` type to indicate unused types. It's worth mentioning that the perception of type aliases being quicker to understand is often just an illusion. In Effect, the explicit notation `Effect` clearly communicates that only type `A` is being used. On the other hand, when using a type alias like `RIO`, questions arise about the type `E`. Is it `unknown`? `never`? Remembering such details becomes challenging.
# [Effect vs fp-ts](https://effect.website/docs/additional-resources/effect-vs-fp-ts/)
## Overview ## Key Developments - **Project Merger**: The fp-ts project is officially merging with the Effect-TS ecosystem. Giulio Canti, the author of fp-ts, is being welcomed into the Effect organization. For more details, see the [announcement here](https://dev.to/effect/a-bright-future-for-effect-455m). - **Continuity and Evolution**: Effect can be seen as the successor to fp-ts v2 and is effectively fp-ts v3, marking a significant evolution in the library's capabilities. ## FAQ ### Bundle Size Comparison Between Effect and fp-ts **Q: I compared the bundle sizes of two simple programs using Effect and fp-ts. Why does Effect have a larger bundle size?** A: It's natural to observe different bundle sizes because Effect and fp-ts are distinct systems designed for different purposes. Effect's bundle size is larger due to its included fiber runtime, which is crucial for its functionality. While the initial bundle size may seem large, the overhead amortizes as you use Effect. **Q: Should I be concerned about the bundle size difference when choosing between Effect and fp-ts?** A: Not necessarily. Consider the specific requirements and benefits of each library for your project. The **Micro** module in Effect is designed as a lightweight alternative to the standard `Effect` module, specifically for scenarios where reducing bundle size is crucial. This module is self-contained and does not include more complex features like `Layer`, `Ref`, `Queue`, and `Deferred`. If any major Effect modules (beyond basic data modules like `Option`, `Either`, `Array`, etc.) are used, the effect runtime will be added to your bundle, negating the benefits of Micro. This makes Micro ideal for libraries that aim to implement Effect functionality with minimal impact on bundle size, especially for libraries that plan to expose `Promise`-based APIs. It also supports scenarios where a client might use Micro while a server uses the full suite of Effect features, maintaining compatibility and shared logic between different parts of an application. ## Comparison Table The following table compares the features of the Effect and [fp-ts](https://github.com/gcanti/fp-ts) libraries. | Feature | fp-ts | Effect | | ------------------------- | ----- | ------ | | Typed Services | ❌ | ✅ | | Built-in Services | ❌ | ✅ | | Typed errors | ✅ | ✅ | | Pipeable APIs | ✅ | ✅ | | Dual APIs | ❌ | ✅ | | Testability | ❌ | ✅ | | Resource Management | ❌ | ✅ | | Interruptions | ❌ | ✅ | | Defects | ❌ | ✅ | | Fiber-Based Concurrency | ❌ | ✅ | | Fiber Supervision | ❌ | ✅ | | Retry and Retry Policies | ❌ | ✅ | | Built-in Logging | ❌ | ✅ | | Built-in Scheduling | ❌ | ✅ | | Built-in Caching | ❌ | ✅ | | Built-in Batching | ❌ | ✅ | | Metrics | ❌ | ✅ | | Tracing | ❌ | ✅ | | Configuration | ❌ | ✅ | | Immutable Data Structures | ❌ | ✅ | | Stream Processing | ❌ | ✅ | Here's an explanation of each feature: ### Typed Services Both fp-ts and Effect libraries provide the ability to track requirements at the type level, allowing you to define and use services with specific types. In fp-ts, you can utilize the `ReaderTaskEither` type, while in Effect, the `Effect` type is available. It's important to note that in fp-ts, the `R` type parameter is contravariant, which means that there is no guarantee of avoiding conflicts, and the library offers only basic tools for dependency management. On the other hand, in Effect, the `R` type parameter is covariant and all APIs have the ability to merge dependencies at the type level when multiple effects are involved. Effect also provides a range of specifically designed tools to simplify the management of dependencies, including `Tag`, `Context`, and `Layer`. These tools enhance the ease and flexibility of handling dependencies in your code, making it easier to compose and manage complex applications. ### Built-in Services The Effect library has built-in services like `Clock`, `Random` and `Tracer`, while fp-ts does not provide any default services. ### Typed errors Both libraries support typed errors, enabling you to define and handle errors with specific types. However, in Effect, all APIs have the ability to merge errors at the type-level when multiple effects are involved, and each effect can potentially fail with different types of errors. This means that when combining multiple effects that can fail, the resulting type of the error will be a union of the individual error types. Effect provides utilities and type-level operations to handle and manage these merged error types effectively. ### Pipeable APIs Both fp-ts and Effect libraries provide pipeable APIs, allowing you to compose and sequence operations in a functional and readable manner using the `pipe` function. However, Effect goes a step further and offers a `.pipe()` method on each data type, making it more convenient to work with pipelines without the need to explicitly import the `pipe` function every time. ### Dual APIs Effect library provides dual APIs, allowing you to use the same API in different ways (e.g., "data-last" and "data-first" variants). ### Testability The functional style of fp-ts generally promotes good testability of the code written using it, but the library itself does not provide dedicated tools specifically designed for the testing phase. On the other hand, Effect takes testability a step further by offering additional tools that are specifically tailored to simplify the testing process. Effect provides a range of utilities that improve testability. For example, it offers the `TestClock` utility, which allows you to control the passage of time during tests. This is useful for testing time-dependent code. Additionally, Effect provides the `TestRandom` utility, which enables fully deterministic testing of code that involves randomness. This ensures consistent and predictable test results. Another helpful tool is `ConfigProvider.fromMap`, which makes it easy to define mock configurations for your application during testing. ### Resource Management The Effect library provides built-in capabilities for resource management, while fp-ts has limited features in this area (mainly `bracket`) and they are less sophisticated. In Effect, resource management refers to the ability to acquire and release resources, such as database connections, file handles, or network sockets, in a safe and controlled manner. The library offers comprehensive and refined mechanisms to handle resource acquisition and release, ensuring proper cleanup and preventing resource leaks. ### Interruptions The Effect library supports interruptions, which means you can interrupt and cancel ongoing computations if needed. This feature gives you more control over the execution of your code and allows you to handle situations where you want to stop a computation before it completes. In Effect, interruptions are useful in scenarios where you need to handle user cancellations, timeouts, or other external events that require stopping ongoing computations. You can explicitly request an interruption and the library will safely and efficiently halt the execution of the computation. On the other hand, fp-ts does not have built-in support for interruptions. Once a computation starts in fp-ts, it will continue until it completes or encounters an error, without the ability to be interrupted midway. ### Defects The Effect library provides mechanisms for handling defects and managing **unexpected** failures. In Effect, defects refer to unexpected errors or failures that can occur during the execution of a program. With the Effect library, you have built-in tools and utilities to handle defects in a structured and reliable manner. It offers error handling capabilities that allow you to catch and handle exceptions, recover from failures, and gracefully handle unexpected scenarios. On the other hand, fp-ts does not have built-in support specifically dedicated to managing defects. While you can handle errors using standard functional programming techniques in fp-ts, the Effect library provides a more comprehensive and streamlined approach to dealing with defects. ### Fiber-Based Concurrency The Effect library leverages fiber-based concurrency, which enables lightweight and efficient concurrent computations. In simpler terms, fiber-based concurrency allows multiple tasks to run concurrently, making your code more responsive and efficient. With fiber-based concurrency, the Effect library can handle concurrent operations in a way that is lightweight and doesn't block the execution of other tasks. This means that you can run multiple computations simultaneously, taking advantage of the available resources and maximizing performance. On the other hand, fp-ts does not have built-in support for fiber-based concurrency. While fp-ts provides a rich set of functional programming features, it doesn't have the same level of support for concurrent computations as the Effect library. ### Fiber Supervision Effect library provides supervision strategies for managing and monitoring fibers. fp-ts does not have built-in support for fiber supervision. ### Retry and Retry Policies The Effect library includes built-in support for retrying computations with customizable retry policies. This feature is not available in fp-ts out of the box, and you would need to rely on external libraries to achieve similar functionality. However, it's important to note that the external libraries may not offer the same level of sophistication and fine-tuning as the built-in retry capabilities provided by the Effect library. Retry functionality allows you to automatically retry a computation or action when it fails, based on a set of predefined rules or policies. This can be particularly useful in scenarios where you are working with unreliable or unpredictable resources, such as network requests or external services. The Effect library provides a comprehensive set of retry policies that you can customize to fit your specific needs. These policies define the conditions for retrying a computation, such as the number of retries, the delay between retries, and the criteria for determining if a retry should be attempted. By leveraging the built-in retry functionality in the Effect library, you can handle transient errors or temporary failures in a more robust and resilient manner. This can help improve the overall reliability and stability of your applications, especially in scenarios where you need to interact with external systems or services. In contrast, fp-ts does not offer built-in support for retrying computations. If you require retry functionality in fp-ts, you would need to rely on external libraries, which may not provide the same level of sophistication and flexibility as the Effect library. It's worth noting that the built-in retry capabilities of the Effect library are designed to work seamlessly with its other features, such as error handling and resource management. This integration allows for a more cohesive and comprehensive approach to handling failures and retries within your computations. ### Built-in Logging The Effect library comes with built-in logging capabilities. This means that you can easily incorporate logging into your applications without the need for additional libraries or dependencies. In addition, the default logger provided by Effect can be replaced with a custom logger to suit your specific logging requirements. Logging is an essential aspect of software development as it allows you to record and track important information during the execution of your code. It helps you monitor the behavior of your application, debug issues, and gather insights for analysis. With the built-in logging capabilities of the Effect library, you can easily log messages, warnings, errors, or any other relevant information at various points in your code. This can be particularly useful for tracking the flow of execution, identifying potential issues, or capturing important events during the operation of your application. On the other hand, fp-ts does not provide built-in logging capabilities. If you need logging functionality in fp-ts, you would need to rely on external libraries or implement your own logging solution from scratch. This can introduce additional complexity and dependencies into your codebase. ### Built-in Scheduling The Effect library provides built-in scheduling capabilities, which allows you to manage the execution of computations over time. This feature is not available in fp-ts. In many applications, it's common to have tasks or computations that need to be executed at specific intervals or scheduled for future execution. For example, you might want to perform periodic data updates, trigger notifications, or run background processes at specific times. This is where built-in scheduling comes in handy. On the other hand, fp-ts does not have built-in scheduling capabilities. If you need to schedule tasks or manage timed computations in fp-ts, you would have to rely on external libraries or implement your own scheduling mechanisms, which can add complexity to your codebase. ### Built-in Caching The Effect library provides built-in caching mechanisms, which enable you to cache the results of computations for improved performance. This feature is not available in fp-ts. In many applications, computations can be time-consuming or resource-intensive, especially when dealing with complex operations or accessing remote resources. Caching is a technique used to store the results of computations so that they can be retrieved quickly without the need to recompute them every time. With the built-in caching capabilities of the Effect library, you can easily cache the results of computations and reuse them when needed. This can significantly improve the performance of your application by avoiding redundant computations and reducing the load on external resources. ### Built-in Batching The Effect library offers built-in batching capabilities, which enable you to combine multiple computations into a single batched computation. This feature is not available in fp-ts. In many scenarios, you may need to perform multiple computations that share similar inputs or dependencies. Performing these computations individually can result in inefficiencies and increased overhead. Batching is a technique that allows you to group these computations together and execute them as a single batch, improving performance and reducing unnecessary processing. ### Metrics The Effect library includes built-in support for collecting and reporting metrics related to computations and system behavior. It specifically supports [OpenTelemetry Metrics](https://opentelemetry.io/docs/specs/otel/metrics/). This feature is not available in fp-ts. Metrics play a crucial role in understanding and monitoring the performance and behavior of your applications. They provide valuable insights into various aspects, such as response times, resource utilization, error rates, and more. By collecting and analyzing metrics, you can identify performance bottlenecks, optimize your code, and make informed decisions to improve your application's overall quality. ### Tracing The Effect library has built-in tracing capabilities, which enable you to trace and debug the execution of your code and track the path of a request through an application. Additionally, Effect offers a dedicated [OpenTelemetry exporter](https://opentelemetry.io/docs/instrumentation/js/exporters/) for integrating with the OpenTelemetry observability framework. In contrast, fp-ts does not offer a similar tracing tool to enhance visibility into code execution. ### Configuration The Effect library provides built-in support for managing and accessing configuration values within your computations. This feature is not available in fp-ts. Configuration values are an essential aspect of software development. They allow you to customize the behavior of your applications without modifying the code. Examples of configuration values include database connection strings, API endpoints, feature flags, and various settings that can vary between environments or deployments. With the Effect library's built-in support for configuration, you can easily manage and access these values within your computations. It provides convenient utilities and abstractions to load, validate, and access configuration values, ensuring that your application has the necessary settings it requires to function correctly. By leveraging the built-in configuration support in the Effect library, you can: - Load configuration values from various sources such as environment variables, configuration files, or remote configuration providers. - Validate and ensure that the loaded configuration values adhere to the expected format and structure. - Access the configuration values within your computations, allowing you to use them wherever necessary. ### Immutable Data Structures The Effect library provides built-in support for immutable data structures such as `Chunk`, `HashSet`, and `HashMap`. These data structures ensure that once created, their values cannot be modified, promoting safer and more predictable code. In contrast, fp-ts does not have built-in support for such data structures and only provides modules that add additional APIs to standard data types like `Set` and `Map`. While these modules can be useful, they do not offer the same level of performance optimizations and specialized operations as the built-in immutable data structures provided by the Effect library. Immutable data structures offer several benefits, including: - Immutability: Immutable data structures cannot be changed after they are created. This property eliminates the risk of accidental modifications and enables safer concurrent programming. - Predictability: With immutable data structures, you can rely on the fact that their values won't change unexpectedly. This predictability simplifies reasoning about code behavior and reduces bugs caused by mutable state. - Sharing and Reusability: Immutable data structures can be safely shared between different parts of your program. Since they cannot be modified, you don't need to create defensive copies, resulting in more efficient memory usage and improved performance. ### Stream Processing The Effect ecosystem provides built-in support for stream processing, enabling you to work with streams of data. Stream processing is a powerful concept that allows you to efficiently process and transform continuous streams of data in a reactive and asynchronous manner. However, fp-ts does not have this feature built-in and relies on external libraries like RxJS to handle stream processing.
# [Effect vs Promise](https://effect.website/docs/additional-resources/effect-vs-promise/)
## Overview import { Tabs, TabItem } from "@astrojs/starlight/components" In this guide, we will explore the differences between `Promise` and `Effect`, two approaches to handling asynchronous operations in TypeScript. We'll discuss their type safety, creation, chaining, and concurrency, providing examples to help you understand their usage. ## Comparing Effects and Promises: Key Distinctions - **Evaluation Strategy:** Promises are eagerly evaluated, whereas effects are lazily evaluated. - **Execution Mode:** Promises are one-shot, executing once, while effects are multi-shot, repeatable. - **Interruption Handling and Automatic Propagation:** Promises lack built-in interruption handling, posing challenges in managing interruptions, and don't automatically propagate interruptions, requiring manual abort controller management. In contrast, effects come with interruption handling capabilities and automatically compose interruption, simplifying management locally on smaller computations without the need for high-level orchestration. - **Structured Concurrency:** Effects offer structured concurrency built-in, which is challenging to achieve with Promises. - **Error Reporting (Type Safety):** Promises don't inherently provide detailed error reporting at the type level, whereas effects do, offering type-safe insight into error cases. - **Runtime Behavior:** The Effect runtime aims to remain synchronous as long as possible, transitioning into asynchronous mode only when necessary due to computation requirements or main thread starvation. ## Type safety Let's start by comparing the types of `Promise` and `Effect`. The type parameter `A` represents the resolved value of the operation: ```ts showLineNumbers=false Promise ``` ```ts showLineNumbers=false Effect ``` Here's what sets `Effect` apart: - It allows you to track the types of errors statically through the type parameter `Error`. For more information about error management in `Effect`, see [Expected Errors](/docs/error-management/expected-errors/). - It allows you to track the types of required dependencies statically through the type parameter `Context`. For more information about context management in `Effect`, see [Managing Services](/docs/requirements-management/services/). ## Creating ### Success Let's compare creating a successful operation using `Promise` and `Effect`: ```ts twoslash const success = Promise.resolve(2) ``` ```ts twoslash import { Effect } from "effect" const success = Effect.succeed(2) ``` ### Failure Now, let's see how to handle failures with `Promise` and `Effect`: ```ts twoslash const failure = Promise.reject("Uh oh!") ``` ```ts twoslash import { Effect } from "effect" const failure = Effect.fail("Uh oh!") ``` ### Constructor Creating operations with custom logic: ```ts twoslash const task = new Promise((resolve, reject) => { setTimeout(() => { Math.random() > 0.5 ? resolve(2) : reject("Uh oh!") }, 300) }) ``` ```ts twoslash import { Effect } from "effect" const task = Effect.gen(function* () { yield* Effect.sleep("300 millis") return Math.random() > 0.5 ? 2 : yield* Effect.fail("Uh oh!") }) ``` ## Thenable Mapping the result of an operation: ### map ```ts twoslash const mapped = Promise.resolve("Hello").then((s) => s.length) ``` ```ts twoslash import { Effect } from "effect" const mapped = Effect.succeed("Hello").pipe( Effect.map((s) => s.length) // or Effect.andThen((s) => s.length) ) ``` ### flatMap Chaining multiple operations: ```ts twoslash const flatMapped = Promise.resolve("Hello").then((s) => Promise.resolve(s.length) ) ``` ```ts twoslash import { Effect } from "effect" const flatMapped = Effect.succeed("Hello").pipe( Effect.flatMap((s) => Effect.succeed(s.length)) // or Effect.andThen((s) => Effect.succeed(s.length)) ) ``` ## Comparing Effect.gen with async/await If you are familiar with `async`/`await`, you may notice that the flow of writing code is similar. Let's compare the two approaches: ```ts twoslash const increment = (x: number) => x + 1 const divide = (a: number, b: number): Promise => b === 0 ? Promise.reject(new Error("Cannot divide by zero")) : Promise.resolve(a / b) const task1 = Promise.resolve(10) const task2 = Promise.resolve(2) const program = async function () { const a = await task1 const b = await task2 const n1 = await divide(a, b) const n2 = increment(n1) return `Result is: ${n2}` } program().then(console.log) // Output: "Result is: 6" ``` ```ts twoslash import { Effect } from "effect" const increment = (x: number) => x + 1 const divide = (a: number, b: number): Effect.Effect => b === 0 ? Effect.fail(new Error("Cannot divide by zero")) : Effect.succeed(a / b) const task1 = Effect.promise(() => Promise.resolve(10)) const task2 = Effect.promise(() => Promise.resolve(2)) const program = Effect.gen(function* () { const a = yield* task1 const b = yield* task2 const n1 = yield* divide(a, b) const n2 = increment(n1) return `Result is: ${n2}` }) Effect.runPromise(program).then(console.log) // Output: "Result is: 6" ``` It's important to note that although the code appears similar, the two programs are not identical. The purpose of comparing them side by side is just to highlight the resemblance in how they are written. ## Concurrency ### Promise.all() ```ts twoslash const task1 = new Promise((resolve, reject) => { console.log("Executing task1...") setTimeout(() => { console.log("task1 done") resolve(1) }, 100) }) const task2 = new Promise((resolve, reject) => { console.log("Executing task2...") setTimeout(() => { console.log("task2 done") reject("Uh oh!") }, 200) }) const task3 = new Promise((resolve, reject) => { console.log("Executing task3...") setTimeout(() => { console.log("task3 done") resolve(3) }, 300) }) const program = Promise.all([task1, task2, task3]) program.then(console.log, console.error) /* Output: Executing task1... Executing task2... Executing task3... task1 done task2 done Uh oh! task3 done */ ``` ```ts twoslash import { Effect } from "effect" const task1 = Effect.gen(function* () { console.log("Executing task1...") yield* Effect.sleep("100 millis") console.log("task1 done") return 1 }) const task2 = Effect.gen(function* () { console.log("Executing task2...") yield* Effect.sleep("200 millis") console.log("task2 done") return yield* Effect.fail("Uh oh!") }) const task3 = Effect.gen(function* () { console.log("Executing task3...") yield* Effect.sleep("300 millis") console.log("task3 done") return 3 }) const program = Effect.all([task1, task2, task3], { concurrency: "unbounded" }) Effect.runPromise(program).then(console.log, console.error) /* Output: Executing task1... Executing task2... Executing task3... task1 done task2 done (FiberFailure) Error: Uh oh! */ ``` ### Promise.allSettled() ```ts const task1 = new Promise((resolve, reject) => { console.log("Executing task1...") setTimeout(() => { console.log("task1 done") resolve(1) }, 100) }) const task2 = new Promise((resolve, reject) => { console.log("Executing task2...") setTimeout(() => { console.log("task2 done") reject("Uh oh!") }, 200) }) const task3 = new Promise((resolve, reject) => { console.log("Executing task3...") setTimeout(() => { console.log("task3 done") resolve(3) }, 300) }) const program = Promise.allSettled([task1, task2, task3]) program.then(console.log, console.error) /* Output: Executing task1... Executing task2... Executing task3... task1 done task2 done task3 done [ { status: 'fulfilled', value: 1 }, { status: 'rejected', reason: 'Uh oh!' }, { status: 'fulfilled', value: 3 } ] */ ``` ```ts twoslash import { Effect } from "effect" const task1 = Effect.gen(function* () { console.log("Executing task1...") yield* Effect.sleep("100 millis") console.log("task1 done") return 1 }) const task2 = Effect.gen(function* () { console.log("Executing task2...") yield* Effect.sleep("200 millis") console.log("task2 done") return yield* Effect.fail("Uh oh!") }) const task3 = Effect.gen(function* () { console.log("Executing task3...") yield* Effect.sleep("300 millis") console.log("task3 done") return 3 }) const program = Effect.forEach( [task1, task2, task3], (task) => Effect.either(task), // or Effect.exit { concurrency: "unbounded" } ) Effect.runPromise(program).then(console.log, console.error) /* Output: Executing task1... Executing task2... Executing task3... task1 done task2 done task3 done [ { _id: "Either", _tag: "Right", right: 1 }, { _id: "Either", _tag: "Left", left: "Uh oh!" }, { _id: "Either", _tag: "Right", right: 3 } ] */ ``` ### Promise.any() ```ts const task1 = new Promise((resolve, reject) => { console.log("Executing task1...") setTimeout(() => { console.log("task1 done") reject("Something went wrong!") }, 100) }) const task2 = new Promise((resolve, reject) => { console.log("Executing task2...") setTimeout(() => { console.log("task2 done") resolve(2) }, 200) }) const task3 = new Promise((resolve, reject) => { console.log("Executing task3...") setTimeout(() => { console.log("task3 done") reject("Uh oh!") }, 300) }) const program = Promise.any([task1, task2, task3]) program.then(console.log, console.error) /* Output: Executing task1... Executing task2... Executing task3... task1 done task2 done 2 task3 done */ ``` ```ts twoslash import { Effect } from "effect" const task1 = Effect.gen(function* () { console.log("Executing task1...") yield* Effect.sleep("100 millis") console.log("task1 done") return yield* Effect.fail("Something went wrong!") }) const task2 = Effect.gen(function* () { console.log("Executing task2...") yield* Effect.sleep("200 millis") console.log("task2 done") return 2 }) const task3 = Effect.gen(function* () { console.log("Executing task3...") yield* Effect.sleep("300 millis") console.log("task3 done") return yield* Effect.fail("Uh oh!") }) const program = Effect.raceAll([task1, task2, task3]) Effect.runPromise(program).then(console.log, console.error) /* Output: Executing task1... Executing task2... Executing task3... task1 done task2 done 2 */ ``` ### Promise.race() ```ts twoslash const task1 = new Promise((resolve, reject) => { console.log("Executing task1...") setTimeout(() => { console.log("task1 done") reject("Something went wrong!") }, 100) }) const task2 = new Promise((resolve, reject) => { console.log("Executing task2...") setTimeout(() => { console.log("task2 done") reject("Uh oh!") }, 200) }) const task3 = new Promise((resolve, reject) => { console.log("Executing task3...") setTimeout(() => { console.log("task3 done") resolve(3) }, 300) }) const program = Promise.race([task1, task2, task3]) program.then(console.log, console.error) /* Output: Executing task1... Executing task2... Executing task3... task1 done Something went wrong! task2 done task3 done */ ``` ```ts twoslash import { Effect } from "effect" const task1 = Effect.gen(function* () { console.log("Executing task1...") yield* Effect.sleep("100 millis") console.log("task1 done") return yield* Effect.fail("Something went wrong!") }) const task2 = Effect.gen(function* () { console.log("Executing task2...") yield* Effect.sleep("200 millis") console.log("task2 done") return yield* Effect.fail("Uh oh!") }) const task3 = Effect.gen(function* () { console.log("Executing task3...") yield* Effect.sleep("300 millis") console.log("task3 done") return 3 }) const program = Effect.raceAll([task1, task2, task3].map(Effect.either)) // or Effect.exit Effect.runPromise(program).then(console.log, console.error) /* Output: Executing task1... Executing task2... Executing task3... task1 done { _id: "Either", _tag: "Left", left: "Something went wrong!" } */ ``` ## FAQ **Question**. What is the equivalent of starting a promise without immediately waiting for it in Effects? ```ts {10,16} twoslash const task = (delay: number, name: string) => new Promise((resolve) => setTimeout(() => { console.log(`${name} done`) return resolve(name) }, delay) ) export async function program() { const r0 = task(2_000, "long running task") const r1 = await task(200, "task 2") const r2 = await task(100, "task 3") return { r1, r2, r0: await r0 } } program().then(console.log) /* Output: task 2 done task 3 done long running task done { r1: 'task 2', r2: 'task 3', r0: 'long running promise' } */ ``` **Answer:** You can achieve this by utilizing `Effect.fork` and `Fiber.join`. ```ts {11,17} twoslash import { Effect, Fiber } from "effect" const task = (delay: number, name: string) => Effect.gen(function* () { yield* Effect.sleep(delay) console.log(`${name} done`) return name }) const program = Effect.gen(function* () { const r0 = yield* Effect.fork(task(2_000, "long running task")) const r1 = yield* task(200, "task 2") const r2 = yield* task(100, "task 3") return { r1, r2, r0: yield* Fiber.join(r0) } }) Effect.runPromise(program).then(console.log) /* Output: task 2 done task 3 done long running task done { r1: 'task 2', r2: 'task 3', r0: 'long running promise' } */ ```
# [Myths About Effect](https://effect.website/docs/additional-resources/myths/)
## Overview ## Effect heavily relies on generators and generators are slow! Effect's internals are not built on generators, we only use generators to provide an API which closely mimics async-await. Internally async-await uses the same mechanics as generators and they are equally performant. So if you don't have a problem with async-await you won't have a problem with Effect's generators. Where generators and iterables are unacceptably slow is in transforming collections of data, for that try to use plain arrays as much as possible. ## Effect will make your code 500x slower! Effect does perform 500x slower if you are comparing: ```ts twoslash const result = 1 + 1 ``` to ```ts twoslash import { Effect } from "effect" const result = Effect.runSync( Effect.zipWith(Effect.succeed(1), Effect.succeed(1), (a, b) => a + b) ) ``` The reason is one operation is optimized by the JIT compiler to be a direct CPU instruction and the other isn't. In reality you'd never use Effect in such cases, Effect is an app-level library to tame concurrency, error handling, and much more! You'd use Effect to coordinate your thunks of code, and you can build your thunks of code in the best perfoming manner as you see fit while still controlling execution through Effect. ## Effect has a huge performance overhead! Depends what you mean by performance, many times performance bottlenecks in JS are due to bad management of concurrency. Thanks to structured concurrency and observability it becomes much easier to spot and optimize those issues. There are apps in frontend running at 120fps that use Effect intensively, so most likely effect won't be your perf problem. In regards of memory, it doesn't use much more memory than a normal program would, there are a few more allocations compared to non Effect code but usually this is no longer the case when the non Effect code does the same thing as the Effect code. The advise would be start using it and monitor your code, optimise out of need not out of thought, optimizing too early is the root of all evils in software design. ## The bundle size is HUGE! Effect's minimum cost is about 25k of gzipped code, that chunk contains the Effect Runtime and already includes almost all the functions that you'll need in a normal app-code scenario. From that point on Effect is tree-shaking friendly so you'll only include what you use. Also when using Effect your own code becomes shorter and terser, so the overall cost is amortized with usage, we have apps where adopting Effect in the majority of the codebase led to reduction of the final bundle. ## Effect is impossible to learn, there are so many functions and modules! True, the full Effect ecosystem is quite large and some modules contain 1000s of functions, the reality is that you don't need to know them all to start being productive, you can safely start using Effect knowing just 10-20 functions and progressively discover the rest, just like you can start using TypeScript without knowing every single NPM package. A short list of commonly used functions to begin are: - [Effect.succeed](/docs/getting-started/creating-effects/#succeed) - [Effect.fail](/docs/getting-started/creating-effects/#fail) - [Effect.sync](/docs/getting-started/creating-effects/#sync) - [Effect.tryPromise](/docs/getting-started/creating-effects/#trypromise) - [Effect.gen](/docs/getting-started/using-generators/) - [Effect.runPromise](/docs/getting-started/running-effects/#runpromise) - [Effect.catchTag](/docs/error-management/expected-errors/#catchtag) - [Effect.catchAll](/docs/error-management/expected-errors/#catchall) - [Effect.acquireRelease](/docs/resource-management/scope/#acquirerelease) - [Effect.acquireUseRelease](/docs/resource-management/introduction/#acquireuserelease) - [Effect.provide](/docs/requirements-management/layers/#providing-a-layer-to-an-effect) - [Effect.provideService](/docs/requirements-management/services/#providing-a-service-implementation) - [Effect.andThen](/docs/getting-started/building-pipelines/#andthen) - [Effect.map](/docs/getting-started/building-pipelines/#map) - [Effect.tap](/docs/getting-started/building-pipelines/#tap) A short list of commonly used modules: - [Effect](https://effect-ts.github.io/effect/effect/Effect.ts.html) - [Context](/docs/requirements-management/services/#creating-a-service) - [Layer](/docs/requirements-management/layers/) - [Option](/docs/data-types/option/) - [Either](/docs/data-types/either/) - [Array](https://effect-ts.github.io/effect/effect/Array.ts.html) - [Match](/docs/code-style/pattern-matching/) ## Effect is the same as RxJS and shares its problems This is a sensitive topic, let's start by saying that RxJS is a great project and that it has helped millions of developers write reliable software and we all should be thankful to the developers who contributed to such an amazing project. Discussing the scope of the projects, RxJS aims to make working with Observables easy and wants to provide reactive extensions to JS, Effect instead wants to make writing production-grade TypeScript easy. While the intersection is non-empty the projects have fundamentally different objectives and strategies. Sometimes people refer to RxJS in bad light, and the reason isn't RxJS in itself but rather usage of RxJS in problem domains where RxJS wasn't thought to be used. Namely the idea that "everything is a stream" is theoretically true but it leads to fundamental limitations on developer experience, the primary issue being that streams are multi-shot (emit potentially multiple elements, or zero) and mutable delimited continuations (JS Generators) are known to be only good to represent single-shot effects (that emit a single value). In short it means that writing in imperative style (think of async/await) is practically impossible with stream primitives (practically because there would be the option of replaying the generator at every element and at every step, but this tends to be inefficient and the semantics of it are counter-intuitive, it would only work under the assumption that the full body is free of side-effects), forcing the developer to use declarative approaches such as pipe to represent all of their code. Effect has a Stream module (which is pull-based instead of push-based in order to be memory constant), but the basic Effect type is single-shot and it is optimised to act as a smart & lazy Promise that enables imperative programming, so when using Effect you're not forced to use a declarative style for everything and you can program using a model which is similar to async-await. The other big difference is that RxJS only cares about the happy-path with explicit types, it doesn't offer a way of typing errors and dependencies, Effect instead consider both errors and dependencies as explicitely typed and offers control-flow around those in a fully type-safe manner. In short if you need reactive programming around Observables, use RxJS, if you need to write production-grade TypeScript that includes by default native telemetry, error handling, dependency injection, and more use Effect. ## Effect should be a language or Use a different language Neither solve the issue of writing production grade software in TypeScript. TypeScript is an amazing language to write full stack code with deep roots in the JS ecosystem and wide compatibility of tools, it is an industrial language adopted by many large scale companies. The fact that something like Effect is possible within the language and the fact that the language supports things such as generators that allows for imperative programming with custom types such as Effect makes TypeScript a unique language. In fact even in functional languages such as Scala the interop with effect systems is less optimal than it is in TypeScript, to the point that effect system authors have expressed wish for their language to support as much as TypeScript supports.
# [Equivalence](https://effect.website/docs/behaviour/equivalence/)
## Overview The Equivalence module provides a way to define equivalence relations between values in TypeScript. An equivalence relation is a binary relation that is reflexive, symmetric, and transitive, establishing a formal notion of when two values should be considered equivalent. ## What is Equivalence? An `Equivalence` represents a function that compares two values of type `A` and determines if they are equivalent. This is more flexible and customizable than simple equality checks using `===`. Here's the structure of an `Equivalence`: ```ts showLineNumbers=false interface Equivalence { (self: A, that: A): boolean } ``` ## Using Built-in Equivalences The module provides several built-in equivalence relations for common data types: | Equivalence | Description | | ----------- | ------------------------------------------- | | `string` | Uses strict equality (`===`) for strings | | `number` | Uses strict equality (`===`) for numbers | | `boolean` | Uses strict equality (`===`) for booleans | | `symbol` | Uses strict equality (`===`) for symbols | | `bigint` | Uses strict equality (`===`) for bigints | | `Date` | Compares `Date` objects by their timestamps | **Example** (Using Built-in Equivalences) ```ts twoslash import { Equivalence } from "effect" console.log(Equivalence.string("apple", "apple")) // Output: true console.log(Equivalence.string("apple", "orange")) // Output: false console.log(Equivalence.Date(new Date(2023, 1, 1), new Date(2023, 1, 1))) // Output: true console.log(Equivalence.Date(new Date(2023, 1, 1), new Date(2023, 10, 1))) // Output: false ``` ## Deriving Equivalences For more complex data structures, you may need custom equivalences. The Equivalence module lets you derive new `Equivalence` instances from existing ones with the `Equivalence.mapInput` function. **Example** (Creating a Custom Equivalence for Objects) ```ts twoslash import { Equivalence } from "effect" interface User { readonly id: number readonly name: string } // Create an equivalence that compares User objects based only on the id const equivalence = Equivalence.mapInput( Equivalence.number, // Base equivalence for comparing numbers (user: User) => user.id // Function to extract the id from a User ) // Compare two User objects: they are equivalent if their ids are the same console.log(equivalence({ id: 1, name: "Alice" }, { id: 1, name: "Al" })) // Output: true ``` The `Equivalence.mapInput` function takes two arguments: 1. The existing `Equivalence` you want to use as a base (`Equivalence.number` in this case, for comparing numbers). 2. A function that extracts the value used for the equivalence check from your data structure (`(user: User) => user.id` in this case).
# [Order](https://effect.website/docs/behaviour/order/)
## Overview The Order module provides a way to compare values and determine their order. It defines an interface `Order` which represents a single function for comparing two values of type `A`. The function returns `-1`, `0`, or `1`, indicating whether the first value is less than, equal to, or greater than the second value. Here's the basic structure of an `Order`: ```ts showLineNumbers=false interface Order { (first: A, second: A): -1 | 0 | 1 } ``` ## Using the Built-in Orders The Order module comes with several built-in comparators for common data types: | Order | Description | | -------- | ---------------------------------- | | `string` | Used for comparing strings. | | `number` | Used for comparing numbers. | | `bigint` | Used for comparing big integers. | | `Date` | Used for comparing `Date` objects. | **Example** (Using Built-in Comparators) ```ts twoslash import { Order } from "effect" console.log(Order.string("apple", "banana")) // Output: -1, as "apple" < "banana" console.log(Order.number(1, 1)) // Output: 0, as 1 = 1 console.log(Order.bigint(2n, 1n)) // Output: 1, as 2n > 1n ``` ## Sorting Arrays You can sort arrays using these comparators. The `Array` module offers a `sort` function that sorts arrays without altering the original one. **Example** (Sorting Arrays with `Order`) ```ts twoslash import { Order, Array } from "effect" const strings = ["b", "a", "d", "c"] const result = Array.sort(strings, Order.string) console.log(strings) // Original array remains unchanged // Output: [ 'b', 'a', 'd', 'c' ] console.log(result) // Sorted array // Output: [ 'a', 'b', 'c', 'd' ] ``` You can also use an `Order` as a comparator with JavaScript's native `Array.sort` method, but keep in mind that this will modify the original array. **Example** (Using `Order` with Native `Array.prototype.sort`) ```ts twoslash import { Order } from "effect" const strings = ["b", "a", "d", "c"] strings.sort(Order.string) // Modifies the original array console.log(strings) // Output: [ 'a', 'b', 'c', 'd' ] ``` ## Deriving Orders For more complex data structures, you may need custom sorting rules. The Order module lets you derive new `Order` instances from existing ones with the `Order.mapInput` function. **Example** (Creating a Custom Order for Objects) Imagine you have a list of `Person` objects, and you want to sort them by their names in ascending order. To achieve this, you can create a custom `Order`. ```ts twoslash import { Order } from "effect" // Define the Person interface interface Person { readonly name: string readonly age: number } // Create a custom order to sort Person objects by name in ascending order // // ┌─── Order // ▼ const byName = Order.mapInput( Order.string, (person: Person) => person.name ) ``` The `Order.mapInput` function takes two arguments: 1. The existing `Order` you want to use as a base (`Order.string` in this case, for comparing strings). 2. A function that extracts the value you want to use for sorting from your data structure (`(person: Person) => person.name` in this case). Once you have defined your custom `Order`, you can apply it to sort an array of `Person` objects: **Example** (Sorting Objects Using a Custom Order) ```ts twoslash collapse={3-13} import { Order, Array } from "effect" // Define the Person interface interface Person { readonly name: string readonly age: number } // Create a custom order to sort Person objects by name in ascending order const byName = Order.mapInput( Order.string, (person: Person) => person.name ) const persons: ReadonlyArray = [ { name: "Charlie", age: 22 }, { name: "Alice", age: 25 }, { name: "Bob", age: 30 } ] // Sort persons array using the custom order const sortedPersons = Array.sort(persons, byName) console.log(sortedPersons) /* Output: [ { name: 'Alice', age: 25 }, { name: 'Bob', age: 30 }, { name: 'Charlie', age: 22 } ] */ ``` ## Combining Orders The Order module lets you combine multiple `Order` instances to create complex sorting rules. This is useful when sorting by multiple properties. **Example** (Sorting by Multiple Criteria) Imagine you have a list of people, each represented by an object with a `name` and an `age`. You want to sort this list first by name and then, for individuals with the same name, by age. ```ts twoslash import { Order, Array } from "effect" // Define the Person interface interface Person { readonly name: string readonly age: number } // Create an Order to sort people by their names in ascending order const byName = Order.mapInput( Order.string, (person: Person) => person.name ) // Create an Order to sort people by their ages in ascending order const byAge = Order.mapInput(Order.number, (person: Person) => person.age) // Combine orders to sort by name, then by age const byNameAge = Order.combine(byName, byAge) const result = Array.sort( [ { name: "Bob", age: 20 }, { name: "Alice", age: 18 }, { name: "Bob", age: 18 } ], byNameAge ) console.log(result) /* Output: [ { name: 'Alice', age: 18 }, // Sorted by name { name: 'Bob', age: 18 }, // Sorted by age within the same name { name: 'Bob', age: 20 } ] */ ``` ## Additional Useful Functions The Order module provides additional functions for common comparison operations, making it easier to work with ordered values. ### Reversing Order `Order.reverse` inverts the order of comparison. If you have an `Order` for ascending values, reversing it makes it descending. **Example** (Reversing an Order) ```ts twoslash import { Order } from "effect" const ascendingOrder = Order.number const descendingOrder = Order.reverse(ascendingOrder) console.log(ascendingOrder(1, 3)) // Output: -1 (1 < 3 in ascending order) console.log(descendingOrder(1, 3)) // Output: 1 (1 > 3 in descending order) ``` ### Comparing Values These functions allow you to perform simple comparisons between values: | API | Description | | ---------------------- | -------------------------------------------------------- | | `lessThan` | Checks if one value is strictly less than another. | | `greaterThan` | Checks if one value is strictly greater than another. | | `lessThanOrEqualTo` | Checks if one value is less than or equal to another. | | `greaterThanOrEqualTo` | Checks if one value is greater than or equal to another. | **Example** (Using Comparison Functions) ```ts twoslash import { Order } from "effect" console.log(Order.lessThan(Order.number)(1, 2)) // Output: true (1 < 2) console.log(Order.greaterThan(Order.number)(5, 3)) // Output: true (5 > 3) console.log(Order.lessThanOrEqualTo(Order.number)(2, 2)) // Output: true (2 <= 2) console.log(Order.greaterThanOrEqualTo(Order.number)(4, 4)) // Output: true (4 >= 4) ``` ### Finding Minimum and Maximum The `Order.min` and `Order.max` functions return the minimum or maximum value between two values, considering the order. **Example** (Finding Minimum and Maximum Numbers) ```ts twoslash import { Order } from "effect" console.log(Order.min(Order.number)(3, 1)) // Output: 1 (1 is the minimum) console.log(Order.max(Order.number)(5, 8)) // Output: 8 (8 is the maximum) ``` ### Clamping Values `Order.clamp` restricts a value within a given range. If the value is outside the range, it is adjusted to the nearest bound. **Example** (Clamping Numbers to a Range) ```ts twoslash import { Order } from "effect" // Define a function to clamp numbers between 20 and 30 const clampNumbers = Order.clamp(Order.number)({ minimum: 20, maximum: 30 }) // Value 26 is within the range [20, 30], so it remains unchanged console.log(clampNumbers(26)) // Output: 26 // Value 10 is below the minimum bound, so it is clamped to 20 console.log(clampNumbers(10)) // Output: 20 // Value 40 is above the maximum bound, so it is clamped to 30 console.log(clampNumbers(40)) // Output: 30 ``` ### Checking Value Range `Order.between` checks if a value falls within a specified inclusive range. **Example** (Checking if Numbers Fall Within a Range) ```ts twoslash import { Order } from "effect" // Create a function to check if numbers are between 20 and 30 const betweenNumbers = Order.between(Order.number)({ minimum: 20, maximum: 30 }) // Value 26 falls within the range [20, 30], so it returns true console.log(betweenNumbers(26)) // Output: true // Value 10 is below the minimum bound, so it returns false console.log(betweenNumbers(10)) // Output: false // Value 40 is above the maximum bound, so it returns false console.log(betweenNumbers(40)) // Output: false ```
# [Cache](https://effect.website/docs/caching/cache/)
## Overview In many applications, handling overlapping work is common. For example, in services that process incoming requests, it's important to avoid redundant work like handling the same request multiple times. The Cache module helps improve performance by preventing duplicate work. Key Features of Cache: | Feature | Description | | --------------------------------- | ---------------------------------------------------------------------------------------------------------------------- | | **Compositionality** | Allows overlapping work across different parts of the application while preserving compositional programming. | | **Unified Sync and Async Caches** | Integrates both synchronous and asynchronous caches through a unified lookup function that computes values either way. | | **Effect Integration** | Works natively with the Effect library, supporting concurrent lookups, failure handling, and interruption. | | **Cache Metrics** | Tracks key metrics like entries, hits, and misses, providing insights for performance optimization. | ## Creating a Cache A cache is defined by a lookup function that computes the value for a given key if it's not already cached: ```ts showLineNumbers=false type Lookup = ( key: Key ) => Effect ``` The lookup function takes a `Key` and returns an `Effect`, which describes how to compute the value (`Value`). This `Effect` may require an environment (`Requirements`), can fail with an `Error`, and succeed with a `Value`. Since it returns an `Effect`, it can handle both synchronous and asynchronous workflows. You create a cache by providing a lookup function along with a maximum size and a time-to-live (TTL) for cached values. ```ts showLineNumbers=false declare const make: (options: { readonly capacity: number readonly timeToLive: Duration.DurationInput readonly lookup: Lookup }) => Effect, never, Requirements> ``` Once a cache is created, the most idiomatic way to work with it is the `get` method. The `get` method returns the current value in the cache if it exists, or computes a new value, puts it in the cache, and returns it. If multiple concurrent processes request the same value, it will only be computed once. All other processes will receive the computed value as soon as it is available. This is managed using Effect's fiber-based concurrency model without blocking the underlying thread. **Example** (Concurrent Cache Lookups) In this example, we call `timeConsumingEffect` three times concurrently with the same key. The cache runs this effect only once, so concurrent lookups will wait until the value is available: ```ts twoslash import { Effect, Cache, Duration } from "effect" // Simulating an expensive lookup with a delay const expensiveLookup = (key: string) => Effect.sleep("2 seconds").pipe(Effect.as(key.length)) const program = Effect.gen(function* () { // Create a cache with a capacity of 100 and an infinite TTL const cache = yield* Cache.make({ capacity: 100, timeToLive: Duration.infinity, lookup: expensiveLookup }) // Perform concurrent lookups using the same key const result = yield* Effect.all( [cache.get("key1"), cache.get("key1"), cache.get("key1")], { concurrency: "unbounded" } ) console.log( "Result of parallel execution of three effects" + `with the same key: ${result}` ) // Fetch and display cache stats const hits = yield* cache.cacheStats.pipe( Effect.map((stats) => stats.hits) ) console.log(`Number of cache hits: ${hits}`) const misses = yield* cache.cacheStats.pipe( Effect.map((stats) => stats.misses) ) console.log(`Number of cache misses: ${misses}`) }) Effect.runPromise(program) /* Output: Result of parallel execution of three effects with the same key: 4,4,4 Number of cache hits: 2 Number of cache misses: 1 */ ``` ## Concurrent Access The cache is designed to be safe for concurrent access and efficient under concurrent conditions. If two concurrent processes request the same value and it is not in the cache, the value will be computed once and provided to both processes as soon as it is available. Concurrent processes will wait for the value without blocking the underlying thread. If the lookup function fails or is interrupted, the error will be propagated to all concurrent processes waiting for the value. Failures are cached to prevent repeated computation of the same failed value. If interrupted, the key will be removed from the cache, so subsequent calls will attempt to compute the value again. ## Capacity A cache is created with a specified capacity. When the cache reaches capacity, the least recently accessed values will be removed first. The cache size may slightly exceed the specified capacity between operations. ## Time To Live (TTL) A cache can also have a specified time to live (TTL). Values older than the TTL will not be returned. The age is calculated from when the value was loaded into the cache. ## Methods In addition to `get`, the cache provides several other methods: | Method | Description | | --------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `refresh` | Triggers a recomputation of the value for a key without removing the old value, allowing continued access. | | `size` | Returns the current size of the cache. The size is approximate under concurrent conditions. | | `contains` | Checks if a value associated with a specified key exists in the cache. Under concurrent access, the result is valid as of the check time but may change immediately after. | | `invalidate` | Evicts the value associated with a specific key. | | `invalidateAll` | Evicts all values from the cache. |
# [Caching Effects](https://effect.website/docs/caching/caching-effects/)
## Overview This section covers several functions from the library that help manage caching and memoization in your application. ## cachedFunction Memoizes a function with effects, caching results for the same inputs to avoid recomputation. **Example** (Memoizing a Random Number Generator) ```ts twoslash import { Effect, Random } from "effect" const program = Effect.gen(function* () { const randomNumber = (n: number) => Random.nextIntBetween(1, n) console.log("non-memoized version:") console.log(yield* randomNumber(10)) // Generates a new random number console.log(yield* randomNumber(10)) // Generates a different number console.log("memoized version:") const memoized = yield* Effect.cachedFunction(randomNumber) console.log(yield* memoized(10)) // Generates and caches the result console.log(yield* memoized(10)) // Reuses the cached result }) Effect.runFork(program) /* Example Output: non-memoized version: 2 8 memoized version: 5 5 */ ``` ## once Ensures an effect is executed only once, even if invoked multiple times. **Example** (Single Execution of an Effect) ```ts twoslash import { Effect, Console } from "effect" const program = Effect.gen(function* () { const task1 = Console.log("task1") // Repeats task1 three times yield* Effect.repeatN(task1, 2) // Ensures task2 is executed only once const task2 = yield* Effect.once(Console.log("task2")) // Attempts to repeat task2, but it will only execute once yield* Effect.repeatN(task2, 2) }) Effect.runFork(program) /* Output: task1 task1 task1 task2 */ ``` ## cached Returns an effect that computes a result lazily and caches it. Subsequent evaluations of this effect will return the cached result without re-executing the logic. **Example** (Lazy Caching of an Expensive Task) ```ts twoslash import { Effect, Console } from "effect" let i = 1 // Simulating an expensive task with a delay const expensiveTask = Effect.promise(() => { console.log("expensive task...") return new Promise((resolve) => { setTimeout(() => { resolve(`result ${i++}`) }, 100) }) }) const program = Effect.gen(function* () { // Without caching, the task is executed each time console.log("-- non-cached version:") yield* expensiveTask.pipe(Effect.andThen(Console.log)) yield* expensiveTask.pipe(Effect.andThen(Console.log)) // With caching, the result is reused after the first run console.log("-- cached version:") const cached = yield* Effect.cached(expensiveTask) yield* cached.pipe(Effect.andThen(Console.log)) yield* cached.pipe(Effect.andThen(Console.log)) }) Effect.runFork(program) /* Output: -- non-cached version: expensive task... result 1 expensive task... result 2 -- cached version: expensive task... result 3 result 3 */ ``` ## cachedWithTTL Returns an effect that caches its result for a specified duration, known as the `timeToLive`. When the cache expires after the duration, the effect will be recomputed upon next evaluation. **Example** (Caching with Time-to-Live) ```ts twoslash import { Effect, Console } from "effect" let i = 1 // Simulating an expensive task with a delay const expensiveTask = Effect.promise(() => { console.log("expensive task...") return new Promise((resolve) => { setTimeout(() => { resolve(`result ${i++}`) }, 100) }) }) const program = Effect.gen(function* () { // Caches the result for 150 milliseconds const cached = yield* Effect.cachedWithTTL(expensiveTask, "150 millis") // First evaluation triggers the task yield* cached.pipe(Effect.andThen(Console.log)) // Second evaluation returns the cached result yield* cached.pipe(Effect.andThen(Console.log)) // Wait for 100 milliseconds, ensuring the cache expires yield* Effect.sleep("100 millis") // Recomputes the task after cache expiration yield* cached.pipe(Effect.andThen(Console.log)) }) Effect.runFork(program) /* Output: expensive task... result 1 result 1 expensive task... result 2 */ ``` ## cachedInvalidateWithTTL Similar to `Effect.cachedWithTTL`, this function caches an effect's result for a specified duration. It also includes an additional effect for manually invalidating the cached value before it naturally expires. **Example** (Invalidating Cache Manually) ```ts twoslash import { Effect, Console } from "effect" let i = 1 // Simulating an expensive task with a delay const expensiveTask = Effect.promise(() => { console.log("expensive task...") return new Promise((resolve) => { setTimeout(() => { resolve(`result ${i++}`) }, 100) }) }) const program = Effect.gen(function* () { // Caches the result for 150 milliseconds const [cached, invalidate] = yield* Effect.cachedInvalidateWithTTL( expensiveTask, "150 millis" ) // First evaluation triggers the task yield* cached.pipe(Effect.andThen(Console.log)) // Second evaluation returns the cached result yield* cached.pipe(Effect.andThen(Console.log)) // Invalidate the cache before it naturally expires yield* invalidate // Third evaluation triggers the task again // since the cache was invalidated yield* cached.pipe(Effect.andThen(Console.log)) }) Effect.runFork(program) /* Output: expensive task... result 1 result 1 expensive task... result 2 */ ```
# [Branded Types](https://effect.website/docs/code-style/branded-types/)
## Overview In this guide, we will explore the concept of **branded types** in TypeScript and learn how to create and work with them using the Brand module. Branded types are TypeScript types with an added type tag that helps prevent accidental usage of a value in the wrong context. They allow us to create distinct types based on an existing underlying type, enabling type safety and better code organization. ## The Problem with TypeScript's Structural Typing TypeScript's type system is structurally typed, meaning that two types are considered compatible if their members are compatible. This can lead to situations where values of the same underlying type are used interchangeably, even when they represent different concepts or have different meanings. Consider the following types: ```ts twoslash type UserId = number type ProductId = number ``` Here, `UserId` and `ProductId` are structurally identical as they are both based on `number`. TypeScript will treat these as interchangeable, potentially causing bugs if they are mixed up in your application. **Example** (Unintended Type Compatibility) ```ts twoslash type UserId = number type ProductId = number const getUserById = (id: UserId) => { // Logic to retrieve user } const getProductById = (id: ProductId) => { // Logic to retrieve product } const id: UserId = 1 getProductById(id) // No type error, but incorrect usage ``` In the example above, passing a `UserId` to `getProductById` does not produce a type error, even though it's logically incorrect. This happens because both types are considered interchangeable. ## How Branded Types Help Branded types allow you to create distinct types from the same underlying type by adding a unique type tag, enforcing proper usage at compile-time. Branding is accomplished by adding a symbolic identifier that distinguishes one type from another at the type level. This method ensures that types remain distinct without altering their runtime characteristics. Let's start by introducing the `BrandTypeId` symbol: ```ts twoslash const BrandTypeId: unique symbol = Symbol.for("effect/Brand") type ProductId = number & { readonly [BrandTypeId]: { readonly ProductId: "ProductId" // unique identifier for ProductId } } ``` This approach assigns a unique identifier as a brand to the `number` type, effectively differentiating `ProductId` from other numerical types. The use of a symbol ensures that the branding field does not conflict with any existing properties of the `number` type. Attempting to use a `UserId` in place of a `ProductId` now results in an error: **Example** (Enforcing Type Safety with Branded Types) ```ts twoslash const BrandTypeId: unique symbol = Symbol.for("effect/Brand") type ProductId = number & { readonly [BrandTypeId]: { readonly ProductId: "ProductId" } } const getProductById = (id: ProductId) => { // Logic to retrieve product } type UserId = number const id: UserId = 1 // @ts-expect-error getProductById(id) /* Argument of type 'number' is not assignable to parameter of type 'ProductId'. Type 'number' is not assignable to type '{ readonly [BrandTypeId]: { readonly ProductId: "ProductId"; }; }'.ts(2345) */ ``` The error message clearly states that a `number` cannot be used in place of a `ProductId`. TypeScript won't let us pass an instance of `number` to the function accepting `ProductId` because it's missing the brand field. Let's add branding to `UserId` as well: **Example** (Branding UserId and ProductId) ```ts twoslash const BrandTypeId: unique symbol = Symbol.for("effect/Brand") type ProductId = number & { readonly [BrandTypeId]: { readonly ProductId: "ProductId" // unique identifier for ProductId } } const getProductById = (id: ProductId) => { // Logic to retrieve product } type UserId = number & { readonly [BrandTypeId]: { readonly UserId: "UserId" // unique identifier for UserId } } declare const id: UserId // @ts-expect-error getProductById(id) /* Argument of type 'UserId' is not assignable to parameter of type 'ProductId'. Type 'UserId' is not assignable to type '{ readonly [BrandTypeId]: { readonly ProductId: "ProductId"; }; }'. Types of property '[BrandTypeId]' are incompatible. Property 'ProductId' is missing in type '{ readonly UserId: "UserId"; }' but required in type '{ readonly ProductId: "ProductId"; }'.ts(2345) */ ``` The error indicates that while both types use branding, the unique values associated with the branding fields (`"ProductId"` and `"UserId"`) ensure they remain distinct and non-interchangeable. ## Generalizing Branded Types To enhance the versatility and reusability of branded types, they can be generalized using a standardized approach: ```ts twoslash const BrandTypeId: unique symbol = Symbol.for("effect/Brand") // Create a generic Brand interface using a unique identifier interface Brand { readonly [BrandTypeId]: { readonly [id in ID]: ID } } // Define a ProductId type branded with a unique identifier type ProductId = number & Brand<"ProductId"> // Define a UserId type branded similarly type UserId = number & Brand<"UserId"> ``` This design allows any type to be branded using a unique identifier, either a string or symbol. Here's how you can utilize the `Brand` interface, which is readily available from the Brand module, eliminating the need to craft your own implementation: **Example** (Using the Brand Interface from the Brand Module) ```ts import { Brand } from "effect" // Define a ProductId type branded with a unique identifier type ProductId = number & Brand.Brand<"ProductId"> // Define a UserId type branded similarly type UserId = number & Brand.Brand<"UserId"> ``` However, creating instances of these types directly leads to an error because the type system expects the brand structure: **Example** (Direct Assignment Error) ```ts twoslash const BrandTypeId: unique symbol = Symbol.for("effect/Brand") interface Brand { readonly [BrandTypeId]: { readonly [k in K]: K } } type ProductId = number & Brand<"ProductId"> // @ts-expect-error const id: ProductId = 1 /* Type 'number' is not assignable to type 'ProductId'. Type 'number' is not assignable to type 'Brand<"ProductId">'.ts(2322) */ ``` You cannot directly assign a `number` to `ProductId`. The Brand module provides utilities to correctly construct values of branded types. ## Constructing Branded Types The Brand module provides two main functions for creating branded types: `nominal` and `refined`. ### nominal The `Brand.nominal` function is designed for defining branded types that do not require runtime validations. It simply adds a type tag to the underlying type, allowing us to distinguish between values of the same type but with different meanings. Nominal branded types are useful when we only want to create distinct types for clarity and code organization purposes. **Example** (Defining Distinct Identifiers with Nominal Branding) ```ts twoslash import { Brand } from "effect" // Define UserId as a branded number type UserId = number & Brand.Brand<"UserId"> // Constructor for UserId const UserId = Brand.nominal() const getUserById = (id: UserId) => { // Logic to retrieve user } // Define ProductId as a branded number type ProductId = number & Brand.Brand<"ProductId"> // Constructor for ProductId const ProductId = Brand.nominal() const getProductById = (id: ProductId) => { // Logic to retrieve product } ``` Attempting to assign a non-`ProductId` value will result in a compile-time error: **Example** (Type Safety with Branded Identifiers) ```ts twoslash import { Brand } from "effect" type UserId = number & Brand.Brand<"UserId"> const UserId = Brand.nominal() const getUserById = (id: UserId) => { // Logic to retrieve user } type ProductId = number & Brand.Brand<"ProductId"> const ProductId = Brand.nominal() const getProductById = (id: ProductId) => { // Logic to retrieve product } // Correct usage getProductById(ProductId(1)) // Incorrect, will result in an error // @ts-expect-error getProductById(1) /* Argument of type 'number' is not assignable to parameter of type 'ProductId'. Type 'number' is not assignable to type 'Brand<"ProductId">'.ts(2345) */ // Also incorrect, will result in an error // @ts-expect-error getProductById(UserId(1)) /* Argument of type 'UserId' is not assignable to parameter of type 'ProductId'. Type 'UserId' is not assignable to type 'Brand<"ProductId">'. Types of property '[BrandTypeId]' are incompatible. Property 'ProductId' is missing in type '{ readonly UserId: "UserId"; }' but required in type '{ readonly ProductId: "ProductId"; }'.ts(2345) */ ``` ### refined The `Brand.refined` function enables the creation of branded types that include data validation. It requires a refinement predicate to check the validity of input data against specific criteria. When the input data does not meet the criteria, the function uses `Brand.error` to generate a `BrandErrors` data type. This provides detailed information about why the validation failed. **Example** (Creating a Branded Type with Validation) ```ts twoslash import { Brand } from "effect" // Define a branded type 'Int' to represent integer values type Int = number & Brand.Brand<"Int"> // Define the constructor using 'refined' to enforce integer values const Int = Brand.refined( // Validation to ensure the value is an integer (n) => Number.isInteger(n), // Provide an error if validation fails (n) => Brand.error(`Expected ${n} to be an integer`) ) ``` **Example** (Using the `Int` Constructor) ```ts twoslash import { Brand } from "effect" type Int = number & Brand.Brand<"Int"> const Int = Brand.refined( // Check if the value is an integer (n) => Number.isInteger(n), // Error message if the value is not an integer (n) => Brand.error(`Expected ${n} to be an integer`) ) // Create a valid Int value const x: Int = Int(3) console.log(x) // Output: 3 // Attempt to create an Int with an invalid value const y: Int = Int(3.14) // throws [ { message: 'Expected 3.14 to be an integer' } ] ``` Attempting to assign a non-`Int` value will result in a compile-time error: **Example** (Compile-Time Error for Incorrect Assignments) ```ts twoslash import { Brand } from "effect" type Int = number & Brand.Brand<"Int"> const Int = Brand.refined( (n) => Number.isInteger(n), (n) => Brand.error(`Expected ${n} to be an integer`) ) // Correct usage const good: Int = Int(3) // Incorrect, will result in an error // @ts-expect-error const bad1: Int = 3 // Also incorrect, will result in an error // @ts-expect-error const bad2: Int = 3.14 ``` ## Combining Branded Types In some cases, you might need to combine multiple branded types. The Brand module provides the `Brand.all` API for this purpose: **Example** (Combining Multiple Branded Types) ```ts twoslash import { Brand } from "effect" type Int = number & Brand.Brand<"Int"> const Int = Brand.refined( (n) => Number.isInteger(n), (n) => Brand.error(`Expected ${n} to be an integer`) ) type Positive = number & Brand.Brand<"Positive"> const Positive = Brand.refined( (n) => n > 0, (n) => Brand.error(`Expected ${n} to be positive`) ) // Combine the Int and Positive constructors // into a new branded constructor PositiveInt const PositiveInt = Brand.all(Int, Positive) // Extract the branded type from the PositiveInt constructor type PositiveInt = Brand.Brand.FromConstructor // Usage example // Valid positive integer const good: PositiveInt = PositiveInt(10) // throws [ { message: 'Expected -5 to be positive' } ] const bad1: PositiveInt = PositiveInt(-5) // throws [ { message: 'Expected 3.14 to be an integer' } ] const bad2: PositiveInt = PositiveInt(3.14) ```
# [Simplifying Excessive Nesting](https://effect.website/docs/code-style/do/)
## Overview import { Steps } from "@astrojs/starlight/components" Suppose you want to create a custom function `elapsed` that prints the elapsed time taken by an effect to execute. ## Using plain pipe Initially, you may come up with code that uses the standard `pipe` [method](/docs/getting-started/building-pipelines/#the-pipe-method), but this approach can lead to excessive nesting and result in verbose and hard-to-read code: **Example** (Measuring Elapsed Time with `pipe`) ```ts twoslash import { Effect, Console } from "effect" // Get the current timestamp const now = Effect.sync(() => new Date().getTime()) // Prints the elapsed time occurred to `self` to execute const elapsed = ( self: Effect.Effect ): Effect.Effect => now.pipe( Effect.andThen((startMillis) => self.pipe( Effect.andThen((result) => now.pipe( Effect.andThen((endMillis) => { // Calculate the elapsed time in milliseconds const elapsed = endMillis - startMillis // Log the elapsed time return Console.log(`Elapsed: ${elapsed}`).pipe( Effect.map(() => result) ) }) ) ) ) ) ) // Simulates a successful computation with a delay of 200 milliseconds const task = Effect.succeed("some task").pipe(Effect.delay("200 millis")) const program = elapsed(task) Effect.runPromise(program).then(console.log) /* Output: Elapsed: 204 some task */ ``` To address this issue and make the code more manageable, there is a solution: the "do simulation." ## Using the "do simulation" The "do simulation" in Effect allows you to write code in a more declarative style, similar to the "do notation" in other programming languages. It provides a way to define variables and perform operations on them using functions like `Effect.bind` and `Effect.let`. Here's how the do simulation works: 1. Start the do simulation using the `Effect.Do` value: ```ts showLineNumbers=false const program = Effect.Do.pipe(/* ... rest of the code */) ``` 2. Within the do simulation scope, you can use the `Effect.bind` function to define variables and bind them to `Effect` values: ```ts showLineNumbers=false Effect.bind("variableName", (scope) => effectValue) ``` - `variableName` is the name you choose for the variable you want to define. It must be unique within the scope. - `effectValue` is the `Effect` value that you want to bind to the variable. It can be the result of a function call or any other valid `Effect` value. 3. You can accumulate multiple `Effect.bind` statements to define multiple variables within the scope: ```ts showLineNumbers=false Effect.bind("variable1", () => effectValue1), Effect.bind("variable2", ({ variable1 }) => effectValue2), // ... additional bind statements ``` 4. Inside the do simulation scope, you can also use the `Effect.let` function to define variables and bind them to simple values: ```ts showLineNumbers=false Effect.let("variableName", (scope) => simpleValue) ``` - `variableName` is the name you give to the variable. Like before, it must be unique within the scope. - `simpleValue` is the value you want to assign to the variable. It can be a simple value like a `number`, `string`, or `boolean`. 5. Regular Effect functions like `Effect.andThen`, `Effect.flatMap`, `Effect.tap`, and `Effect.map` can still be used within the do simulation. These functions will receive the accumulated variables as arguments within the scope: ```ts showLineNumbers=false Effect.andThen(({ variable1, variable2 }) => { // Perform operations using variable1 and variable2 // Return an `Effect` value as the result }) ``` With the do simulation, you can rewrite the `elapsed` function like this: **Example** (Using Do Simulation to Measure Elapsed Time) ```ts twoslash import { Effect, Console } from "effect" // Get the current timestamp const now = Effect.sync(() => new Date().getTime()) const elapsed = ( self: Effect.Effect ): Effect.Effect => Effect.Do.pipe( Effect.bind("startMillis", () => now), Effect.bind("result", () => self), Effect.bind("endMillis", () => now), Effect.let( "elapsed", // Calculate the elapsed time in milliseconds ({ startMillis, endMillis }) => endMillis - startMillis ), // Log the elapsed time Effect.tap(({ elapsed }) => Console.log(`Elapsed: ${elapsed}`)), Effect.map(({ result }) => result) ) // Simulates a successful computation with a delay of 200 milliseconds const task = Effect.succeed("some task").pipe(Effect.delay("200 millis")) const program = elapsed(task) Effect.runPromise(program).then(console.log) /* Output: Elapsed: 204 some task */ ``` ## Using Effect.gen The most concise and convenient solution is to use [Effect.gen](/docs/getting-started/using-generators/), which allows you to work with [generators](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Generator) when dealing with effects. This approach leverages the native scope provided by the generator syntax, avoiding excessive nesting and leading to more concise code. **Example** (Using Effect.gen to Measure Elapsed Time) ```ts twoslash import { Effect } from "effect" // Get the current timestamp const now = Effect.sync(() => new Date().getTime()) // Prints the elapsed time occurred to `self` to execute const elapsed = ( self: Effect.Effect ): Effect.Effect => Effect.gen(function* () { const startMillis = yield* now const result = yield* self const endMillis = yield* now // Calculate the elapsed time in milliseconds const elapsed = endMillis - startMillis // Log the elapsed time console.log(`Elapsed: ${elapsed}`) return result }) // Simulates a successful computation with a delay of 200 milliseconds const task = Effect.succeed("some task").pipe(Effect.delay("200 millis")) const program = elapsed(task) Effect.runPromise(program).then(console.log) /* Output: Elapsed: 204 some task */ ``` Within the generator, we use `yield*` to invoke effects and bind their results to variables. This eliminates the nesting and provides a more readable and sequential code structure. The generator style in Effect uses a more linear and sequential flow of execution, resembling traditional imperative programming languages. This makes the code easier to read and understand, especially for developers who are more familiar with imperative programming paradigms.
# [Dual APIs](https://effect.website/docs/code-style/dual/)
## Overview import { Aside } from "@astrojs/starlight/components" When you're working with APIs in the Effect ecosystem, you may come across two different ways to use the same API. These two ways are called the "data-last" and "data-first" variants. When an API supports both variants, we call them "dual" APIs. Here's an illustration of these two variants using `Effect.map`. ## Effect.map as a dual API The `Effect.map` function is defined with two TypeScript overloads. The terms "data-last" and "data-first" refer to the position of the `self` argument (also known as the "data") in the signatures of the two overloads: ```ts showLineNumbers=false declare const map: { // ┌─── data-last // ▼ (f: (a: A) => B): (self: Effect) => Effect // ┌─── data-first // ▼ (self: Effect, f: (a: A) => B): Effect } ``` ### data-last In the first overload, the `self` argument comes **last**: ```ts showLineNumbers=false "self" declare const map: ( f: (a: A) => B ) => (self: Effect) => Effect ``` This version is commonly used with the `pipe` function. You start by passing the `Effect` as the initial argument to `pipe` and then chain transformations like `Effect.map`: **Example** (Using data-last with `pipe`) ```ts showLineNumbers=false const mappedEffect = pipe(effect, Effect.map(func)) ``` This style is helpful when chaining multiple transformations, making the code easier to follow in a pipeline format: ```ts showLineNumbers=false pipe(effect, Effect.map(func1), Effect.map(func2), ...) ``` ### data-first In the second overload, the `self` argument comes **first**: ```ts showLineNumbers=false "self" declare const map: ( self: Effect, f: (a: A) => B ) => Effect ``` This form doesn't require `pipe`. Instead, you provide the `Effect` directly as the first argument: **Example** (Using data-first without `pipe`) ```ts showLineNumbers=false const mappedEffect = Effect.map(effect, func) ``` This version works well when you only need to perform a single operation on the `Effect`.
# [Guidelines](https://effect.website/docs/code-style/guidelines/)
## Overview import { Aside } from "@astrojs/starlight/components" ## Using runMain In Effect, `runMain` is the primary entry point for executing an Effect application on Node.js. **Example** (Running an Effect Application with Graceful Teardown) ```ts import { Effect, Console, Schedule, pipe } from "effect" import { NodeRuntime } from "@effect/platform-node" const program = pipe( Effect.addFinalizer(() => Console.log("Application is about to exit!")), Effect.andThen(Console.log("Application started!")), Effect.andThen( Effect.repeat(Console.log("still alive..."), { schedule: Schedule.spaced("1 second") }) ), Effect.scoped ) // No graceful teardown on CTRL+C // Effect.runPromise(program) // Use NodeRuntime.runMain for graceful teardown on CTRL+C NodeRuntime.runMain(program) /* Output: Application started! still alive... still alive... still alive... still alive... ^C <-- CTRL+C Application is about to exit! */ ``` The `runMain` function handles finding and interrupting all fibers. Internally, it observes the fiber and listens for `sigint` signals, ensuring a graceful shutdown of the application when interrupted (e.g., using CTRL+C). ### Versions for Different Platforms Effect provides versions of `runMain` tailored for different platforms: | Platform | Runtime Version | Import Path | | -------- | ------------------------ | -------------------------- | | Node.js | `NodeRuntime.runMain` | `@effect/platform-node` | | Bun | `BunRuntime.runMain` | `@effect/platform-bun` | | Browser | `BrowserRuntime.runMain` | `@effect/platform-browser` | ## Avoid Tacit Usage Avoid using tacit (point-free) function calls, such as `Effect.map(fn)`, or using `flow` from the `effect/Function` module. In Effect, it's generally safer to write functions explicitly: ```ts showLineNumbers=false Effect.map((x) => fn(x)) ``` rather than in a point-free style: ```ts showLineNumbers=false Effect.map(fn) ``` While tacit functions may be appealing for their brevity, they can introduce a number of problems: - Using tacit functions, particularly when dealing with optional parameters, can be unsafe. For example, if a function has overloads, writing it in a tacit style may erase all generics, resulting in bugs. Check out this X thread for more details: [link to thread](https://twitter.com/MichaelArnaldi/status/1670715270845935616). - Tacit usage can also compromise TypeScript's ability to infer types, potentially causing unexpected errors. This isn't just a matter of style but a way to avoid subtle mistakes that can arise from type inference issues. - Additionally, stack traces might not be as clear when tacit usage is employed. Avoiding tacit usage is a simple precaution that makes your code more reliable.
# [Pattern Matching](https://effect.website/docs/code-style/pattern-matching/)
## Overview import { Aside } from "@astrojs/starlight/components" Pattern matching is a method that allows developers to handle intricate conditions within a single, concise expression. It simplifies code, making it more concise and easier to understand. Additionally, it includes a process called exhaustiveness checking, which helps to ensure that no possible case has been overlooked. Originating from functional programming languages, pattern matching stands as a powerful technique for code branching. It often offers a more potent and less verbose solution compared to imperative alternatives such as if/else or switch statements, particularly when dealing with complex conditions. Although not yet a native feature in JavaScript, there's an ongoing [tc39 proposal](https://github.com/tc39/proposal-pattern-matching) in its early stages to introduce pattern matching to JavaScript. However, this proposal is at stage 1 and might take several years to be implemented. Nonetheless, developers can implement pattern matching in their codebase. The `effect/Match` module provides a reliable, type-safe pattern matching implementation that is available for immediate use. **Example** (Handling Different Data Types with Pattern Matching) ```ts twoslash import { Match } from "effect" // Simulated dynamic input that can be a string or a number const input: string | number = "some input" // ┌─── string // ▼ const result = Match.value(input).pipe( // Match if the value is a number Match.when(Match.number, (n) => `number: ${n}`), // Match if the value is a string Match.when(Match.string, (s) => `string: ${s}`), // Ensure all possible cases are covered Match.exhaustive ) console.log(result) // Output: "string: some input" ``` ## How Pattern Matching Works Pattern matching follows a structured process: 1. **Creating a matcher**. Define a `Matcher` that operates on either a specific [type](#matching-by-type) or [value](#matching-by-value). 2. **Defining patterns**. Use combinators such as `Match.when`, `Match.not`, and `Match.tag` to specify matching conditions. 3. **Completing the match**. Apply a finalizer such as `Match.exhaustive`, `Match.orElse`, or `Match.option` to determine how unmatched cases should be handled. ## Creating a matcher You can create a `Matcher` using either: - `Match.type()`: Matches against a specific type. - `Match.value(value)`: Matches against a specific value. ### Matching by Type The `Match.type` constructor defines a `Matcher` that operates on a specific type. Once created, you can use patterns like `Match.when` to define conditions for handling different cases. **Example** (Matching Numbers and Strings) ```ts twoslash import { Match } from "effect" // Create a matcher for values that are either strings or numbers // // ┌─── (u: string | number) => string // ▼ const match = Match.type().pipe( // Match when the value is a number Match.when(Match.number, (n) => `number: ${n}`), // Match when the value is a string Match.when(Match.string, (s) => `string: ${s}`), // Ensure all possible cases are handled Match.exhaustive ) console.log(match(0)) // Output: "number: 0" console.log(match("hello")) // Output: "string: hello" ``` ### Matching by Value Instead of creating a matcher for a type, you can define one directly from a specific value using `Match.value`. **Example** (Matching an Object by Property) ```ts twoslash import { Match } from "effect" const input = { name: "John", age: 30 } // Create a matcher for the specific object const result = Match.value(input).pipe( // Match when the 'name' property is "John" Match.when( { name: "John" }, (user) => `${user.name} is ${user.age} years old` ), // Provide a fallback if no match is found Match.orElse(() => "Oh, not John") ) console.log(result) // Output: "John is 30 years old" ``` ### Enforcing a Return Type You can use `Match.withReturnType()` to ensure that all branches return a specific type. **Example** (Validating Return Type Consistency) This example enforces that every matching branch returns a `string`. ```ts twoslash import { Match } from "effect" const match = Match.type<{ a: number } | { b: string }>().pipe( // Ensure all branches return a string Match.withReturnType(), // ❌ Type error: 'number' is not assignable to type 'string' // @ts-expect-error Match.when({ a: Match.number }, (_) => _.a), // ✅ Correct: returns a string Match.when({ b: Match.string }, (_) => _.b), Match.exhaustive ) ``` ## Defining patterns ### when The `Match.when` function allows you to define conditions for matching values. It supports both direct value comparisons and predicate functions. **Example** (Matching with Values and Predicates) ```ts twoslash import { Match } from "effect" // Create a matcher for objects with an "age" property const match = Match.type<{ age: number }>().pipe( // Match when age is greater than 18 Match.when({ age: (age) => age > 18 }, (user) => `Age: ${user.age}`), // Match when age is exactly 18 Match.when({ age: 18 }, () => "You can vote"), // Fallback case for all other ages Match.orElse((user) => `${user.age} is too young`) ) console.log(match({ age: 20 })) // Output: "Age: 20" console.log(match({ age: 18 })) // Output: "You can vote" console.log(match({ age: 4 })) // Output: "4 is too young" ``` ### not The `Match.not` function allows you to exclude specific values while matching all others. **Example** (Ignoring a Specific Value) ```ts twoslash import { Match } from "effect" // Create a matcher for string or number values const match = Match.type().pipe( // Match any value except "hi", returning "ok" Match.not("hi", () => "ok"), // Fallback case for when the value is "hi" Match.orElse(() => "fallback") ) console.log(match("hello")) // Output: "ok" console.log(match("hi")) // Output: "fallback" ``` ### tag The `Match.tag` function allows pattern matching based on the `_tag` field in a [Discriminated Union](https://www.typescriptlang.org/docs/handbook/typescript-in-5-minutes-func.html#discriminated-unions). You can specify multiple tags to match within a single pattern. **Example** (Matching a Discriminated Union by Tag) ```ts twoslash import { Match } from "effect" type Event = | { readonly _tag: "fetch" } | { readonly _tag: "success"; readonly data: string } | { readonly _tag: "error"; readonly error: Error } | { readonly _tag: "cancel" } // Create a Matcher for Either const match = Match.type().pipe( // Match either "fetch" or "success" Match.tag("fetch", "success", () => `Ok!`), // Match "error" and extract the error message Match.tag("error", (event) => `Error: ${event.error.message}`), // Match "cancel" Match.tag("cancel", () => "Cancelled"), Match.exhaustive ) console.log(match({ _tag: "success", data: "Hello" })) // Output: "Ok!" console.log(match({ _tag: "error", error: new Error("Oops!") })) // Output: "Error: Oops!" ``` ### Built-in Predicates The `Match` module provides built-in predicates for common types, such as `Match.number`, `Match.string`, and `Match.boolean`. These predicates simplify the process of matching against primitive types. **Example** (Using Built-in Predicates for Property Keys) ```ts twoslash import { Match } from "effect" const matchPropertyKey = Match.type().pipe( // Match when the value is a number Match.when(Match.number, (n) => `Key is a number: ${n}`), // Match when the value is a string Match.when(Match.string, (s) => `Key is a string: ${s}`), // Match when the value is a symbol Match.when(Match.symbol, (s) => `Key is a symbol: ${String(s)}`), // Ensure all possible cases are handled Match.exhaustive ) console.log(matchPropertyKey(42)) // Output: "Key is a number: 42" console.log(matchPropertyKey("username")) // Output: "Key is a string: username" console.log(matchPropertyKey(Symbol("id"))) // Output: "Key is a symbol: Symbol(id)" ``` | Predicate | Description | | ------------------------- | ----------------------------------------------------------------------------- | | `Match.string` | Matches values of type `string`. | | `Match.nonEmptyString` | Matches non-empty strings. | | `Match.number` | Matches values of type `number`. | | `Match.boolean` | Matches values of type `boolean`. | | `Match.bigint` | Matches values of type `bigint`. | | `Match.symbol` | Matches values of type `symbol`. | | `Match.date` | Matches values that are instances of `Date`. | | `Match.record` | Matches objects where keys are `string` or `symbol` and values are `unknown`. | | `Match.null` | Matches the value `null`. | | `Match.undefined` | Matches the value `undefined`. | | `Match.defined` | Matches any defined (non-null and non-undefined) value. | | `Match.any` | Matches any value without restrictions. | | `Match.is(...values)` | Matches a specific set of literal values (e.g., `Match.is("a", 42, true)`). | | `Match.instanceOf(Class)` | Matches instances of a given class. | ## Completing the match ### exhaustive The `Match.exhaustive` method finalizes the pattern matching process by ensuring that all possible cases are accounted for. If any case is missing, TypeScript will produce a type error. This is particularly useful when working with unions, as it helps prevent unintended gaps in pattern matching. **Example** (Ensuring All Cases Are Covered) ```ts twoslash import { Match } from "effect" // Create a matcher for string or number values const match = Match.type().pipe( // Match when the value is a number Match.when(Match.number, (n) => `number: ${n}`), // Mark the match as exhaustive, ensuring all cases are handled // TypeScript will throw an error if any case is missing // @ts-expect-error Type 'string' is not assignable to type 'never' Match.exhaustive ) ``` ### orElse The `Match.orElse` method defines a fallback value to return when no other patterns match. This ensures that the matcher always produces a valid result. **Example** (Providing a Default Value When No Patterns Match) ```ts twoslash import { Match } from "effect" // Create a matcher for string or number values const match = Match.type().pipe( // Match when the value is "a" Match.when("a", () => "ok"), // Fallback when no patterns match Match.orElse(() => "fallback") ) console.log(match("a")) // Output: "ok" console.log(match("b")) // Output: "fallback" ``` ### option `Match.option` wraps the match result in an [Option](/docs/data-types/option/). If a match is found, it returns `Some(value)`, otherwise, it returns `None`. **Example** (Extracting a User Role with Option) ```ts twoslash import { Match } from "effect" type User = { readonly role: "admin" | "editor" | "viewer" } // Create a matcher to extract user roles const getRole = Match.type().pipe( Match.when({ role: "admin" }, () => "Has full access"), Match.when({ role: "editor" }, () => "Can edit content"), Match.option // Wrap the result in an Option ) console.log(getRole({ role: "admin" })) // Output: { _id: 'Option', _tag: 'Some', value: 'Has full access' } console.log(getRole({ role: "viewer" })) // Output: { _id: 'Option', _tag: 'None' } ``` ### either The `Match.either` method wraps the result in an [Either](/docs/data-types/either/), providing a structured way to distinguish between matched and unmatched cases. If a match is found, it returns `Right(value)`, otherwise, it returns `Left(no match)`. **Example** (Extracting a User Role with Either) ```ts twoslash import { Match } from "effect" type User = { readonly role: "admin" | "editor" | "viewer" } // Create a matcher to extract user roles const getRole = Match.type().pipe( Match.when({ role: "admin" }, () => "Has full access"), Match.when({ role: "editor" }, () => "Can edit content"), Match.either // Wrap the result in an Either ) console.log(getRole({ role: "admin" })) // Output: { _id: 'Either', _tag: 'Right', right: 'Has full access' } console.log(getRole({ role: "viewer" })) // Output: { _id: 'Either', _tag: 'Left', left: { role: 'viewer' } } ```
# [Basic Concurrency](https://effect.website/docs/concurrency/basic-concurrency/)
## Overview import { Aside } from "@astrojs/starlight/components" ## Concurrency Options Effect provides options to manage how effects are executed, particularly focusing on controlling how many effects run concurrently. ```ts showLineNumbers=false type Options = { readonly concurrency?: Concurrency } ``` The `concurrency` option is used to determine the level of concurrency, with the following values: ```ts showLineNumbers=false type Concurrency = number | "unbounded" | "inherit" ``` Let's explore each configuration in detail. ### Sequential Execution (Default) By default, if you don't specify any concurrency option, effects will run sequentially, one after the other. This means each effect starts only after the previous one completes. **Example** (Sequential Execution) ```ts twoslash import { Effect, Duration } from "effect" // Helper function to simulate a task with a delay const makeTask = (n: number, delay: Duration.DurationInput) => Effect.promise( () => new Promise((resolve) => { console.log(`start task${n}`) // Logs when the task starts setTimeout(() => { console.log(`task${n} done`) // Logs when the task finishes resolve() }, Duration.toMillis(delay)) }) ) const task1 = makeTask(1, "200 millis") const task2 = makeTask(2, "100 millis") const sequential = Effect.all([task1, task2]) Effect.runPromise(sequential) /* Output: start task1 task1 done start task2 <-- task2 starts only after task1 completes task2 done */ ``` ### Numbered Concurrency You can control how many effects run concurrently by setting a `number` for `concurrency`. For example, `concurrency: 2` allows up to two effects to run at the same time. **Example** (Limiting to 2 Concurrent Tasks) ```ts twoslash import { Effect, Duration } from "effect" // Helper function to simulate a task with a delay const makeTask = (n: number, delay: Duration.DurationInput) => Effect.promise( () => new Promise((resolve) => { console.log(`start task${n}`) // Logs when the task starts setTimeout(() => { console.log(`task${n} done`) // Logs when the task finishes resolve() }, Duration.toMillis(delay)) }) ) const task1 = makeTask(1, "200 millis") const task2 = makeTask(2, "100 millis") const task3 = makeTask(3, "210 millis") const task4 = makeTask(4, "110 millis") const task5 = makeTask(5, "150 millis") const numbered = Effect.all([task1, task2, task3, task4, task5], { concurrency: 2 }) Effect.runPromise(numbered) /* Output: start task1 start task2 <-- active tasks: task1, task2 task2 done start task3 <-- active tasks: task1, task3 task1 done start task4 <-- active tasks: task3, task4 task4 done start task5 <-- active tasks: task3, task5 task3 done task5 done */ ``` ### Unbounded Concurrency When `concurrency: "unbounded"` is used, there's no limit to the number of effects running concurrently. **Example** (Unbounded Concurrency) ```ts twoslash import { Effect, Duration } from "effect" // Helper function to simulate a task with a delay const makeTask = (n: number, delay: Duration.DurationInput) => Effect.promise( () => new Promise((resolve) => { console.log(`start task${n}`) // Logs when the task starts setTimeout(() => { console.log(`task${n} done`) // Logs when the task finishes resolve() }, Duration.toMillis(delay)) }) ) const task1 = makeTask(1, "200 millis") const task2 = makeTask(2, "100 millis") const task3 = makeTask(3, "210 millis") const task4 = makeTask(4, "110 millis") const task5 = makeTask(5, "150 millis") const unbounded = Effect.all([task1, task2, task3, task4, task5], { concurrency: "unbounded" }) Effect.runPromise(unbounded) /* Output: start task1 start task2 start task3 start task4 start task5 task2 done task4 done task5 done task1 done task3 done */ ``` ### Inherit Concurrency When using `concurrency: "inherit"`, the concurrency level is inherited from the surrounding context. This context can be set using `Effect.withConcurrency(number | "unbounded")`. If no context is provided, the default is `"unbounded"`. **Example** (Inheriting Concurrency from Context) ```ts twoslash import { Effect, Duration } from "effect" // Helper function to simulate a task with a delay const makeTask = (n: number, delay: Duration.DurationInput) => Effect.promise( () => new Promise((resolve) => { console.log(`start task${n}`) // Logs when the task starts setTimeout(() => { console.log(`task${n} done`) // Logs when the task finishes resolve() }, Duration.toMillis(delay)) }) ) const task1 = makeTask(1, "200 millis") const task2 = makeTask(2, "100 millis") const task3 = makeTask(3, "210 millis") const task4 = makeTask(4, "110 millis") const task5 = makeTask(5, "150 millis") // Running all tasks with concurrency: "inherit", // which defaults to "unbounded" const inherit = Effect.all([task1, task2, task3, task4, task5], { concurrency: "inherit" }) Effect.runPromise(inherit) /* Output: start task1 start task2 start task3 start task4 start task5 task2 done task4 done task5 done task1 done task3 done */ ``` If you use `Effect.withConcurrency`, the concurrency configuration will adjust to the specified option. **Example** (Setting Concurrency Option) ```ts twoslash import { Effect, Duration } from "effect" // Helper function to simulate a task with a delay const makeTask = (n: number, delay: Duration.DurationInput) => Effect.promise( () => new Promise((resolve) => { console.log(`start task${n}`) // Logs when the task starts setTimeout(() => { console.log(`task${n} done`) // Logs when the task finishes resolve() }, Duration.toMillis(delay)) }) ) const task1 = makeTask(1, "200 millis") const task2 = makeTask(2, "100 millis") const task3 = makeTask(3, "210 millis") const task4 = makeTask(4, "110 millis") const task5 = makeTask(5, "150 millis") // Running tasks with concurrency: "inherit", // which will inherit the surrounding context const inherit = Effect.all([task1, task2, task3, task4, task5], { concurrency: "inherit" }) // Setting a concurrency limit of 2 const withConcurrency = inherit.pipe(Effect.withConcurrency(2)) Effect.runPromise(withConcurrency) /* Output: start task1 start task2 <-- active tasks: task1, task2 task2 done start task3 <-- active tasks: task1, task3 task1 done start task4 <-- active tasks: task3, task4 task4 done start task5 <-- active tasks: task3, task5 task3 done task5 done */ ``` ## Interruptions All effects in Effect are executed by [fibers](/docs/concurrency/fibers/). If you didn't create the fiber yourself, it was created by an operation you're using (if it's concurrent) or by the Effect [runtime](/docs/runtime/) system. A fiber is created any time an effect is run. When running effects concurrently, a fiber is created for each concurrent effect. To summarize: - An `Effect` is a higher-level concept that describes an effectful computation. It is lazy and immutable, meaning it represents a computation that may produce a value or fail but does not immediately execute. - A fiber, on the other hand, represents the running execution of an `Effect`. It can be interrupted or awaited to retrieve its result. Think of it as a way to control and interact with the ongoing computation. Fibers can be interrupted in various ways. Let's explore some of these scenarios and see examples of how to interrupt fibers in Effect. ### interrupt A fiber can be interrupted using the `Effect.interrupt` effect on that particular fiber. This effect models the explicit interruption of the fiber in which it runs. When executed, it causes the fiber to stop its operation immediately, capturing the interruption details such as the fiber's ID and its start time. The resulting interruption can be observed in the [Exit](/docs/data-types/exit/) type if the effect is run with functions like [runPromiseExit](/docs/getting-started/running-effects/#runpromiseexit). **Example** (Without Interruption) In this case, the program runs without any interruption, logging the start and completion of the task. ```ts twoslash import { Effect } from "effect" const program = Effect.gen(function* () { console.log("start") yield* Effect.sleep("2 seconds") console.log("done") return "some result" }) Effect.runPromiseExit(program).then(console.log) /* Output: start done { _id: 'Exit', _tag: 'Success', value: 'some result' } */ ``` **Example** (With Interruption) Here, the fiber is interrupted after the log `"start"` but before the `"done"` log. The `Effect.interrupt` stops the fiber, and it never reaches the final log. ```ts {6} twoslash import { Effect } from "effect" const program = Effect.gen(function* () { console.log("start") yield* Effect.sleep("2 seconds") yield* Effect.interrupt console.log("done") return "some result" }) Effect.runPromiseExit(program).then(console.log) /* Output: start { _id: 'Exit', _tag: 'Failure', cause: { _id: 'Cause', _tag: 'Interrupt', fiberId: { _id: 'FiberId', _tag: 'Runtime', id: 0, startTimeMillis: ... } } } */ ``` ### onInterrupt Registers a cleanup effect to run when an effect is interrupted. This function allows you to specify an effect to run when the fiber is interrupted. This effect will be executed when the fiber is interrupted, allowing you to perform cleanup or other actions. **Example** (Running a Cleanup Action on Interruption) In this example, we set up a handler that logs "Cleanup completed" whenever the fiber is interrupted. We then show three cases: a successful effect, a failing effect, and an interrupted effect, demonstrating how the handler is triggered depending on how the effect ends. ```ts twoslash import { Console, Effect } from "effect" // This handler is executed when the fiber is interrupted const handler = Effect.onInterrupt((_fibers) => Console.log("Cleanup completed") ) const success = Console.log("Task completed").pipe( Effect.as("some result"), handler ) Effect.runFork(success) /* Output: Task completed */ const failure = Console.log("Task failed").pipe( Effect.andThen(Effect.fail("some error")), handler ) Effect.runFork(failure) /* Output: Task failed */ const interruption = Console.log("Task interrupted").pipe( Effect.andThen(Effect.interrupt), handler ) Effect.runFork(interruption) /* Output: Task interrupted Cleanup completed */ ``` ### Interruption of Concurrent Effects When running multiple effects concurrently, such as with `Effect.forEach`, if one of the effects is interrupted, it causes all concurrent effects to be interrupted as well. The resulting [cause](/docs/data-types/cause/) includes information about which fibers were interrupted. **Example** (Interrupting Concurrent Effects) ```ts twoslash import { Effect, Console } from "effect" const program = Effect.forEach( [1, 2, 3], (n) => Effect.gen(function* () { console.log(`start #${n}`) yield* Effect.sleep(`${n} seconds`) if (n > 1) { yield* Effect.interrupt } console.log(`done #${n}`) }).pipe(Effect.onInterrupt(() => Console.log(`interrupted #${n}`))), { concurrency: "unbounded" } ) Effect.runPromiseExit(program).then((exit) => console.log(JSON.stringify(exit, null, 2)) ) /* Output: start #1 start #2 start #3 done #1 interrupted #2 interrupted #3 { "_id": "Exit", "_tag": "Failure", "cause": { "_id": "Cause", "_tag": "Parallel", "left": { "_id": "Cause", "_tag": "Interrupt", "fiberId": { "_id": "FiberId", "_tag": "Runtime", "id": 3, "startTimeMillis": ... } }, "right": { "_id": "Cause", "_tag": "Sequential", "left": { "_id": "Cause", "_tag": "Empty" }, "right": { "_id": "Cause", "_tag": "Interrupt", "fiberId": { "_id": "FiberId", "_tag": "Runtime", "id": 0, "startTimeMillis": ... } } } } } */ ``` ## Racing ### race This function takes two effects and runs them concurrently. The first effect that successfully completes will determine the result of the race, and the other effect will be interrupted. If neither effect succeeds, the function will fail with a [cause](/docs/data-types/cause/) containing all the errors. This is useful when you want to run two effects concurrently, but only care about the first one to succeed. It is commonly used in cases like timeouts, retries, or when you want to optimize for the faster response without worrying about the other effect. **Example** (Both Tasks Succeed) ```ts twoslash import { Effect, Console } from "effect" const task1 = Effect.succeed("task1").pipe( Effect.delay("200 millis"), Effect.tap(Console.log("task1 done")), Effect.onInterrupt(() => Console.log("task1 interrupted")) ) const task2 = Effect.succeed("task2").pipe( Effect.delay("100 millis"), Effect.tap(Console.log("task2 done")), Effect.onInterrupt(() => Console.log("task2 interrupted")) ) const program = Effect.race(task1, task2) Effect.runFork(program) /* Output: task1 done task2 interrupted */ ``` **Example** (One Task Fails, One Succeeds) ```ts twoslash import { Effect, Console } from "effect" const task1 = Effect.fail("task1").pipe( Effect.delay("100 millis"), Effect.tap(Console.log("task1 done")), Effect.onInterrupt(() => Console.log("task1 interrupted")) ) const task2 = Effect.succeed("task2").pipe( Effect.delay("200 millis"), Effect.tap(Console.log("task2 done")), Effect.onInterrupt(() => Console.log("task2 interrupted")) ) const program = Effect.race(task1, task2) Effect.runFork(program) /* Output: task2 done */ ``` **Example** (Both Tasks Fail) ```ts twoslash import { Effect, Console } from "effect" const task1 = Effect.fail("task1").pipe( Effect.delay("100 millis"), Effect.tap(Console.log("task1 done")), Effect.onInterrupt(() => Console.log("task1 interrupted")) ) const task2 = Effect.fail("task2").pipe( Effect.delay("200 millis"), Effect.tap(Console.log("task2 done")), Effect.onInterrupt(() => Console.log("task2 interrupted")) ) const program = Effect.race(task1, task2) Effect.runPromiseExit(program).then(console.log) /* Output: { _id: 'Exit', _tag: 'Failure', cause: { _id: 'Cause', _tag: 'Parallel', left: { _id: 'Cause', _tag: 'Fail', failure: 'task1' }, right: { _id: 'Cause', _tag: 'Fail', failure: 'task2' } } } */ ``` If you want to handle the result of whichever task completes first, whether it succeeds or fails, you can use the `Effect.either` function. This function wraps the result in an [Either](/docs/data-types/either/) type, allowing you to see if the result was a success (`Right`) or a failure (`Left`): **Example** (Handling Success or Failure with Either) ```ts twoslash import { Effect, Console } from "effect" const task1 = Effect.fail("task1").pipe( Effect.delay("100 millis"), Effect.tap(Console.log("task1 done")), Effect.onInterrupt(() => Console.log("task1 interrupted")) ) const task2 = Effect.succeed("task2").pipe( Effect.delay("200 millis"), Effect.tap(Console.log("task2 done")), Effect.onInterrupt(() => Console.log("task2 interrupted")) ) // Run both tasks concurrently, wrapping the result // in Either to capture success or failure const program = Effect.race(Effect.either(task1), Effect.either(task2)) Effect.runPromise(program).then(console.log) /* Output: task2 interrupted { _id: 'Either', _tag: 'Left', left: 'task1' } */ ``` ### raceAll This function runs multiple effects concurrently and returns the result of the first one to succeed. If one effect succeeds, the others will be interrupted. If none of the effects succeed, the function will fail with the last error encountered. This is useful when you want to race multiple effects, but only care about the first one to succeed. It is commonly used in cases like timeouts, retries, or when you want to optimize for the faster response without worrying about the other effects. **Example** (All Tasks Succeed) ```ts twoslash import { Effect, Console } from "effect" const task1 = Effect.succeed("task1").pipe( Effect.delay("100 millis"), Effect.tap(Console.log("task1 done")), Effect.onInterrupt(() => Console.log("task1 interrupted")) ) const task2 = Effect.succeed("task2").pipe( Effect.delay("200 millis"), Effect.tap(Console.log("task2 done")), Effect.onInterrupt(() => Console.log("task2 interrupted")) ) const task3 = Effect.succeed("task3").pipe( Effect.delay("150 millis"), Effect.tap(Console.log("task3 done")), Effect.onInterrupt(() => Console.log("task3 interrupted")) ) const program = Effect.raceAll([task1, task2, task3]) Effect.runFork(program) /* Output: task1 done task2 interrupted task3 interrupted */ ``` **Example** (One Task Fails, Two Tasks Succeed) ```ts twoslash import { Effect, Console } from "effect" const task1 = Effect.fail("task1").pipe( Effect.delay("100 millis"), Effect.tap(Console.log("task1 done")), Effect.onInterrupt(() => Console.log("task1 interrupted")) ) const task2 = Effect.succeed("task2").pipe( Effect.delay("200 millis"), Effect.tap(Console.log("task2 done")), Effect.onInterrupt(() => Console.log("task2 interrupted")) ) const task3 = Effect.succeed("task3").pipe( Effect.delay("150 millis"), Effect.tap(Console.log("task3 done")), Effect.onInterrupt(() => Console.log("task3 interrupted")) ) const program = Effect.raceAll([task1, task2, task3]) Effect.runFork(program) /* Output: task3 done task2 interrupted */ ``` **Example** (All Tasks Fail) ```ts twoslash import { Effect, Console } from "effect" const task1 = Effect.fail("task1").pipe( Effect.delay("100 millis"), Effect.tap(Console.log("task1 done")), Effect.onInterrupt(() => Console.log("task1 interrupted")) ) const task2 = Effect.fail("task2").pipe( Effect.delay("200 millis"), Effect.tap(Console.log("task2 done")), Effect.onInterrupt(() => Console.log("task2 interrupted")) ) const task3 = Effect.fail("task3").pipe( Effect.delay("150 millis"), Effect.tap(Console.log("task3 done")), Effect.onInterrupt(() => Console.log("task3 interrupted")) ) const program = Effect.raceAll([task1, task2, task3]) Effect.runPromiseExit(program).then(console.log) /* Output: { _id: 'Exit', _tag: 'Failure', cause: { _id: 'Cause', _tag: 'Fail', failure: 'task2' } } */ ``` ### raceFirst This function takes two effects and runs them concurrently, returning the result of the first one that completes, regardless of whether it succeeds or fails. This function is useful when you want to race two operations, and you want to proceed with whichever one finishes first, regardless of whether it succeeds or fails. **Example** (Both Tasks Succeed) ```ts twoslash import { Effect, Console } from "effect" const task1 = Effect.succeed("task1").pipe( Effect.delay("100 millis"), Effect.tap(Console.log("task1 done")), Effect.onInterrupt(() => Console.log("task1 interrupted").pipe(Effect.delay("100 millis")) ) ) const task2 = Effect.succeed("task2").pipe( Effect.delay("200 millis"), Effect.tap(Console.log("task2 done")), Effect.onInterrupt(() => Console.log("task2 interrupted").pipe(Effect.delay("100 millis")) ) ) const program = Effect.raceFirst(task1, task2).pipe( Effect.tap(Console.log("more work...")) ) Effect.runPromiseExit(program).then(console.log) /* Output: task1 done task2 interrupted more work... { _id: 'Exit', _tag: 'Success', value: 'task1' } */ ``` **Example** (One Task Fails, One Succeeds) ```ts twoslash import { Effect, Console } from "effect" const task1 = Effect.fail("task1").pipe( Effect.delay("100 millis"), Effect.tap(Console.log("task1 done")), Effect.onInterrupt(() => Console.log("task1 interrupted").pipe(Effect.delay("100 millis")) ) ) const task2 = Effect.succeed("task2").pipe( Effect.delay("200 millis"), Effect.tap(Console.log("task2 done")), Effect.onInterrupt(() => Console.log("task2 interrupted").pipe(Effect.delay("100 millis")) ) ) const program = Effect.raceFirst(task1, task2).pipe( Effect.tap(Console.log("more work...")) ) Effect.runPromiseExit(program).then(console.log) /* Output: task2 interrupted { _id: 'Exit', _tag: 'Failure', cause: { _id: 'Cause', _tag: 'Fail', failure: 'task1' } } */ ``` #### Disconnecting Effects The `Effect.raceFirst` function safely interrupts the "loser" effect once the other completes, but it will not resume until the loser is cleanly terminated. If you want a quicker return, you can disconnect the interrupt signal for both effects. Instead of calling: ```ts showLineNumbers=false Effect.raceFirst(task1, task2) ``` You can use: ```ts showLineNumbers=false Effect.raceFirst(Effect.disconnect(task1), Effect.disconnect(task2)) ``` This allows both effects to complete independently while still terminating the losing effect in the background. **Example** (Using `Effect.disconnect` for Quicker Return) ```ts twoslash import { Effect, Console } from "effect" const task1 = Effect.succeed("task1").pipe( Effect.delay("100 millis"), Effect.tap(Console.log("task1 done")), Effect.onInterrupt(() => Console.log("task1 interrupted").pipe(Effect.delay("100 millis")) ) ) const task2 = Effect.succeed("task2").pipe( Effect.delay("200 millis"), Effect.tap(Console.log("task2 done")), Effect.onInterrupt(() => Console.log("task2 interrupted").pipe(Effect.delay("100 millis")) ) ) // Race the two tasks with disconnect to allow quicker return const program = Effect.raceFirst( Effect.disconnect(task1), Effect.disconnect(task2) ).pipe(Effect.tap(Console.log("more work..."))) Effect.runPromiseExit(program).then(console.log) /* Output: task1 done more work... { _id: 'Exit', _tag: 'Success', value: 'task1' } task2 interrupted */ ``` ### raceWith This function runs two effects concurrently and calls a specified "finisher" function once one of the effects completes, regardless of whether it succeeds or fails. The finisher functions for each effect allow you to handle the results of each effect as soon as they complete. The function takes two finisher callbacks, one for each effect, and allows you to specify how to handle the result of the race. This function is useful when you need to react to the completion of either effect without waiting for both to finish. It can be used whenever you want to take action based on the first available result. **Example** (Handling Results of Concurrent Tasks) ```ts twoslash import { Effect, Console } from "effect" const task1 = Effect.succeed("task1").pipe( Effect.delay("100 millis"), Effect.tap(Console.log("task1 done")), Effect.onInterrupt(() => Console.log("task1 interrupted").pipe(Effect.delay("100 millis")) ) ) const task2 = Effect.succeed("task2").pipe( Effect.delay("200 millis"), Effect.tap(Console.log("task2 done")), Effect.onInterrupt(() => Console.log("task2 interrupted").pipe(Effect.delay("100 millis")) ) ) const program = Effect.raceWith(task1, task2, { onSelfDone: (exit) => Console.log(`task1 exited with ${exit}`), onOtherDone: (exit) => Console.log(`task2 exited with ${exit}`) }) Effect.runFork(program) /* Output: task1 done task1 exited with { "_id": "Exit", "_tag": "Success", "value": "task1" } task2 interrupted */ ```
# [Deferred](https://effect.website/docs/concurrency/deferred/)
## Overview A `Deferred` is a specialized subtype of `Effect` that acts like a one-time variable with some unique characteristics. It can only be completed once, making it a useful tool for managing asynchronous operations and synchronization between different parts of your program. A deferred is essentially a synchronization primitive that represents a value that may not be available right away. When you create a deferred, it starts out empty. Later, it can be completed with either a success value `Success` or an error value `Error`: ```text showLineNumbers=false ┌─── Represents the success type │ ┌─── Represents the error type │ │ ▼ ▼ Deferred ``` Once completed, it cannot be changed again. When a fiber calls `Deferred.await`, it will pause until the deferred is completed. While the fiber is waiting, it doesn't block the thread, it only blocks semantically. This means other fibers can still run, ensuring efficient concurrency. A deferred is conceptually similar to JavaScript's `Promise`. The key difference is that it supports both success and error types, giving more type safety. ## Creating a Deferred A deferred can be created using the `Deferred.make` constructor. This returns an effect that represents the creation of the deferred. Since the creation of a deferred involves memory allocation, it must be done within an effect to ensure safe management of resources. **Example** (Creating a Deferred) ```ts twoslash import { Deferred } from "effect" // ┌─── Effect> // ▼ const deferred = Deferred.make() ``` ## Awaiting To retrieve a value from a deferred, you can use `Deferred.await`. This operation suspends the calling fiber until the deferred is completed with a value or an error. ```ts twoslash import { Effect, Deferred } from "effect" // ┌─── Effect, never, never> // ▼ const deferred = Deferred.make() // ┌─── Effect // ▼ const value = deferred.pipe(Effect.andThen(Deferred.await)) ``` ## Completing You can complete a deferred in several ways, depending on whether you want to succeed, fail, or interrupt the waiting fibers: | API | Description | | ----------------------- | --------------------------------------------------------------------------------------------------------------- | | `Deferred.succeed` | Completes the deferred successfully with a value. | | `Deferred.done` | Completes the deferred with an [Exit](/docs/data-types/exit/) value. | | `Deferred.complete` | Completes the deferred with the result of an effect. | | `Deferred.completeWith` | Completes the deferred with an effect. This effect will be executed by each waiting fiber, so use it carefully. | | `Deferred.fail` | Fails the deferred with an error. | | `Deferred.die` | Defects the deferred with a user-defined error. | | `Deferred.failCause` | Fails or defects the deferred with a [Cause](/docs/data-types/cause/). | | `Deferred.interrupt` | Interrupts the deferred, forcefully stopping or interrupting the waiting fibers. | **Example** (Completing a Deferred with Success) ```ts twoslash import { Effect, Deferred } from "effect" const program = Effect.gen(function* () { const deferred = yield* Deferred.make() // Complete the Deferred successfully yield* Deferred.succeed(deferred, 1) // Awaiting the Deferred to get its value const value = yield* Deferred.await(deferred) console.log(value) }) Effect.runFork(program) // Output: 1 ``` Completing a deferred produces an `Effect`. This effect returns `true` if the deferred was successfully completed, and `false` if it had already been completed previously. This can be useful for tracking the state of the deferred. **Example** (Checking Completion Status) ```ts twoslash import { Effect, Deferred } from "effect" const program = Effect.gen(function* () { const deferred = yield* Deferred.make() // Attempt to fail the Deferred const firstAttempt = yield* Deferred.fail(deferred, "oh no!") // Attempt to succeed after it has already been completed const secondAttempt = yield* Deferred.succeed(deferred, 1) console.log([firstAttempt, secondAttempt]) }) Effect.runFork(program) // Output: [ true, false ] ``` ## Checking Completion Status Sometimes, you might need to check if a deferred has been completed without suspending the fiber. This can be done using the `Deferred.poll` method. Here's how it works: - `Deferred.poll` returns an `Option>`: - If the `Deferred` is incomplete, it returns `None`. - If the `Deferred` is complete, it returns `Some`, which contains the result or error. Additionally, you can use the `Deferred.isDone` function to check if a deferred has been completed. This method returns an `Effect`, which evaluates to `true` if the `Deferred` is completed, allowing you to quickly check its state. **Example** (Polling and Checking Completion Status) ```ts twoslash import { Effect, Deferred } from "effect" const program = Effect.gen(function* () { const deferred = yield* Deferred.make() // Polling the Deferred to check if it's completed const done1 = yield* Deferred.poll(deferred) // Checking if the Deferred has been completed const done2 = yield* Deferred.isDone(deferred) console.log([done1, done2]) }) Effect.runFork(program) /* Output: [ { _id: 'Option', _tag: 'None' }, false ] */ ``` ## Common Use Cases `Deferred` becomes useful when you need to wait for something specific to happen in your program. It's ideal for scenarios where you want one part of your code to signal another part when it's ready. Here are a few common use cases: | **Use Case** | **Description** | | ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Coordinating Fibers** | When you have multiple concurrent tasks and need to coordinate their actions, `Deferred` can help one fiber signal to another when it has completed its task. | | **Synchronization** | Anytime you want to ensure that one piece of code doesn't proceed until another piece of code has finished its work, `Deferred` can provide the synchronization you need. | | **Handing Over Work** | You can use `Deferred` to hand over work from one fiber to another. For example, one fiber can prepare some data, and then a second fiber can continue processing it. | | **Suspending Execution** | When you want a fiber to pause its execution until some condition is met, a `Deferred` can be used to block it until the condition is satisfied. | **Example** (Using Deferred to Coordinate Two Fibers) In this example, a deferred is used to pass a value between two fibers. By running both fibers concurrently and using the deferred as a synchronization point, we can ensure that `fiberB` only proceeds after `fiberA` has completed its task. ```ts twoslash import { Effect, Deferred, Fiber } from "effect" const program = Effect.gen(function* () { const deferred = yield* Deferred.make() // Completes the Deferred with a value after a delay const taskA = Effect.gen(function* () { console.log("Starting task to complete the Deferred") yield* Effect.sleep("1 second") console.log("Completing the Deferred") return yield* Deferred.succeed(deferred, "hello world") }) // Waits for the Deferred and prints the value const taskB = Effect.gen(function* () { console.log("Starting task to get the value from the Deferred") const value = yield* Deferred.await(deferred) console.log("Got the value from the Deferred") return value }) // Run both fibers concurrently const fiberA = yield* Effect.fork(taskA) const fiberB = yield* Effect.fork(taskB) // Wait for both fibers to complete const both = yield* Fiber.join(Fiber.zip(fiberA, fiberB)) console.log(both) }) Effect.runFork(program) /* Starting task to complete the Deferred Starting task to get the value from the Deferred Completing the Deferred Got the value from the Deferred [ true, 'hello world' ] */ ```
# [Fibers](https://effect.website/docs/concurrency/fibers/)
## Overview import { Aside } from "@astrojs/starlight/components" Effect is a highly concurrent framework powered by fibers. Fibers are lightweight virtual threads with resource-safe cancellation capabilities, enabling many features in Effect. In this section, you will learn the basics of fibers and get familiar with some of the powerful low-level operators that utilize fibers. ## What Are Virtual Threads? JavaScript is inherently single-threaded, meaning it executes code in a single sequence of instructions. However, modern JavaScript environments use an event loop to manage asynchronous operations, creating the illusion of multitasking. In this context, virtual threads, or fibers, are logical threads simulated by the Effect runtime. They allow concurrent execution without relying on true multi-threading, which is not natively supported in JavaScript. ## How Fibers work All effects in Effect are executed by fibers. If you didn't create the fiber yourself, it was created by an operation you're using (if it's concurrent) or by the Effect runtime system. A fiber is created any time an effect is run. When running effects concurrently, a fiber is created for each concurrent effect. Even if you write "single-threaded" code with no concurrent operations, there will always be at least one fiber: the "main" fiber that executes your effect. Effect fibers have a well-defined lifecycle based on the effect they are executing. Every fiber exits with either a failure or success, depending on whether the effect it is executing fails or succeeds. Effect fibers have unique identities, local state, and a status (such as done, running, or suspended). To summarize: - An `Effect` is a higher-level concept that describes an effectful computation. It is lazy and immutable, meaning it represents a computation that may produce a value or fail but does not immediately execute. - A fiber, on the other hand, represents the running execution of an `Effect`. It can be interrupted or awaited to retrieve its result. Think of it as a way to control and interact with the ongoing computation. ## The Fiber Data Type The `Fiber` data type in Effect represents a "handle" on the execution of an effect. Here is the general form of a `Fiber`: ```text showLineNumbers=false ┌─── Represents the success type │ ┌─── Represents the error type │ │ ▼ ▼ Fiber ``` This type indicates that a fiber: - Succeeds and returns a value of type `Success` - Fails with an error of type `Error` Fibers do not have an `Requirements` type parameter because they only execute effects that have already had their requirements provided to them. ## Forking Effects You can create a new fiber by **forking** an effect. This starts the effect in a new fiber, and you receive a reference to that fiber. **Example** (Forking a Fiber) In this example, the Fibonacci calculation is forked into its own fiber, allowing it to run independently of the main fiber. The reference to the `fib10Fiber` can be used later to join or interrupt the fiber. ```ts twoslash import { Effect } from "effect" const fib = (n: number): Effect.Effect => n < 2 ? Effect.succeed(n) : Effect.zipWith(fib(n - 1), fib(n - 2), (a, b) => a + b) // ┌─── Effect, never, never> // ▼ const fib10Fiber = Effect.fork(fib(10)) ``` ## Joining Fibers One common operation with fibers is **joining** them. By using the `Fiber.join` function, you can wait for a fiber to complete and retrieve its result. The joined fiber will either succeed or fail, and the `Effect` returned by `join` reflects the outcome of the fiber. **Example** (Joining a Fiber) ```ts twoslash import { Effect, Fiber } from "effect" const fib = (n: number): Effect.Effect => n < 2 ? Effect.succeed(n) : Effect.zipWith(fib(n - 1), fib(n - 2), (a, b) => a + b) // ┌─── Effect, never, never> // ▼ const fib10Fiber = Effect.fork(fib(10)) const program = Effect.gen(function* () { // Retrieve the fiber const fiber = yield* fib10Fiber // Join the fiber and get the result const n = yield* Fiber.join(fiber) console.log(n) }) Effect.runFork(program) // Output: 55 ``` ## Awaiting Fibers The `Fiber.await` function is a helpful tool when working with fibers. It allows you to wait for a fiber to complete and retrieve detailed information about how it finished. The result is encapsulated in an [Exit](/docs/data-types/exit/) value, which gives you insight into whether the fiber succeeded, failed, or was interrupted. **Example** (Awaiting Fiber Completion) ```ts twoslash import { Effect, Fiber } from "effect" const fib = (n: number): Effect.Effect => n < 2 ? Effect.succeed(n) : Effect.zipWith(fib(n - 1), fib(n - 2), (a, b) => a + b) // ┌─── Effect, never, never> // ▼ const fib10Fiber = Effect.fork(fib(10)) const program = Effect.gen(function* () { // Retrieve the fiber const fiber = yield* fib10Fiber // Await its completion and get the Exit result const exit = yield* Fiber.await(fiber) console.log(exit) }) Effect.runFork(program) /* Output: { _id: 'Exit', _tag: 'Success', value: 55 } */ ``` ## Interruption Model While developing concurrent applications, there are several cases that we need to interrupt the execution of other fibers, for example: 1. A parent fiber might start some child fibers to perform a task, and later the parent might decide that, it doesn't need the result of some or all of the child fibers. 2. Two or more fibers start race with each other. The fiber whose result is computed first wins, and all other fibers are no longer needed, and should be interrupted. 3. In interactive applications, a user may want to stop some already running tasks, such as clicking on the "stop" button to prevent downloading more files. 4. Computations that run longer than expected should be aborted by using timeout operations. 5. When we have an application that perform compute-intensive tasks based on the user inputs, if the user changes the input we should cancel the current task and perform another one. ### Polling vs. Asynchronous Interruption When it comes to interrupting fibers, a naive approach is to allow one fiber to forcefully terminate another fiber. However, this approach is not ideal because it can leave shared state in an inconsistent and unreliable state if the target fiber is in the middle of modifying that state. Therefore, it does not guarantee internal consistency of the shared mutable state. Instead, there are two popular and valid solutions to tackle this problem: 1. **Semi-asynchronous Interruption (Polling for Interruption)**: Imperative languages often employ polling as a semi-asynchronous signaling mechanism, such as Java. In this model, a fiber sends an interruption request to another fiber. The target fiber continuously polls the interrupt status and checks whether it has received any interruption requests from other fibers. If an interruption request is detected, the target fiber terminates itself as soon as possible. With this solution, the fiber itself handles critical sections. So, if a fiber is in the middle of a critical section and receives an interruption request, it ignores the interruption and defers its handling until after the critical section. However, one drawback of this approach is that if the programmer forgets to poll regularly, the target fiber can become unresponsive, leading to deadlocks. Additionally, polling a global flag is not aligned with the functional paradigm followed by Effect. 2. **Asynchronous Interruption**: In asynchronous interruption, a fiber is allowed to terminate another fiber. The target fiber is not responsible for polling the interrupt status. Instead, during critical sections, the target fiber disables the interruptibility of those regions. This is a purely functional solution that doesn't require polling a global state. Effect adopts this solution for its interruption model, which is a fully asynchronous signaling mechanism. This mechanism overcomes the drawback of forgetting to poll regularly. It is also fully compatible with the functional paradigm because in a purely functional computation, we can abort the computation at any point, except during critical sections where interruption is disabled. ### Interrupting Fibers Fibers can be interrupted if their result is no longer needed. This action immediately stops the fiber and safely runs all finalizers to release any resources. Like `Fiber.await`, the `Fiber.interrupt` function returns an [Exit](/docs/data-types/exit/) value that provides detailed information about how the fiber ended. **Example** (Interrupting a Fiber) ```ts twoslash import { Effect, Fiber } from "effect" const program = Effect.gen(function* () { // Fork a fiber that runs indefinitely, printing "Hi!" const fiber = yield* Effect.fork( Effect.forever(Effect.log("Hi!").pipe(Effect.delay("10 millis"))) ) yield* Effect.sleep("30 millis") // Interrupt the fiber and get an Exit value detailing how it finished const exit = yield* Fiber.interrupt(fiber) console.log(exit) }) Effect.runFork(program) /* Output: timestamp=... level=INFO fiber=#1 message=Hi! timestamp=... level=INFO fiber=#1 message=Hi! { _id: 'Exit', _tag: 'Failure', cause: { _id: 'Cause', _tag: 'Interrupt', fiberId: { _id: 'FiberId', _tag: 'Runtime', id: 0, startTimeMillis: ... } } } */ ``` By default, the effect returned by `Fiber.interrupt` waits until the fiber has fully terminated before resuming. This ensures that no new fibers are started before the previous ones have finished, a behavior known as "back-pressuring." If you do not require this waiting behavior, you can fork the interruption itself, allowing the main program to proceed without waiting for the fiber to terminate: **Example** (Forking an Interruption) ```ts twoslash import { Effect, Fiber } from "effect" const program = Effect.gen(function* () { const fiber = yield* Effect.fork( Effect.forever(Effect.log("Hi!").pipe(Effect.delay("10 millis"))) ) yield* Effect.sleep("30 millis") const _ = yield* Effect.fork(Fiber.interrupt(fiber)) console.log("Do something else...") }) Effect.runFork(program) /* Output: timestamp=... level=INFO fiber=#1 message=Hi! timestamp=... level=INFO fiber=#1 message=Hi! Do something else... */ ``` There is also a shorthand for background interruption called `Fiber.interruptFork`. ```ts twoslash del={8} ins={9} import { Effect, Fiber } from "effect" const program = Effect.gen(function* () { const fiber = yield* Effect.fork( Effect.forever(Effect.log("Hi!").pipe(Effect.delay("10 millis"))) ) yield* Effect.sleep("30 millis") // const _ = yield* Effect.fork(Fiber.interrupt(fiber)) const _ = yield* Fiber.interruptFork(fiber) console.log("Do something else...") }) Effect.runFork(program) /* Output: timestamp=... level=INFO fiber=#1 message=Hi! timestamp=... level=INFO fiber=#1 message=Hi! Do something else... */ ``` ## Composing Fibers The `Fiber.zip` and `Fiber.zipWith` functions allow you to combine two fibers into one. The resulting fiber will produce the results of both input fibers. If either fiber fails, the combined fiber will also fail. **Example** (Combining Fibers with `Fiber.zip`) In this example, both fibers run concurrently, and the results are combined into a tuple. ```ts twoslash import { Effect, Fiber } from "effect" const program = Effect.gen(function* () { // Fork two fibers that each produce a string const fiber1 = yield* Effect.fork(Effect.succeed("Hi!")) const fiber2 = yield* Effect.fork(Effect.succeed("Bye!")) // Combine the two fibers using Fiber.zip const fiber = Fiber.zip(fiber1, fiber2) // Join the combined fiber and get the result as a tuple const tuple = yield* Fiber.join(fiber) console.log(tuple) }) Effect.runFork(program) /* Output: [ 'Hi!', 'Bye!' ] */ ``` Another way to compose fibers is by using `Fiber.orElse`. This function allows you to provide an alternative fiber that will execute if the first one fails. If the first fiber succeeds, its result will be returned. If it fails, the second fiber will run instead, and its result will be returned regardless of its outcome. **Example** (Providing a Fallback Fiber with `Fiber.orElse`) ```ts twoslash import { Effect, Fiber } from "effect" const program = Effect.gen(function* () { // Fork a fiber that will fail const fiber1 = yield* Effect.fork(Effect.fail("Uh oh!")) // Fork another fiber that will succeed const fiber2 = yield* Effect.fork(Effect.succeed("Hurray!")) // If fiber1 fails, fiber2 will be used as a fallback const fiber = Fiber.orElse(fiber1, fiber2) const message = yield* Fiber.join(fiber) console.log(message) }) Effect.runFork(program) /* Output: Hurray! */ ``` ## Lifetime of Child Fibers When we fork fibers, depending on how we fork them we can have four different lifetime strategies for the child fibers: 1. **Fork With Automatic Supervision**. If we use the ordinary `Effect.fork` operation, the child fiber will be automatically supervised by the parent fiber. The lifetime child fibers are tied to the lifetime of their parent fiber. This means that these fibers will be terminated either when they end naturally, or when their parent fiber is terminated. 2. **Fork in Global Scope (Daemon)**. Sometimes we want to run long-running background fibers that aren't tied to their parent fiber, and also we want to fork them in a global scope. Any fiber that is forked in global scope will become daemon fiber. This can be achieved by using the `Effect.forkDaemon` operator. As these fibers have no parent, they are not supervised, and they will be terminated when they end naturally, or when our application is terminated. 3. **Fork in Local Scope**. Sometimes, we want to run a background fiber that isn't tied to its parent fiber, but we want to live that fiber in the local scope. We can fork fibers in the local scope by using `Effect.forkScoped`. Such fibers can outlive their parent fiber (so they are not supervised by their parents), and they will be terminated when their life end or their local scope is closed. 4. **Fork in Specific Scope**. This is similar to the previous strategy, but we can have more fine-grained control over the lifetime of the child fiber by forking it in a specific scope. We can do this by using the `Effect.forkIn` operator. ### Fork with Automatic Supervision Effect follows a **structured concurrency** model, where child fibers' lifetimes are tied to their parent. Simply put, the lifespan of a fiber depends on the lifespan of its parent fiber. **Example** (Automatically Supervised Child Fiber) In this scenario, the `parent` fiber spawns a `child` fiber that repeatedly prints a message every second. The `child` fiber will be terminated when the `parent` fiber completes. ```ts twoslash import { Effect, Console, Schedule } from "effect" // Child fiber that logs a message repeatedly every second const child = Effect.repeat( Console.log("child: still running!"), Schedule.fixed("1 second") ) const parent = Effect.gen(function* () { console.log("parent: started!") // Child fiber is supervised by the parent yield* Effect.fork(child) yield* Effect.sleep("3 seconds") console.log("parent: finished!") }) Effect.runFork(parent) /* Output: parent: started! child: still running! child: still running! child: still running! parent: finished! */ ``` This behavior can be extended to any level of nested fibers, ensuring a predictable and controlled fiber lifecycle. ### Fork in Global Scope (Daemon) You can create a long-running background fiber using `Effect.forkDaemon`. This type of fiber, known as a daemon fiber, is not tied to the lifecycle of its parent fiber. Instead, its lifetime is linked to the global scope. A daemon fiber continues running even if its parent fiber is terminated and will only stop when the global scope is closed or the fiber completes naturally. **Example** (Creating a Daemon Fiber) This example shows how daemon fibers can continue running in the background even after the parent fiber has finished. ```ts twoslash import { Effect, Console, Schedule } from "effect" // Daemon fiber that logs a message repeatedly every second const daemon = Effect.repeat( Console.log("daemon: still running!"), Schedule.fixed("1 second") ) const parent = Effect.gen(function* () { console.log("parent: started!") // Daemon fiber running independently yield* Effect.forkDaemon(daemon) yield* Effect.sleep("3 seconds") console.log("parent: finished!") }) Effect.runFork(parent) /* Output: parent: started! daemon: still running! daemon: still running! daemon: still running! parent: finished! daemon: still running! daemon: still running! daemon: still running! daemon: still running! daemon: still running! ...etc... */ ``` Even if the parent fiber is interrupted, the daemon fiber will continue running independently. **Example** (Interrupting the Parent Fiber) In this example, interrupting the parent fiber doesn't affect the daemon fiber, which continues to run in the background. ```ts twoslash import { Effect, Console, Schedule, Fiber } from "effect" // Daemon fiber that logs a message repeatedly every second const daemon = Effect.repeat( Console.log("daemon: still running!"), Schedule.fixed("1 second") ) const parent = Effect.gen(function* () { console.log("parent: started!") // Daemon fiber running independently yield* Effect.forkDaemon(daemon) yield* Effect.sleep("3 seconds") console.log("parent: finished!") }).pipe(Effect.onInterrupt(() => Console.log("parent: interrupted!"))) // Program that interrupts the parent fiber after 2 seconds const program = Effect.gen(function* () { const fiber = yield* Effect.fork(parent) yield* Effect.sleep("2 seconds") yield* Fiber.interrupt(fiber) // Interrupt the parent fiber }) Effect.runFork(program) /* Output: parent: started! daemon: still running! daemon: still running! parent: interrupted! daemon: still running! daemon: still running! daemon: still running! daemon: still running! daemon: still running! ...etc... */ ``` ### Fork in Local Scope Sometimes we want to create a fiber that is tied to a local [scope](/docs/resource-management/scope/), meaning its lifetime is not dependent on its parent fiber but is bound to the local scope in which it was forked. This can be done using the `Effect.forkScoped` operator. Fibers created with `Effect.forkScoped` can outlive their parent fibers and will only be terminated when the local scope itself is closed. **Example** (Forking a Fiber in a Local Scope) In this example, the `child` fiber continues to run beyond the lifetime of the `parent` fiber. The `child` fiber is tied to the local scope and will be terminated only when the scope ends. ```ts twoslash import { Effect, Console, Schedule } from "effect" // Child fiber that logs a message repeatedly every second const child = Effect.repeat( Console.log("child: still running!"), Schedule.fixed("1 second") ) // ┌─── Effect // ▼ const parent = Effect.gen(function* () { console.log("parent: started!") // Child fiber attached to local scope yield* Effect.forkScoped(child) yield* Effect.sleep("3 seconds") console.log("parent: finished!") }) // Program runs within a local scope const program = Effect.scoped( Effect.gen(function* () { console.log("Local scope started!") yield* Effect.fork(parent) // Scope lasts for 5 seconds yield* Effect.sleep("5 seconds") console.log("Leaving the local scope!") }) ) Effect.runFork(program) /* Output: Local scope started! parent: started! child: still running! child: still running! child: still running! parent: finished! child: still running! child: still running! Leaving the local scope! */ ``` ### Fork in Specific Scope There are some cases where we need more fine-grained control, so we want to fork a fiber in a specific scope. We can use the `Effect.forkIn` operator which takes the target scope as an argument. **Example** (Forking a Fiber in a Specific Scope) In this example, the `child` fiber is forked into the `outerScope`, allowing it to outlive the inner scope but still be terminated when the `outerScope` is closed. ```ts twoslash import { Console, Effect, Schedule } from "effect" // Child fiber that logs a message repeatedly every second const child = Effect.repeat( Console.log("child: still running!"), Schedule.fixed("1 second") ) const program = Effect.scoped( Effect.gen(function* () { yield* Effect.addFinalizer(() => Console.log("The outer scope is about to be closed!") ) // Capture the outer scope const outerScope = yield* Effect.scope // Create an inner scope yield* Effect.scoped( Effect.gen(function* () { yield* Effect.addFinalizer(() => Console.log("The inner scope is about to be closed!") ) // Fork the child fiber in the outer scope yield* Effect.forkIn(child, outerScope) yield* Effect.sleep("3 seconds") }) ) yield* Effect.sleep("5 seconds") }) ) Effect.runFork(program) /* Output: child: still running! child: still running! child: still running! The inner scope is about to be closed! child: still running! child: still running! child: still running! child: still running! child: still running! child: still running! The outer scope is about to be closed! */ ``` ## When do Fibers run? Forked fibers begin execution after the current fiber completes or yields. **Example** (Late Fiber Start Captures Only One Value) In the following example, the `changes` stream only captures a single value, `2`. This happens because the fiber created by `Effect.fork` starts **after** the value is updated. ```ts twoslash import { Effect, SubscriptionRef, Stream, Console } from "effect" const program = Effect.gen(function* () { const ref = yield* SubscriptionRef.make(0) yield* ref.changes.pipe( // Log each change in SubscriptionRef Stream.tap((n) => Console.log(`SubscriptionRef changed to ${n}`)), Stream.runDrain, // Fork a fiber to run the stream Effect.fork ) yield* SubscriptionRef.set(ref, 1) yield* SubscriptionRef.set(ref, 2) }) Effect.runFork(program) /* Output: SubscriptionRef changed to 2 */ ``` If you add a short delay with `Effect.sleep()` or call `Effect.yieldNow()`, you allow the current fiber to yield. This gives the forked fiber enough time to start and collect all values before they are updated. **Example** (Delay Allows Fiber to Capture All Values) ```ts twoslash ins={14} import { Effect, SubscriptionRef, Stream, Console } from "effect" const program = Effect.gen(function* () { const ref = yield* SubscriptionRef.make(0) yield* ref.changes.pipe( // Log each change in SubscriptionRef Stream.tap((n) => Console.log(`SubscriptionRef changed to ${n}`)), Stream.runDrain, // Fork a fiber to run the stream Effect.fork ) // Allow the fiber a chance to start yield* Effect.sleep("100 millis") yield* SubscriptionRef.set(ref, 1) yield* SubscriptionRef.set(ref, 2) }) Effect.runFork(program) /* Output: SubscriptionRef changed to 0 SubscriptionRef changed to 1 SubscriptionRef changed to 2 */ ```
# [Latch](https://effect.website/docs/concurrency/latch/)
## Overview A Latch is a synchronization tool that works like a gate, letting fibers wait until the latch is opened before they continue. The latch can be either open or closed: - When closed, fibers that reach the latch wait until it opens. - When open, fibers pass through immediately. Once opened, a latch typically stays open, although you can close it again if needed Imagine an application that processes requests only after completing an initial setup (like loading configuration data or establishing a database connection). You can create a latch in a closed state while the setup is happening. Any incoming requests, represented as fibers, would wait at the latch until it opens. Once the setup is finished, you call `latch.open` so the requests can proceed. ## The Latch Interface A `Latch` includes several operations that let you control and observe its state: | Operation | Description | | ---------- | -------------------------------------------------------------------------------------------------------- | | `whenOpen` | Runs a given effect only if the latch is open, otherwise, waits until it opens. | | `open` | Opens the latch so that any waiting fibers can proceed. | | `close` | Closes the latch, causing fibers to wait when they reach this latch in the future. | | `await` | Suspends the current fiber until the latch is opened. If the latch is already open, returns immediately. | | `release` | Allows waiting fibers to continue without permanently opening the latch. | ## Creating a Latch Use the `Effect.makeLatch` function to create a latch in an open or closed state by passing a boolean. The default is `false`, which means it starts closed. **Example** (Creating and Using a Latch) In this example, the latch starts closed. A fiber logs "open sesame" only when the latch is open. After waiting for one second, the latch is opened, releasing the fiber: ```ts twoslash import { Console, Effect } from "effect" // A generator function that demonstrates latch usage const program = Effect.gen(function* () { // Create a latch, starting in the closed state const latch = yield* Effect.makeLatch() // Fork a fiber that logs "open sesame" only when the latch is open const fiber = yield* Console.log("open sesame").pipe( latch.whenOpen, // Waits for the latch to open Effect.fork // Fork the effect into a new fiber ) // Wait for 1 second yield* Effect.sleep("1 second") // Open the latch, releasing the fiber yield* latch.open // Wait for the forked fiber to finish yield* fiber.await }) Effect.runFork(program) // Output: open sesame (after 1 second) ``` ## Latch vs Semaphore A latch is good when you have a one-time event or condition that determines whether fibers can proceed. For example, you might use a latch to block all fibers until a setup step is finished, and then open the latch so everyone can continue. A [semaphore](/docs/concurrency/semaphore/) with one lock (often called a binary semaphore or a mutex) is usually for mutual exclusion: it ensures that only one fiber at a time accesses a shared resource or section of code. Once a fiber acquires the lock, no other fiber can enter the protected area until the lock is released. In short: - Use a **latch** if you're gating a set of fibers on a specific event ("Wait here until this becomes true"). - Use a **semaphore (with one lock)** if you need to ensure only one fiber at a time is in a critical section or using a shared resource.
# [PubSub](https://effect.website/docs/concurrency/pubsub/)
## Overview import { Aside } from "@astrojs/starlight/components" A `PubSub` serves as an asynchronous message hub, allowing publishers to send messages that can be received by all current subscribers. Unlike a [Queue](/docs/concurrency/queue/), where each value is delivered to only one consumer, a `PubSub` broadcasts each published message to all subscribers. This makes `PubSub` ideal for scenarios requiring message broadcasting rather than load distribution. ## Basic Operations A `PubSub` stores messages of type `A` and provides two fundamental operations: | API | Description | | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `PubSub.publish` | Sends a message of type `A` to the `PubSub`, returning an effect indicating if the message was successfully published. | | `PubSub.subscribe` | Creates a scoped effect that allows subscription to the `PubSub`, automatically unsubscribing when the scope ends. Subscribers receive messages through a [Dequeue](/docs/concurrency/queue/#dequeue) which holds published messages. | **Example** (Publishing a Message to Multiple Subscribers) ```ts twoslash import { Effect, PubSub, Queue } from "effect" const program = Effect.scoped( Effect.gen(function* () { const pubsub = yield* PubSub.bounded(2) // Two subscribers const dequeue1 = yield* PubSub.subscribe(pubsub) const dequeue2 = yield* PubSub.subscribe(pubsub) // Publish a message to the pubsub yield* PubSub.publish(pubsub, "Hello from a PubSub!") // Each subscriber receives the message console.log("Subscriber 1: " + (yield* Queue.take(dequeue1))) console.log("Subscriber 2: " + (yield* Queue.take(dequeue2))) }) ) Effect.runFork(program) /* Output: Subscriber 1: Hello from a PubSub! Subscriber 2: Hello from a PubSub! */ ``` ## Creating a PubSub ### Bounded PubSub A bounded `PubSub` applies back pressure to publishers when it reaches capacity, suspending additional publishing until space becomes available. Back pressure ensures that all subscribers receive all messages while they are subscribed. However, it can lead to slower message delivery if a subscriber is slow. **Example** (Bounded PubSub Creation) ```ts twoslash import { PubSub } from "effect" // Creates a bounded PubSub with a capacity of 2 const boundedPubSub = PubSub.bounded(2) ``` ### Dropping PubSub A dropping `PubSub` discards new values when full. The `PubSub.publish` operation returns `false` if the message is dropped. In a dropping pubsub, publishers can continue to publish new values, but subscribers are not guaranteed to receive all messages. **Example** (Dropping PubSub Creation) ```ts twoslash import { PubSub } from "effect" // Creates a dropping PubSub with a capacity of 2 const droppingPubSub = PubSub.dropping(2) ``` ### Sliding PubSub A sliding `PubSub` removes the oldest message to make space for new ones, ensuring that publishing never blocks. A sliding pubsub prevents slow subscribers from impacting the message delivery rate. However, there's still a risk that slow subscribers may miss some messages. **Example** (Sliding PubSub Creation) ```ts twoslash import { PubSub } from "effect" // Creates a sliding PubSub with a capacity of 2 const slidingPubSub = PubSub.sliding(2) ``` ### Unbounded PubSub An unbounded `PubSub` has no capacity limit, so publishing always succeeds immediately. Unbounded pubsubs guarantee that all subscribers receive all messages without slowing down message delivery. However, they can grow indefinitely if messages are published faster than they are consumed. Generally, it's recommended to use bounded, dropping, or sliding pubsubs unless you have specific use cases for unbounded pubsubs. **Example** ```ts twoslash import { PubSub } from "effect" // Creates an unbounded PubSub with unlimited capacity const unboundedPubSub = PubSub.unbounded() ``` ## Operators On PubSubs ### publishAll The `PubSub.publishAll` function lets you publish multiple values to the pubsub at once. **Example** (Publishing Multiple Messages) ```ts twoslash import { Effect, PubSub, Queue } from "effect" const program = Effect.scoped( Effect.gen(function* () { const pubsub = yield* PubSub.bounded(2) const dequeue = yield* PubSub.subscribe(pubsub) yield* PubSub.publishAll(pubsub, ["Message 1", "Message 2"]) console.log(yield* Queue.takeAll(dequeue)) }) ) Effect.runFork(program) /* Output: { _id: 'Chunk', values: [ 'Message 1', 'Message 2' ] } */ ``` ### capacity / size You can check the capacity and current size of a pubsub using `PubSub.capacity` and `PubSub.size`, respectively. Note that `PubSub.capacity` returns a `number` because the capacity is set at pubsub creation and never changes. In contrast, `PubSub.size` returns an effect that determines the current size of the pubsub since the number of messages in the pubsub can change over time. **Example** (Retrieving PubSub Capacity and Size) ```ts twoslash import { Effect, PubSub } from "effect" const program = Effect.gen(function* () { const pubsub = yield* PubSub.bounded(2) console.log(`capacity: ${PubSub.capacity(pubsub)}`) console.log(`size: ${yield* PubSub.size(pubsub)}`) }) Effect.runFork(program) /* Output: capacity: 2 size: 0 */ ``` ### Shutting Down a PubSub To shut down a pubsub, use `PubSub.shutdown`. You can also verify if it has been shut down with `PubSub.isShutdown`, or wait for the shutdown to complete with `PubSub.awaitShutdown`. Shutting down a pubsub also terminates all associated queues, ensuring that the shutdown signal is effectively communicated. ## PubSub as an Enqueue `PubSub` operators mirror those of [Queue](/docs/concurrency/queue/) with the main difference being that `PubSub.publish` and `PubSub.subscribe` are used in place of `Queue.offer` and `Queue.take`. If you're already familiar with using a `Queue`, you’ll find `PubSub` straightforward. Essentially, a `PubSub` can be seen as a `Enqueue` that only allows writes: ```ts twoslash showLineNumbers=false import type { Queue } from "effect" interface PubSub extends Queue.Enqueue {} ``` Here, the `Enqueue` type refers to a queue that only accepts enqueues (or writes). Any value enqueued here is published to the pubsub, and operations like shutdown will also affect the pubsub. This design makes `PubSub` highly flexible, letting you use it anywhere you need a `Enqueue` that only accepts published values.
# [Queue](https://effect.website/docs/concurrency/queue/)
## Overview A `Queue` is a lightweight in-memory queue with built-in back-pressure, enabling asynchronous, purely-functional, and type-safe handling of data. ## Basic Operations A `Queue` stores values of type `A` and provides two fundamental operations: | API | Description | | ------------- | ---------------------------------------------------- | | `Queue.offer` | Adds a value of type `A` to the queue. | | `Queue.take` | Removes and returns the oldest value from the queue. | **Example** (Adding and Retrieving an Item) ```ts twoslash import { Effect, Queue } from "effect" const program = Effect.gen(function* () { // Creates a bounded queue with capacity 100 const queue = yield* Queue.bounded(100) // Adds 1 to the queue yield* Queue.offer(queue, 1) // Retrieves and removes the oldest value const value = yield* Queue.take(queue) return value }) Effect.runPromise(program).then(console.log) // Output: 1 ``` ## Creating a Queue Queues can be **bounded** (with a specified capacity) or **unbounded** (without a limit). Different types of queues handle new values differently when they reach capacity. ### Bounded Queue A bounded queue applies back-pressure when full, meaning any `Queue.offer` operation will suspend until there is space. **Example** (Creating a Bounded Queue) ```ts twoslash import { Queue } from "effect" // Creating a bounded queue with a capacity of 100 const boundedQueue = Queue.bounded(100) ``` ### Dropping Queue A dropping queue discards new values if the queue is full. **Example** (Creating a Dropping Queue) ```ts twoslash import { Queue } from "effect" // Creating a dropping queue with a capacity of 100 const droppingQueue = Queue.dropping(100) ``` ### Sliding Queue A sliding queue removes old values to make space for new ones when it reaches capacity. **Example** (Creating a Sliding Queue) ```ts twoslash import { Queue } from "effect" // Creating a sliding queue with a capacity of 100 const slidingQueue = Queue.sliding(100) ``` ### Unbounded Queue An unbounded queue has no capacity limit, allowing unrestricted additions. **Example** (Creating an Unbounded Queue) ```ts twoslash import { Queue } from "effect" // Creates an unbounded queue without a capacity limit const unboundedQueue = Queue.unbounded() ``` ## Adding Items to a Queue ### offer Use `Queue.offer` to add values to the queue. **Example** (Adding a Single Item) ```ts twoslash import { Effect, Queue } from "effect" const program = Effect.gen(function* () { const queue = yield* Queue.bounded(100) // Adds 1 to the queue yield* Queue.offer(queue, 1) }) ``` When using a back-pressured queue, `Queue.offer` suspends if the queue is full. To avoid blocking the main fiber, you can fork the `Queue.offer` operation. **Example** (Handling a Full Queue with `Effect.fork`) ```ts twoslash import { Effect, Queue, Fiber } from "effect" const program = Effect.gen(function* () { const queue = yield* Queue.bounded(1) // Fill the queue with one item yield* Queue.offer(queue, 1) // Attempting to add a second item will suspend as the queue is full const fiber = yield* Effect.fork(Queue.offer(queue, 2)) // Empties the queue to make space yield* Queue.take(queue) // Joins the fiber, completing the suspended offer yield* Fiber.join(fiber) // Returns the size of the queue after additions return yield* Queue.size(queue) }) Effect.runPromise(program).then(console.log) // Output: 1 ``` ### offerAll You can also add multiple items at once using `Queue.offerAll`. **Example** (Adding Multiple Items) ```ts twoslash import { Effect, Queue, Array } from "effect" const program = Effect.gen(function* () { const queue = yield* Queue.bounded(100) const items = Array.range(1, 10) // Adds all items to the queue at once yield* Queue.offerAll(queue, items) // Returns the size of the queue after additions return yield* Queue.size(queue) }) Effect.runPromise(program).then(console.log) // Output: 10 ``` ## Consuming Items from a Queue ### take The `Queue.take` operation removes and returns the oldest item from the queue. If the queue is empty, `Queue.take` will suspend and only resume when an item is added. To prevent blocking, you can fork the `Queue.take` operation into a new fiber. **Example** (Waiting for an Item in a Fiber) ```ts twoslash import { Effect, Queue, Fiber } from "effect" const program = Effect.gen(function* () { const queue = yield* Queue.bounded(100) // This take operation will suspend because the queue is empty const fiber = yield* Effect.fork(Queue.take(queue)) // Adds an item to the queue yield* Queue.offer(queue, "something") // Joins the fiber to get the result of the take operation const value = yield* Fiber.join(fiber) return value }) Effect.runPromise(program).then(console.log) // Output: something ``` ### poll To retrieve the queue's first item without suspending, use `Queue.poll`. If the queue is empty, `Queue.poll` returns `None`; if it has an item, it wraps it in `Some`. **Example** (Polling an Item) ```ts twoslash import { Effect, Queue } from "effect" const program = Effect.gen(function* () { const queue = yield* Queue.bounded(100) // Adds items to the queue yield* Queue.offer(queue, 10) yield* Queue.offer(queue, 20) // Retrieves the first item if available const head = yield* Queue.poll(queue) return head }) Effect.runPromise(program).then(console.log) /* Output: { _id: "Option", _tag: "Some", value: 10 } */ ``` ### takeUpTo To retrieve multiple items, use `Queue.takeUpTo`, which returns up to the specified number of items. If there aren't enough items, it returns all available items without waiting for more. This function is particularly useful for batch processing when an exact number of items is not required. It ensures the program continues working with whatever data is currently available. If you need to wait for an exact number of items before proceeding, consider using [takeN](#taken). **Example** (Taking Up to N Items) ```ts twoslash import { Effect, Queue } from "effect" const program = Effect.gen(function* () { const queue = yield* Queue.bounded(100) // Adds items to the queue yield* Queue.offer(queue, 1) yield* Queue.offer(queue, 2) yield* Queue.offer(queue, 3) // Retrieves up to 2 items const chunk = yield* Queue.takeUpTo(queue, 2) console.log(chunk) return "some result" }) Effect.runPromise(program).then(console.log) /* Output: { _id: 'Chunk', values: [ 1, 2 ] } some result */ ``` ### takeN Takes a specified number of elements from a queue. If the queue does not contain enough elements, the operation suspends until the required number of elements become available. This function is useful for scenarios where processing requires an exact number of items at a time, ensuring that the operation does not proceed until the batch is complete. **Example** (Taking a Fixed Number of Items) ```ts twoslash import { Effect, Queue, Fiber } from "effect" const program = Effect.gen(function* () { // Create a queue that can hold up to 100 elements const queue = yield* Queue.bounded(100) // Fork a fiber that attempts to take 3 items from the queue const fiber = yield* Effect.fork( Effect.gen(function* () { console.log("Attempting to take 3 items from the queue...") const chunk = yield* Queue.takeN(queue, 3) console.log(`Successfully took 3 items: ${chunk}`) }) ) // Offer only 2 items initially yield* Queue.offer(queue, 1) yield* Queue.offer(queue, 2) console.log( "Offered 2 items. The fiber is now waiting for the 3rd item..." ) // Simulate some delay yield* Effect.sleep("2 seconds") // Offer the 3rd item, which will unblock the takeN call yield* Queue.offer(queue, 3) console.log("Offered the 3rd item, which should unblock the fiber.") // Wait for the fiber to finish yield* Fiber.join(fiber) return "some result" }) Effect.runPromise(program).then(console.log) /* Output: Offered 2 items. The fiber is now waiting for the 3rd item... Attempting to take 3 items from the queue... Offered the 3rd item, which should unblock the fiber. Successfully took 3 items: { "_id": "Chunk", "values": [ 1, 2, 3 ] } some result */ ``` ### takeAll To retrieve all items from the queue at once, use `Queue.takeAll`. This operation completes immediately, returning an empty collection if the queue is empty. **Example** (Taking All Items) ```ts twoslash import { Effect, Queue } from "effect" const program = Effect.gen(function* () { const queue = yield* Queue.bounded(100) // Adds items to the queue yield* Queue.offer(queue, 10) yield* Queue.offer(queue, 20) yield* Queue.offer(queue, 30) // Retrieves all items from the queue const chunk = yield* Queue.takeAll(queue) return chunk }) Effect.runPromise(program).then(console.log) /* Output: { _id: "Chunk", values: [ 10, 20, 30 ] } */ ``` ## Shutting Down a Queue ### shutdown The `Queue.shutdown` operation allows you to interrupt all fibers that are currently suspended on `offer*` or `take*` operations. This action also empties the queue and makes any future `offer*` and `take*` calls terminate immediately. **Example** (Interrupting Fibers on Queue Shutdown) ```ts twoslash import { Effect, Queue, Fiber } from "effect" const program = Effect.gen(function* () { const queue = yield* Queue.bounded(3) // Forks a fiber that waits to take an item from the queue const fiber = yield* Effect.fork(Queue.take(queue)) // Shuts down the queue, interrupting the fiber yield* Queue.shutdown(queue) // Joins the interrupted fiber yield* Fiber.join(fiber) }) ``` ### awaitShutdown The `Queue.awaitShutdown` operation can be used to run an effect when the queue shuts down. It waits until the queue is closed and resumes immediately if the queue is already shut down. **Example** (Waiting for Queue Shutdown) ```ts twoslash import { Effect, Queue, Fiber, Console } from "effect" const program = Effect.gen(function* () { const queue = yield* Queue.bounded(3) // Forks a fiber to await queue shutdown and log a message const fiber = yield* Effect.fork( Queue.awaitShutdown(queue).pipe( Effect.andThen(Console.log("shutting down")) ) ) // Shuts down the queue, triggering the await in the fiber yield* Queue.shutdown(queue) yield* Fiber.join(fiber) }) Effect.runPromise(program) // Output: shutting down ``` ## Offer-only / Take-only Queues Sometimes, you might want certain parts of your code to only add values to a queue (`Enqueue`) or only retrieve values from a queue (`Dequeue`). Effect provides interfaces to enforce these specific capabilities. ### Enqueue All methods for adding values to a queue are defined by the `Enqueue` interface. This restricts the queue to only offer operations. **Example** (Restricting Queue to Offer-only Operations) ```ts twoslash import { Queue } from "effect" const send = (offerOnlyQueue: Queue.Enqueue, value: number) => { // This queue is restricted to offer operations only // Error: cannot use take on an offer-only queue // @ts-expect-error Queue.take(offerOnlyQueue) // Valid offer operation return Queue.offer(offerOnlyQueue, value) } ``` ### Dequeue Similarly, all methods for retrieving values from a queue are defined by the `Dequeue` interface, which restricts the queue to only take operations. **Example** (Restricting Queue to Take-only Operations) ```ts twoslash import { Queue } from "effect" const receive = (takeOnlyQueue: Queue.Dequeue) => { // This queue is restricted to take operations only // Error: cannot use offer on a take-only queue // @ts-expect-error Queue.offer(takeOnlyQueue, 1) // Valid take operation return Queue.take(takeOnlyQueue) } ``` The `Queue` type combines both `Enqueue` and `Dequeue`, so you can easily pass it to different parts of your code, enforcing only `Enqueue` or `Dequeue` behaviors as needed. **Example** (Using Offer-only and Take-only Queues Together) ```ts twoslash import { Effect, Queue } from "effect" const send = (offerOnlyQueue: Queue.Enqueue, value: number) => { return Queue.offer(offerOnlyQueue, value) } const receive = (takeOnlyQueue: Queue.Dequeue) => { return Queue.take(takeOnlyQueue) } const program = Effect.gen(function* () { const queue = yield* Queue.unbounded() // Add values to the queue yield* send(queue, 1) yield* send(queue, 2) // Retrieve values from the queue console.log(yield* receive(queue)) console.log(yield* receive(queue)) }) Effect.runFork(program) /* Output: 1 2 */ ```
# [Semaphore](https://effect.website/docs/concurrency/semaphore/)
## Overview import { Aside } from "@astrojs/starlight/components" A semaphore is a synchronization mechanism used to manage access to a shared resource. In Effect, semaphores help control resource access or coordinate tasks within asynchronous, concurrent operations. A semaphore acts as a generalized mutex, allowing a set number of **permits** to be held and released concurrently. Permits act like tickets, giving tasks or fibers controlled access to a shared resource. When no permits are available, tasks trying to acquire one will wait until a permit is released. ## Creating a Semaphore The `Effect.makeSemaphore` function initializes a semaphore with a specified number of permits. Each permit allows one task to access a resource or perform an operation concurrently, and multiple permits enable a configurable level of concurrency. **Example** (Creating a Semaphore with 3 Permits) ```ts twoslash import { Effect } from "effect" // Create a semaphore with 3 permits const mutex = Effect.makeSemaphore(3) ``` ## withPermits The `withPermits` method lets you specify the number of permits required to run an effect. Once the specified permits are available, it runs the effect, automatically releasing the permits when the task completes. **Example** (Forcing Sequential Task Execution with a One-Permit Semaphore) In this example, three tasks are started concurrently, but they run sequentially because the one-permit semaphore only allows one task to proceed at a time. ```ts twoslash import { Effect } from "effect" const task = Effect.gen(function* () { yield* Effect.log("start") yield* Effect.sleep("2 seconds") yield* Effect.log("end") }) const program = Effect.gen(function* () { const mutex = yield* Effect.makeSemaphore(1) // Wrap the task to require one permit, forcing sequential execution const semTask = mutex .withPermits(1)(task) .pipe(Effect.withLogSpan("elapsed")) // Run 3 tasks concurrently, but they execute sequentially // due to the one-permit semaphore yield* Effect.all([semTask, semTask, semTask], { concurrency: "unbounded" }) }) Effect.runFork(program) /* Output: timestamp=... level=INFO fiber=#1 message=start elapsed=3ms timestamp=... level=INFO fiber=#1 message=end elapsed=2010ms timestamp=... level=INFO fiber=#2 message=start elapsed=2012ms timestamp=... level=INFO fiber=#2 message=end elapsed=4017ms timestamp=... level=INFO fiber=#3 message=start elapsed=4018ms timestamp=... level=INFO fiber=#3 message=end elapsed=6026ms */ ``` **Example** (Using Multiple Permits to Control Concurrent Task Execution) In this example, we create a semaphore with five permits and use `withPermits(n)` to allocate a different number of permits for each task: ```ts twoslash import { Effect } from "effect" const program = Effect.gen(function* () { const mutex = yield* Effect.makeSemaphore(5) const tasks = [1, 2, 3, 4, 5].map((n) => mutex .withPermits(n)( Effect.delay(Effect.log(`process: ${n}`), "2 seconds") ) .pipe(Effect.withLogSpan("elapsed")) ) yield* Effect.all(tasks, { concurrency: "unbounded" }) }) Effect.runFork(program) /* Output: timestamp=... level=INFO fiber=#1 message="process: 1" elapsed=2011ms timestamp=... level=INFO fiber=#2 message="process: 2" elapsed=2017ms timestamp=... level=INFO fiber=#3 message="process: 3" elapsed=4020ms timestamp=... level=INFO fiber=#4 message="process: 4" elapsed=6025ms timestamp=... level=INFO fiber=#5 message="process: 5" elapsed=8034ms */ ```
# [BigDecimal](https://effect.website/docs/data-types/bigdecimal/)
## Overview import { Aside } from "@astrojs/starlight/components" In JavaScript, numbers are typically stored as 64-bit floating-point values. While floating-point numbers are fast and versatile, they can introduce small rounding errors. These are often hard to notice in everyday usage but can become problematic in areas like finance or statistics, where small inaccuracies may lead to larger discrepancies over time. By using the BigDecimal module, you can avoid these issues and perform calculations with a higher degree of precision. The `BigDecimal` data type can represent real numbers with a large number of decimal places, preventing the common errors of floating-point math (for example, 0.1 + 0.2 ≠ 0.3). ## How BigDecimal Works A `BigDecimal` represents a number using two components: 1. `value`: A `BigInt` that stores the digits of the number. 2. `scale`: A 64-bit integer that determines the position of the decimal point. The number represented by a `BigDecimal` is calculated as: value x 10-scale. - If `scale` is zero or positive, it specifies the number of digits to the right of the decimal point. - If `scale` is negative, the `value` is multiplied by 10 raised to the power of the negated scale. For example: - A `BigDecimal` with `value = 12345n` and `scale = 2` represents `123.45`. - A `BigDecimal` with `value = 12345n` and `scale = -2` represents `1234500`. The maximum precision is large but not infinite, limited to 263 decimal places. ## Creating a BigDecimal ### make The `make` function creates a `BigDecimal` by specifying a `BigInt` value and a scale. The `scale` determines the number of digits to the right of the decimal point. **Example** (Creating a BigDecimal with a Specified Scale) ```ts twoslash import { BigDecimal } from "effect" // Create a BigDecimal from a BigInt (1n) with a scale of 2 const decimal = BigDecimal.make(1n, 2) console.log(decimal) // Output: { _id: 'BigDecimal', value: '1', scale: 2 } // Convert the BigDecimal to a string console.log(String(decimal)) // Output: BigDecimal(0.01) // Format the BigDecimal as a standard decimal string console.log(BigDecimal.format(decimal)) // Output: 0.01 // Convert the BigDecimal to exponential notation console.log(BigDecimal.toExponential(decimal)) // Output: 1e-2 ``` ### fromBigInt The `fromBigInt` function creates a `BigDecimal` from a `bigint`. The `scale` defaults to `0`, meaning the number has no fractional part. **Example** (Creating a BigDecimal from a BigInt) ```ts twoslash import { BigDecimal } from "effect" const decimal = BigDecimal.fromBigInt(10n) console.log(decimal) // Output: { _id: 'BigDecimal', value: '10', scale: 0 } ``` ### fromString Parses a numerical string into a `BigDecimal`. Returns an `Option`: - `Some(BigDecimal)` if the string is valid. - `None` if the string is invalid. **Example** (Parsing a String into a BigDecimal) ```ts twoslash import { BigDecimal } from "effect" const decimal = BigDecimal.fromString("0.02") console.log(decimal) /* Output: { _id: 'Option', _tag: 'Some', value: { _id: 'BigDecimal', value: '2', scale: 2 } } */ ``` ### unsafeFromString The `unsafeFromString` function is a variant of `fromString` that throws an error if the input string is invalid. Use this only when you are confident that the input will always be valid. **Example** (Unsafe Parsing of a String) ```ts twoslash import { BigDecimal } from "effect" const decimal = BigDecimal.unsafeFromString("0.02") console.log(decimal) // Output: { _id: 'BigDecimal', value: '2', scale: 2 } ``` ### unsafeFromNumber Creates a `BigDecimal` from a JavaScript `number`. Throws a `RangeError` for non-finite numbers (`NaN`, `+Infinity`, or `-Infinity`). **Example** (Unsafe Parsing of a Number) ```ts twoslash import { BigDecimal } from "effect" console.log(BigDecimal.unsafeFromNumber(123.456)) // Output: { _id: 'BigDecimal', value: '123456', scale: 3 } ``` ## Basic Arithmetic Operations The BigDecimal module supports a variety of arithmetic operations that provide precision and avoid the rounding errors common in standard JavaScript arithmetic. Below is a list of supported operations: | Function | Description | | ----------------- | -------------------------------------------------------------------------------------------------------------- | | `sum` | Adds two `BigDecimal` values. | | `subtract` | Subtracts one `BigDecimal` value from another. | | `multiply` | Multiplies two `BigDecimal` values. | | `divide` | Divides one `BigDecimal` value by another, returning an `Option`. | | `unsafeDivide` | Divides one `BigDecimal` value by another, throwing an error if the divisor is zero. | | `negate` | Negates a `BigDecimal` value (i.e., changes its sign). | | `remainder` | Returns the remainder of dividing one `BigDecimal` value by another, returning an `Option`. | | `unsafeRemainder` | Returns the remainder of dividing one `BigDecimal` value by another, throwing an error if the divisor is zero. | | `sign` | Returns the sign of a `BigDecimal` value (`-1`, `0`, or `1`). | | `abs` | Returns the absolute value of a `BigDecimal`. | **Example** (Performing Basic Arithmetic with BigDecimal) ```ts twoslash import { BigDecimal } from "effect" const dec1 = BigDecimal.unsafeFromString("1.05") const dec2 = BigDecimal.unsafeFromString("2.10") // Addition console.log(String(BigDecimal.sum(dec1, dec2))) // Output: BigDecimal(3.15) // Multiplication console.log(String(BigDecimal.multiply(dec1, dec2))) // Output: BigDecimal(2.205) // Subtraction console.log(String(BigDecimal.subtract(dec2, dec1))) // Output: BigDecimal(1.05) // Division (safe, returns Option) console.log(BigDecimal.divide(dec2, dec1)) /* Output: { _id: 'Option', _tag: 'Some', value: { _id: 'BigDecimal', value: '2', scale: 0 } } */ // Division (unsafe, throws if divisor is zero) console.log(String(BigDecimal.unsafeDivide(dec2, dec1))) // Output: BigDecimal(2) // Negation console.log(String(BigDecimal.negate(dec1))) // Output: BigDecimal(-1.05) // Modulus (unsafe, throws if divisor is zero) console.log( String( BigDecimal.unsafeRemainder(dec2, BigDecimal.unsafeFromString("0.6")) ) ) // Output: BigDecimal(0.3) ``` Using `BigDecimal` for arithmetic operations helps to avoid the inaccuracies commonly encountered with floating-point numbers in JavaScript. For example: **Example** (Avoiding Floating-Point Errors) ```ts twoslash const dec1 = 1.05 const dec2 = 2.1 console.log(String(dec1 + dec2)) // Output: 3.1500000000000004 ``` ## Comparison Operations The `BigDecimal` module provides several functions for comparing decimal values. These allow you to determine the relative order of two values, find the minimum or maximum, and check specific properties like positivity or integer status. ### Comparison Functions | Function | Description | | ---------------------- | ------------------------------------------------------------------------ | | `lessThan` | Checks if the first `BigDecimal` is smaller than the second. | | `lessThanOrEqualTo` | Checks if the first `BigDecimal` is smaller than or equal to the second. | | `greaterThan` | Checks if the first `BigDecimal` is larger than the second. | | `greaterThanOrEqualTo` | Checks if the first `BigDecimal` is larger than or equal to the second. | | `min` | Returns the smaller of two `BigDecimal` values. | | `max` | Returns the larger of two `BigDecimal` values. | **Example** (Comparing Two BigDecimal Values) ```ts twoslash import { BigDecimal } from "effect" const dec1 = BigDecimal.unsafeFromString("1.05") const dec2 = BigDecimal.unsafeFromString("2.10") console.log(BigDecimal.lessThan(dec1, dec2)) // Output: true console.log(BigDecimal.lessThanOrEqualTo(dec1, dec2)) // Output: true console.log(BigDecimal.greaterThan(dec1, dec2)) // Output: false console.log(BigDecimal.greaterThanOrEqualTo(dec1, dec2)) // Output: false console.log(BigDecimal.min(dec1, dec2)) // Output: { _id: 'BigDecimal', value: '105', scale: 2 } console.log(BigDecimal.max(dec1, dec2)) // Output: { _id: 'BigDecimal', value: '210', scale: 2 } ``` ### Predicates for Comparison The module also includes predicates to check specific properties of a `BigDecimal`: | Predicate | Description | | ------------ | -------------------------------------------------------------- | | `isZero` | Checks if the value is exactly zero. | | `isPositive` | Checks if the value is positive. | | `isNegative` | Checks if the value is negative. | | `between` | Checks if the value lies within a specified range (inclusive). | | `isInteger` | Checks if the value is an integer (i.e., no fractional part). | **Example** (Checking the Sign and Properties of BigDecimal Values) ```ts twoslash import { BigDecimal } from "effect" const dec1 = BigDecimal.unsafeFromString("1.05") const dec2 = BigDecimal.unsafeFromString("-2.10") console.log(BigDecimal.isZero(BigDecimal.unsafeFromString("0"))) // Output: true console.log(BigDecimal.isPositive(dec1)) // Output: true console.log(BigDecimal.isNegative(dec2)) // Output: true console.log( BigDecimal.between({ minimum: BigDecimal.unsafeFromString("1"), maximum: BigDecimal.unsafeFromString("2") })(dec1) ) // Output: true console.log( BigDecimal.isInteger(dec2), BigDecimal.isInteger(BigDecimal.fromBigInt(3n)) ) // Output: false true ``` ## Normalization and Equality In some cases, two `BigDecimal` values can have different internal representations but still represent the same number. For example, `1.05` could be internally represented with different scales, such as: - `105n` with a scale of `2` - `1050n` with a scale of `3` To ensure consistency, you can normalize a `BigDecimal` to adjust the scale and remove trailing zeros. ### Normalization The `BigDecimal.normalize` function adjusts the scale of a `BigDecimal` and eliminates any unnecessary trailing zeros in its internal representation. **Example** (Normalizing a BigDecimal) ```ts twoslash import { BigDecimal } from "effect" const dec = BigDecimal.make(1050n, 3) console.log(BigDecimal.normalize(dec)) // Output: { _id: 'BigDecimal', value: '105', scale: 2 } ``` ### Equality To check if two `BigDecimal` values are numerically equal, regardless of their internal representation, use the `BigDecimal.equals` function. **Example** (Checking Equality) ```ts twoslash import { BigDecimal } from "effect" const dec1 = BigDecimal.make(105n, 2) const dec2 = BigDecimal.make(1050n, 3) console.log(BigDecimal.equals(dec1, dec2)) // Output: true ```
# [Cause](https://effect.website/docs/data-types/cause/)
## Overview The [`Effect`](/docs/getting-started/the-effect-type/) type is polymorphic in error type `E`, allowing flexibility in handling any desired error type. However, there is often additional information about failures that the error type `E` alone does not capture. To address this, Effect uses the `Cause` data type to store various details such as: - Unexpected errors or defects - Stack and execution traces - Reasons for fiber interruptions Effect strictly preserves all failure-related information, storing a full picture of the error context in the `Cause` type. This comprehensive approach enables precise analysis and handling of failures, ensuring no data is lost. Though `Cause` values aren't typically manipulated directly, they underlie errors within Effect workflows, providing access to both concurrent and sequential error details. This allows for thorough error analysis when needed. ## Creating Causes You can intentionally create an effect with a specific cause using `Effect.failCause`. **Example** (Defining Effects with Different Causes) ```ts twoslash import { Effect, Cause } from "effect" // Define an effect that dies with an unexpected error // // ┌─── Effect // ▼ const die = Effect.failCause(Cause.die("Boom!")) // Define an effect that fails with an expected error // // ┌─── Effect // ▼ const fail = Effect.failCause(Cause.fail("Oh no!")) ``` Some causes do not influence the error type of the effect, leading to `never` in the error channel: ```text showLineNumbers=false ┌─── no error information ▼ Effect ``` For instance, `Cause.die` does not specify an error type for the effect, while `Cause.fail` does, setting the error channel type accordingly. ## Cause Variations There are several causes for various errors, in this section, we will describe each of these causes. ### Empty The `Empty` cause signifies the absence of any errors. ### Fail The `Fail` cause represents a failure due to an expected error of type `E`. ### Die The `Die` cause indicates a failure resulting from a defect, which is an unexpected or unintended error. ### Interrupt The `Interrupt` cause represents a failure due to `Fiber` interruption and contains the `FiberId` of the interrupted `Fiber`. ### Sequential The `Sequential` cause combines two causes that occurred one after the other. For example, in an `Effect.ensuring` operation (analogous to `try-finally`), if both the `try` and `finally` sections fail, the two errors are represented in sequence by a `Sequential` cause. **Example** (Capturing Sequential Failures with a `Sequential` Cause) ```ts twoslash import { Effect, Cause } from "effect" const program = Effect.failCause(Cause.fail("Oh no!")).pipe( Effect.ensuring(Effect.failCause(Cause.die("Boom!"))) ) Effect.runPromiseExit(program).then(console.log) /* Output: { _id: 'Exit', _tag: 'Failure', cause: { _id: 'Cause', _tag: 'Sequential', left: { _id: 'Cause', _tag: 'Fail', failure: 'Oh no!' }, right: { _id: 'Cause', _tag: 'Die', defect: 'Boom!' } } } */ ``` ### Parallel The `Parallel` cause combines two causes that occurred concurrently. In Effect programs, two operations may run in parallel, potentially leading to multiple failures. When both computations fail simultaneously, a `Parallel` cause represents the concurrent errors within the effect workflow. **Example** (Capturing Concurrent Failures with a `Parallel` Cause) ```ts twoslash import { Effect, Cause } from "effect" const program = Effect.all( [ Effect.failCause(Cause.fail("Oh no!")), Effect.failCause(Cause.die("Boom!")) ], { concurrency: 2 } ) Effect.runPromiseExit(program).then(console.log) /* Output: { _id: 'Exit', _tag: 'Failure', cause: { _id: 'Cause', _tag: 'Parallel', left: { _id: 'Cause', _tag: 'Fail', failure: 'Oh no!' }, right: { _id: 'Cause', _tag: 'Die', defect: 'Boom!' } } } */ ``` ## Retrieving the Cause of an Effect To retrieve the cause of a failed effect, use `Effect.cause`. This allows you to inspect or handle the exact reason behind the failure. **Example** (Retrieving and Inspecting a Failure Cause) ```ts twoslash import { Effect } from "effect" const program = Effect.gen(function* () { const cause = yield* Effect.cause(Effect.fail("Oh no!")) console.log(cause) }) Effect.runPromise(program) /* Output: { _id: 'Cause', _tag: 'Fail', failure: 'Oh no!' } */ ``` ## Guards To determine the specific type of a `Cause`, use the guards provided in the Cause module: - `Cause.isEmpty`: Checks if the cause is empty, indicating no error. - `Cause.isFailType`: Identifies causes that represent an expected failure. - `Cause.isDie`: Identifies causes that represent an unexpected defect. - `Cause.isInterruptType`: Identifies causes related to fiber interruptions. - `Cause.isSequentialType`: Checks if the cause consists of sequential errors. - `Cause.isParallelType`: Checks if the cause contains parallel errors. **Example** (Using Guards to Identify Cause Types) ```ts twoslash import { Cause } from "effect" const cause = Cause.fail(new Error("my message")) if (Cause.isFailType(cause)) { console.log(cause.error.message) // Output: my message } ``` These guards allow you to accurately identify the type of a `Cause`, making it easier to handle various error cases in your code. Whether dealing with expected failures, unexpected defects, interruptions, or composite errors, these guards provide a clear method for assessing and managing error scenarios. ## Pattern Matching The `Cause.match` function provides a straightforward way to handle each case of a `Cause`. By defining callbacks for each possible cause type, you can respond to specific error scenarios with custom behavior. **Example** (Pattern Matching on Different Causes) ```ts twoslash import { Cause } from "effect" const cause = Cause.parallel( Cause.fail(new Error("my fail message")), Cause.die("my die message") ) console.log( Cause.match(cause, { onEmpty: "(empty)", onFail: (error) => `(error: ${error.message})`, onDie: (defect) => `(defect: ${defect})`, onInterrupt: (fiberId) => `(fiberId: ${fiberId})`, onSequential: (left, right) => `(onSequential (left: ${left}) (right: ${right}))`, onParallel: (left, right) => `(onParallel (left: ${left}) (right: ${right})` }) ) /* Output: (onParallel (left: (error: my fail message)) (right: (defect: my die message)) */ ``` ## Pretty Printing Clear and readable error messages are key for effective debugging. The `Cause.pretty` function helps by formatting error messages in a structured way, making it easier to understand failure details. **Example** (Using `Cause.pretty` for Readable Error Messages) ```ts twoslash import { Cause, FiberId } from "effect" console.log(Cause.pretty(Cause.empty)) /* Output: All fibers interrupted without errors. */ console.log(Cause.pretty(Cause.fail(new Error("my fail message")))) /* Output: Error: my fail message ...stack trace... */ console.log(Cause.pretty(Cause.die("my die message"))) /* Output: Error: my die message */ console.log(Cause.pretty(Cause.interrupt(FiberId.make(1, 0)))) /* Output: All fibers interrupted without errors. */ console.log( Cause.pretty(Cause.sequential(Cause.fail("fail1"), Cause.fail("fail2"))) ) /* Output: Error: fail1 Error: fail2 */ ``` ## Retrieval of Failures and Defects To specifically collect failures or defects from a `Cause`, you can use `Cause.failures` and `Cause.defects`. These functions allow you to inspect only the errors or unexpected defects that occurred. **Example** (Extracting Failures and Defects from a Cause) ```ts twoslash import { Effect, Cause } from "effect" const program = Effect.gen(function* () { const cause = yield* Effect.cause( Effect.all([ Effect.fail("error 1"), Effect.die("defect"), Effect.fail("error 2") ]) ) console.log(Cause.failures(cause)) console.log(Cause.defects(cause)) }) Effect.runPromise(program) /* Output: { _id: 'Chunk', values: [ 'error 1' ] } { _id: 'Chunk', values: [] } */ ```
# [Chunk](https://effect.website/docs/data-types/chunk/)
## Overview import { Aside } from "@astrojs/starlight/components" A `Chunk` represents an ordered, immutable collection of values of type `A`. While similar to an array, `Chunk` provides a functional interface, optimizing certain operations that can be costly with regular arrays, like repeated concatenation. ## Why Use Chunk? - **Immutability**: Unlike standard JavaScript arrays, which are mutable, `Chunk` provides a truly immutable collection, preventing data from being modified after creation. This is especially useful in concurrent programming contexts where immutability can enhance data consistency. - **High Performance**: `Chunk` supports specialized operations for efficient array manipulation, such as appending single elements or concatenating chunks, making these operations faster than their regular JavaScript array equivalents. ## Creating a Chunk ### empty Create an empty `Chunk` with `Chunk.empty`. **Example** (Creating an Empty Chunk) ```ts twoslash import { Chunk } from "effect" // ┌─── Chunk // ▼ const chunk = Chunk.empty() ``` ### make To create a `Chunk` with specific values, use `Chunk.make(...values)`. Note that the resulting chunk is typed as non-empty. **Example** (Creating a Non-Empty Chunk) ```ts twoslash import { Chunk } from "effect" // ┌─── NonEmptyChunk // ▼ const chunk = Chunk.make(1, 2, 3) ``` ### fromIterable You can create a `Chunk` by providing a collection, either from an iterable or directly from an array. **Example** (Creating a Chunk from an Iterable) ```ts twoslash import { Chunk, List } from "effect" const fromArray = Chunk.fromIterable([1, 2, 3]) const fromList = Chunk.fromIterable(List.make(1, 2, 3)) ``` ### unsafeFromArray `Chunk.unsafeFromArray` creates a `Chunk` directly from an array without cloning. This approach can improve performance by avoiding the overhead of copying data but requires caution, as it bypasses the usual immutability guarantees. **Example** (Directly Creating a Chunk from an Array) ```ts twoslash import { Chunk } from "effect" const chunk = Chunk.unsafeFromArray([1, 2, 3]) ``` ## Concatenating To combine two `Chunk` instances into one, use `Chunk.appendAll`. **Example** (Combining Two Chunks into One) ```ts twoslash import { Chunk } from "effect" // Concatenate two chunks with different types of elements // // ┌─── NonEmptyChunk // ▼ const chunk = Chunk.appendAll(Chunk.make(1, 2), Chunk.make("a", "b")) console.log(chunk) /* Output: { _id: 'Chunk', values: [ 1, 2, 'a', 'b' ] } */ ``` ## Dropping To remove elements from the beginning of a `Chunk`, use `Chunk.drop`, specifying the number of elements to discard. **Example** (Dropping Elements from the Start) ```ts twoslash import { Chunk } from "effect" // Drops the first 2 elements from the Chunk const chunk = Chunk.drop(Chunk.make(1, 2, 3, 4), 2) ``` ## Comparing To check if two `Chunk` instances are equal, use [`Equal.equals`](/docs/trait/equal/). This function compares the contents of each `Chunk` for structural equality. **Example** (Comparing Two Chunks) ```ts twoslash import { Chunk, Equal } from "effect" const chunk1 = Chunk.make(1, 2) const chunk2 = Chunk.make(1, 2, 3) console.log(Equal.equals(chunk1, chunk1)) // Output: true console.log(Equal.equals(chunk1, chunk2)) // Output: false console.log(Equal.equals(chunk1, Chunk.make(1, 2))) // Output: true ``` ## Converting Convert a `Chunk` to a `ReadonlyArray` using `Chunk.toReadonlyArray`. The resulting type varies based on the `Chunk`'s contents, distinguishing between empty, non-empty, and generic chunks. **Example** (Converting a Chunk to a ReadonlyArray) ```ts twoslash import { Chunk } from "effect" // ┌─── readonly [number, ...number[]] // ▼ const nonEmptyArray = Chunk.toReadonlyArray(Chunk.make(1, 2, 3)) // ┌─── readonly never[] // ▼ const emptyArray = Chunk.toReadonlyArray(Chunk.empty()) declare const chunk: Chunk.Chunk // ┌─── readonly number[] // ▼ const array = Chunk.toReadonlyArray(chunk) ```
# [Data](https://effect.website/docs/data-types/data/)
## Overview import { Aside } from "@astrojs/starlight/components" The Data module simplifies creating and handling data structures in TypeScript. It provides tools for **defining data types**, ensuring **equality** between objects, and **hashing** data for efficient comparisons. ## Value Equality The Data module provides constructors for creating data types with built-in support for equality and hashing, eliminating the need for custom implementations. This means that two values created using these constructors are considered equal if they have the same structure and values. ### struct In plain JavaScript, objects are considered equal only if they refer to the exact same instance. **Example** (Comparing Two Objects in Plain JavaScript) ```ts twoslash const alice = { name: "Alice", age: 30 } // This comparison is false because they are different instances // @ts-expect-error console.log(alice === { name: "Alice", age: 30 }) // Output: false ``` However, the `Data.struct` constructor allows you to compare values based on their structure and content. **Example** (Creating and Checking Equality of Structs) ```ts twoslash import { Data, Equal } from "effect" // ┌─── { readonly name: string; readonly age: number; } // ▼ const alice = Data.struct({ name: "Alice", age: 30 }) // Check if Alice is equal to a new object // with the same structure and values console.log(Equal.equals(alice, Data.struct({ name: "Alice", age: 30 }))) // Output: true // Check if Alice is equal to a plain JavaScript object // with the same content console.log(Equal.equals(alice, { name: "Alice", age: 30 })) // Output: false ``` The comparison performed by `Equal.equals` is **shallow**, meaning nested objects are not compared recursively unless they are also created using `Data.struct`. **Example** (Shallow Comparison with Nested Objects) ```ts twoslash import { Data, Equal } from "effect" const nested = Data.struct({ name: "Alice", nested_field: { value: 42 } }) // This will be false because the nested objects are compared by reference console.log( Equal.equals( nested, Data.struct({ name: "Alice", nested_field: { value: 42 } }) ) ) // Output: false ``` To ensure nested objects are compared by structure, use `Data.struct` for them as well. **Example** (Correctly Comparing Nested Objects) ```ts twoslash import { Data, Equal } from "effect" const nested = Data.struct({ name: "Alice", nested_field: Data.struct({ value: 42 }) }) // Now, the comparison returns true console.log( Equal.equals( nested, Data.struct({ name: "Alice", nested_field: Data.struct({ value: 42 }) }) ) ) // Output: true ``` ### tuple To represent your data using tuples, you can use the `Data.tuple` constructor. This ensures that your tuples can be compared structurally. **Example** (Creating and Checking Equality of Tuples) ```ts twoslash import { Data, Equal } from "effect" // ┌─── readonly [string, number] // ▼ const alice = Data.tuple("Alice", 30) // Check if Alice is equal to a new tuple // with the same structure and values console.log(Equal.equals(alice, Data.tuple("Alice", 30))) // Output: true // Check if Alice is equal to a plain JavaScript tuple // with the same content console.log(Equal.equals(alice, ["Alice", 30])) // Output: false ``` ### array You can use `Data.array` to create an array-like data structure that supports structural equality. **Example** (Creating and Checking Equality of Arrays) ```ts twoslash import { Data, Equal } from "effect" // ┌─── readonly number[] // ▼ const numbers = Data.array([1, 2, 3, 4, 5]) // Check if the array is equal to a new array // with the same values console.log(Equal.equals(numbers, Data.array([1, 2, 3, 4, 5]))) // Output: true // Check if the array is equal to a plain JavaScript array // with the same content console.log(Equal.equals(numbers, [1, 2, 3, 4, 5])) // Output: false ``` ## Constructors The module introduces a concept known as "Case classes", which automate various essential operations when defining data types. These operations include generating **constructors**, handling **equality** checks, and managing **hashing**. Case classes can be defined in two primary ways: - as plain objects using `case` or `tagged` - as TypeScript classes using `Class` or `TaggedClass` ### case The `Data.case` helper generates constructors and built-in support for equality checks and hashing for your data type. **Example** (Defining a Case Class and Checking Equality) In this example, `Data.case` is used to create a constructor for `Person`. The resulting instances have built-in support for equality checks, allowing you to compare them directly using `Equal.equals`. ```ts twoslash import { Data, Equal } from "effect" interface Person { readonly name: string } // Create a constructor for `Person` // // ┌─── (args: { readonly name: string; }) => Person // ▼ const make = Data.case() const alice = make({ name: "Alice" }) console.log(Equal.equals(alice, make({ name: "Alice" }))) // Output: true console.log(Equal.equals(alice, make({ name: "John" }))) // Output: false ``` **Example** (Defining and Comparing Nested Case Classes) This example demonstrates using `Data.case` to create nested data structures, such as a `Person` type containing an `Address`. Both `Person` and `Address` constructors support equality checks. ```ts twoslash import { Data, Equal } from "effect" interface Address { readonly street: string readonly city: string } // Create a constructor for `Address` const Address = Data.case() interface Person { readonly name: string readonly address: Address } // Create a constructor for `Person` const Person = Data.case() const alice = Person({ name: "Alice", address: Address({ street: "123 Main St", city: "Wonderland" }) }) const anotherAlice = Person({ name: "Alice", address: Address({ street: "123 Main St", city: "Wonderland" }) }) console.log(Equal.equals(alice, anotherAlice)) // Output: true ``` Alternatively, you can use `Data.struct` to create nested data structures without defining a separate `Address` constructor. **Example** (Using `Data.struct` for Nested Objects) ```ts twoslash import { Data, Equal } from "effect" interface Person { readonly name: string readonly address: { readonly street: string readonly city: string } } // Create a constructor for `Person` const Person = Data.case() const alice = Person({ name: "Alice", address: Data.struct({ street: "123 Main St", city: "Wonderland" }) }) const anotherAlice = Person({ name: "Alice", address: Data.struct({ street: "123 Main St", city: "Wonderland" }) }) console.log(Equal.equals(alice, anotherAlice)) // Output: true ``` **Example** (Defining and Comparing Recursive Case Classes) This example demonstrates a recursive structure using `Data.case` to define a binary tree where each node can contain other nodes. ```ts twoslash import { Data, Equal } from "effect" interface BinaryTree { readonly value: T readonly left: BinaryTree | null readonly right: BinaryTree | null } // Create a constructor for `BinaryTree` const BinaryTree = Data.case>() const tree1 = BinaryTree({ value: 0, left: BinaryTree({ value: 1, left: null, right: null }), right: null }) const tree2 = BinaryTree({ value: 0, left: BinaryTree({ value: 1, left: null, right: null }), right: null }) console.log(Equal.equals(tree1, tree2)) // Output: true ``` ### tagged When you're working with a data type that includes a tag field, like in disjoint union types, defining the tag manually for each instance can get repetitive. Using the `case` approach requires you to specify the tag field every time, which can be cumbersome. **Example** (Defining a Tagged Case Class Manually) Here, we create a `Person` type with a `_tag` field using `Data.case`. Notice that the `_tag` needs to be specified for every new instance. ```ts twoslash import { Data } from "effect" interface Person { readonly _tag: "Person" // the tag readonly name: string } const Person = Data.case() // Repeating `_tag: 'Person'` for each instance const alice = Person({ _tag: "Person", name: "Alice" }) const bob = Person({ _tag: "Person", name: "Bob" }) ``` To streamline this process, the `Data.tagged` helper automatically adds the tag. It follows the convention in the Effect ecosystem of naming the tag field as `"_tag"`. **Example** (Using Data.tagged to Simplify Tagging) The `Data.tagged` helper allows you to define the tag just once, making instance creation simpler. ```ts twoslash import { Data } from "effect" interface Person { readonly _tag: "Person" // the tag readonly name: string } const Person = Data.tagged("Person") // The `_tag` field is automatically added const alice = Person({ name: "Alice" }) const bob = Person({ name: "Bob" }) console.log(alice) // Output: { name: 'Alice', _tag: 'Person' } ``` ### Class If you prefer working with classes instead of plain objects, you can use `Data.Class` as an alternative to `Data.case`. This approach may feel more natural in scenarios where you want a class-oriented structure, complete with methods and custom logic. **Example** (Using Data.Class for a Class-Oriented Structure) Here's how to define a `Person` class using `Data.Class`: ```ts twoslash import { Data, Equal } from "effect" // Define a Person class extending Data.Class class Person extends Data.Class<{ name: string }> {} // Create an instance of Person const alice = new Person({ name: "Alice" }) // Check for equality between two instances console.log(Equal.equals(alice, new Person({ name: "Alice" }))) // Output: true ``` One of the benefits of using classes is that you can easily add custom methods and getters. This allows you to extend the functionality of your data types. **Example** (Adding Custom Getters to a Class) In this example, we add a `upperName` getter to the `Person` class to return the name in uppercase: ```ts twoslash import { Data } from "effect" // Extend Person class with a custom getter class Person extends Data.Class<{ name: string }> { get upperName() { return this.name.toUpperCase() } } // Create an instance and use the custom getter const alice = new Person({ name: "Alice" }) console.log(alice.upperName) // Output: ALICE ``` ### TaggedClass If you prefer a class-based approach but also want the benefits of tagging for disjoint unions, `Data.TaggedClass` can be a helpful option. It works similarly to `tagged` but is tailored for class definitions. **Example** (Defining a Tagged Class with Built-In Tagging) Here's how to define a `Person` class using `Data.TaggedClass`. Notice that the tag `"Person"` is automatically added: ```ts twoslash import { Data, Equal } from "effect" // Define a tagged class Person with the _tag "Person" class Person extends Data.TaggedClass("Person")<{ name: string }> {} // Create an instance of Person const alice = new Person({ name: "Alice" }) console.log(alice) // Output: Person { name: 'Alice', _tag: 'Person' } // Check equality between two instances console.log(Equal.equals(alice, new Person({ name: "Alice" }))) // Output: true ``` One benefit of using tagged classes is the ability to easily add custom methods and getters, extending the class's functionality as needed. **Example** (Adding Custom Getters to a Tagged Class) In this example, we add a `upperName` getter to the `Person` class, which returns the name in uppercase: ```ts twoslash import { Data } from "effect" // Extend the Person class with a custom getter class Person extends Data.TaggedClass("Person")<{ name: string }> { get upperName() { return this.name.toUpperCase() } } // Create an instance and use the custom getter const alice = new Person({ name: "Alice" }) console.log(alice.upperName) // Output: ALICE ``` ## Union of Tagged Structs To create a disjoint union of tagged structs, you can use `Data.TaggedEnum` and `Data.taggedEnum`. These utilities make it straightforward to define and work with unions of plain objects. ### Definition The type passed to `Data.TaggedEnum` must be an object where the keys represent the tags, and the values define the structure of the corresponding data types. **Example** (Defining a Tagged Union and Checking Equality) ```ts twoslash import { Data, Equal } from "effect" // Define a union type using TaggedEnum type RemoteData = Data.TaggedEnum<{ Loading: {} Success: { readonly data: string } Failure: { readonly reason: string } }> // Create constructors for each case in the union const { Loading, Success, Failure } = Data.taggedEnum() // Instantiate different states const state1 = Loading() const state2 = Success({ data: "test" }) const state3 = Success({ data: "test" }) const state4 = Failure({ reason: "not found" }) // Check equality between states console.log(Equal.equals(state2, state3)) // Output: true console.log(Equal.equals(state2, state4)) // Output: false // Display the states console.log(state1) // Output: { _tag: 'Loading' } console.log(state2) // Output: { data: 'test', _tag: 'Success' } console.log(state4) // Output: { reason: 'not found', _tag: 'Failure' } ``` ### $is and $match The `Data.taggedEnum` provides `$is` and `$match` functions for convenient type guarding and pattern matching. **Example** (Using Type Guards and Pattern Matching) ```ts twoslash import { Data } from "effect" type RemoteData = Data.TaggedEnum<{ Loading: {} Success: { readonly data: string } Failure: { readonly reason: string } }> const { $is, $match, Loading, Success } = Data.taggedEnum() // Use `$is` to create a type guard for "Loading" const isLoading = $is("Loading") console.log(isLoading(Loading())) // Output: true console.log(isLoading(Success({ data: "test" }))) // Output: false // Use `$match` for pattern matching const matcher = $match({ Loading: () => "this is a Loading", Success: ({ data }) => `this is a Success: ${data}`, Failure: ({ reason }) => `this is a Failre: ${reason}` }) console.log(matcher(Success({ data: "test" }))) // Output: "this is a Success: test" ``` ### Adding Generics You can create more flexible and reusable tagged unions by using `TaggedEnum.WithGenerics`. This approach allows you to define tagged unions that can handle different types dynamically. **Example** (Using Generics with TaggedEnum) ```ts twoslash import { Data } from "effect" // Define a generic TaggedEnum for RemoteData type RemoteData = Data.TaggedEnum<{ Loading: {} Success: { data: Success } Failure: { reason: Failure } }> // Extend TaggedEnum.WithGenerics to add generics interface RemoteDataDefinition extends Data.TaggedEnum.WithGenerics<2> { readonly taggedEnum: RemoteData } // Create constructors for the generic RemoteData const { Loading, Failure, Success } = Data.taggedEnum() // Instantiate each case with specific types const loading = Loading() const failure = Failure({ reason: "not found" }) const success = Success({ data: 1 }) ``` ## Errors In Effect, handling errors is simplified using specialized constructors: - `Error` - `TaggedError` These constructors make defining custom error types straightforward, while also providing useful integrations like equality checks and structured error handling. ### Error `Data.Error` lets you create an `Error` type with extra fields beyond the typical `message` property. **Example** (Creating a Custom Error with Additional Fields) ```ts twoslash import { Data } from "effect" // Define a custom error with additional fields class NotFound extends Data.Error<{ message: string; file: string }> {} // Create an instance of the custom error const err = new NotFound({ message: "Cannot find this file", file: "foo.txt" }) console.log(err instanceof Error) // Output: true console.log(err.file) // Output: foo.txt console.log(err) /* Output: NotFound [Error]: Cannot find this file file: 'foo.txt' ... stack trace ... */ ``` You can yield an instance of `NotFound` directly in an [Effect.gen](/docs/getting-started/using-generators/), without needing to use `Effect.fail`. **Example** (Yielding a Custom Error in `Effect.gen`) ```ts twoslash import { Data, Effect } from "effect" class NotFound extends Data.Error<{ message: string; file: string }> {} const program = Effect.gen(function* () { yield* new NotFound({ message: "Cannot find this file", file: "foo.txt" }) }) Effect.runPromise(program) /* throws: Error: Cannot find this file at ... { name: '(FiberFailure) Error', [Symbol(effect/Runtime/FiberFailure/Cause)]: { _tag: 'Fail', error: NotFound [Error]: Cannot find this file at ...stack trace... file: 'foo.txt' } } } */ ``` ### TaggedError Effect provides a `TaggedError` API to add a `_tag` field automatically to your custom errors. This simplifies error handling with APIs like [Effect.catchTag](/docs/error-management/expected-errors/#catchtag) or [Effect.catchTags](/docs/error-management/expected-errors/#catchtags). ```ts twoslash import { Data, Effect, Console } from "effect" // Define a custom tagged error class NotFound extends Data.TaggedError("NotFound")<{ message: string file: string }> {} const program = Effect.gen(function* () { yield* new NotFound({ message: "Cannot find this file", file: "foo.txt" }) }).pipe( // Catch and handle the tagged error Effect.catchTag("NotFound", (err) => Console.error(`${err.message} (${err.file})`) ) ) Effect.runPromise(program) // Output: Cannot find this file (foo.txt) ``` ### Native Cause Support Errors created using `Data.Error` or `Data.TaggedError` can include a `cause` property, integrating with the native `cause` feature of JavaScript's `Error` for more detailed error tracing. **Example** (Using the `cause` Property) ```ts twoslash {22} import { Data, Effect } from "effect" // Define an error with a cause property class MyError extends Data.Error<{ cause: Error }> {} const program = Effect.gen(function* () { yield* new MyError({ cause: new Error("Something went wrong") }) }) Effect.runPromise(program) /* throws: Error: An error has occurred at ... { name: '(FiberFailure) Error', [Symbol(effect/Runtime/FiberFailure/Cause)]: { _tag: 'Fail', error: MyError at ... [cause]: Error: Something went wrong at ... */ ```
# [DateTime](https://effect.website/docs/data-types/datetime/)
## Overview import { Aside } from "@astrojs/starlight/components" Working with dates and times in JavaScript can be challenging. The built-in `Date` object mutates its internal state, and time zone handling can be confusing. These design choices can lead to errors when working on applications that rely on date-time accuracy, such as scheduling systems, timestamping services, or logging utilities. The DateTime module aims to address these limitations by offering: - **Immutable Data**: Each `DateTime` is an immutable structure, reducing mistakes related to in-place mutations. - **Time Zone Support**: `DateTime` provides robust support for time zones, including automatic daylight saving time adjustments. - **Arithmetic Operations**: You can perform arithmetic operations on `DateTime` instances, such as adding or subtracting durations. ## The DateTime Type A `DateTime` represents a moment in time. It can be stored as either a simple UTC value or as a value with an associated time zone. Storing time this way helps you manage both precise timestamps and the context for how that time should be displayed or interpreted. There are two main variants of `DateTime`: 1. **Utc**: An immutable structure that uses `epochMillis` (milliseconds since the Unix epoch) to represent a point in time in Coordinated Universal Time (UTC). 2. **Zoned**: Includes `epochMillis` along with a `TimeZone`, allowing you to attach an offset or a named region (like "America/New_York") to the timestamp. ### Why Have Two Variants? - **Utc** is straightforward if you only need a universal reference without relying on local time zones. - **Zoned** is helpful when you need to keep track of time zone information for tasks such as converting to local times or adjusting for daylight saving time. ### TimeZone Variants A `TimeZone` can be either: - **Offset**: Represents a fixed offset from UTC (for example, UTC+2 or UTC-5). - **Named**: Uses a named region (e.g., "Europe/London" or "America/New_York") that automatically accounts for region-specific rules like daylight saving time changes. ### TypeScript Definition Below is the TypeScript definition for the `DateTime` type: ```ts showLineNumbers=false type DateTime = Utc | Zoned interface Utc { readonly _tag: "Utc" readonly epochMillis: number } interface Zoned { readonly _tag: "Zoned" readonly epochMillis: number readonly zone: TimeZone } type TimeZone = TimeZone.Offset | TimeZone.Named declare namespace TimeZone { interface Offset { readonly _tag: "Offset" readonly offset: number } interface Named { readonly _tag: "Named" readonly id: string } } ``` ## The DateTime.Parts Type The `DateTime.Parts` type defines the main components of a date, such as the year, month, day, hours, minutes, and seconds. ```ts showLineNumbers=false namespace DateTime { interface Parts { readonly millis: number readonly seconds: number readonly minutes: number readonly hours: number readonly day: number readonly month: number readonly year: number } interface PartsWithWeekday extends Parts { readonly weekDay: number } } ``` ## The DateTime.Input Type The `DateTime.Input` type is a flexible input type that can be used to create a `DateTime` instance. It can be one of the following: - A `DateTime` instance - A JavaScript `Date` object - A numeric value representing milliseconds since the Unix epoch - An object with partial date [parts](#the-datetimeparts-type) (e.g., `{ year: 2024, month: 1, day: 1 }`) - A string that can be parsed by JavaScript's [Date.parse](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/parse) ```ts showLineNumbers=false namespace DateTime { type Input = DateTime | Partial | Date | number | string } ``` ## Utc Constructors `Utc` is an immutable structure that uses `epochMillis` (milliseconds since the Unix epoch) to represent a point in time in Coordinated Universal Time (UTC). ### unsafeFromDate Creates a `Utc` from a JavaScript `Date`. Throws an `IllegalArgumentException` if the provided `Date` is invalid. When a `Date` object is passed, it is converted to a `Utc` instance. The time is interpreted as the local time of the system executing the code and then adjusted to UTC. This ensures a consistent, timezone-independent representation of the date and time. **Example** (Converting Local Time to UTC in Italy) The following example assumes the code is executed on a system in Italy (CET timezone): ```ts twoslash import { DateTime } from "effect" // Create a Utc instance from a local JavaScript Date // // ┌─── Utc // ▼ const utc = DateTime.unsafeFromDate(new Date("2025-01-01 04:00:00")) console.log(utc) // Output: DateTime.Utc(2025-01-01T03:00:00.000Z) console.log(utc.epochMillis) // Output: 1735700400000 ``` **Explanation**: - The local time **2025-01-01 04:00:00** (in Italy, CET) is converted to **UTC** by subtracting the timezone offset (UTC+1 in January). - As a result, the UTC time becomes **2025-01-01 03:00:00.000Z**. - `epochMillis` provides the same time as milliseconds since the Unix Epoch, ensuring a precise numeric representation of the UTC timestamp. ### unsafeMake Creates a `Utc` from a [DateTime.Input](#the-datetimeinput-type). **Example** (Creating a DateTime with unsafeMake) The following example assumes the code is executed on a system in Italy (CET timezone): ```ts twoslash import { DateTime } from "effect" // From a JavaScript Date const utc1 = DateTime.unsafeMake(new Date("2025-01-01 04:00:00")) console.log(utc1) // Output: DateTime.Utc(2025-01-01T03:00:00.000Z) // From partial date parts const utc2 = DateTime.unsafeMake({ year: 2025 }) console.log(utc2) // Output: DateTime.Utc(2025-01-01T00:00:00.000Z) // From a string const utc3 = DateTime.unsafeMake("2025-01-01") console.log(utc3) // Output: DateTime.Utc(2025-01-01T00:00:00.000Z) ``` **Explanation**: - The local time **2025-01-01 04:00:00** (in Italy, CET) is converted to **UTC** by subtracting the timezone offset (UTC+1 in January). - As a result, the UTC time becomes **2025-01-01 03:00:00.000Z**. ### make Similar to [unsafeMake](#unsafemake), but returns an [Option](/docs/data-types/option/) instead of throwing an error if the input is invalid. If the input is invalid, it returns `None`. If valid, it returns `Some` containing the `Utc`. **Example** (Creating a DateTime Safely) The following example assumes the code is executed on a system in Italy (CET timezone): ```ts twoslash import { DateTime } from "effect" // From a JavaScript Date const maybeUtc1 = DateTime.make(new Date("2025-01-01 04:00:00")) console.log(maybeUtc1) /* Output: { _id: 'Option', _tag: 'Some', value: '2025-01-01T03:00:00.000Z' } */ // From partial date parts const maybeUtc2 = DateTime.make({ year: 2025 }) console.log(maybeUtc2) /* Output: { _id: 'Option', _tag: 'Some', value: '2025-01-01T00:00:00.000Z' } */ // From a string const maybeUtc3 = DateTime.make("2025-01-01") console.log(maybeUtc3) /* Output: { _id: 'Option', _tag: 'Some', value: '2025-01-01T00:00:00.000Z' } */ ``` **Explanation**: - The local time **2025-01-01 04:00:00** (in Italy, CET) is converted to **UTC** by subtracting the timezone offset (UTC+1 in January). - As a result, the UTC time becomes **2025-01-01 03:00:00.000Z**. ## Zoned Constructors A `Zoned` includes `epochMillis` along with a `TimeZone`, allowing you to attach an offset or a named region (like "America/New_York") to the timestamp. ### unsafeMakeZoned Creates a `Zoned` by combining a [DateTime.Input](#the-datetimeinput-type) with an optional `TimeZone`. This allows you to represent a specific point in time with an associated time zone. The time zone can be provided in several ways: - As a `TimeZone` object - A string identifier (e.g., `"Europe/London"`) - A numeric offset in milliseconds If the input or time zone is invalid, an `IllegalArgumentException` is thrown. **Example** (Creating a Zoned DateTime Without Specifying a Time Zone) The following example assumes the code is executed on a system in Italy (CET timezone): ```ts twoslash import { DateTime } from "effect" // Create a Zoned DateTime based on the system's local time zone const zoned = DateTime.unsafeMakeZoned(new Date("2025-01-01 04:00:00")) console.log(zoned) // Output: DateTime.Zoned(2025-01-01T04:00:00.000+01:00) console.log(zoned.zone) // Output: TimeZone.Offset(+01:00) ``` Here, the system's time zone (CET, which is UTC+1 in January) is used to create the `Zoned` instance. **Example** (Specifying a Named Time Zone) The following example assumes the code is executed on a system in Italy (CET timezone): ```ts twoslash import { DateTime } from "effect" // Create a Zoned DateTime with a specified named time zone const zoned = DateTime.unsafeMakeZoned(new Date("2025-01-01 04:00:00"), { timeZone: "Europe/Rome" }) console.log(zoned) // Output: DateTime.Zoned(2025-01-01T04:00:00.000+01:00[Europe/Rome]) console.log(zoned.zone) // Output: TimeZone.Named(Europe/Rome) ``` In this case, the `"Europe/Rome"` time zone is explicitly provided, resulting in the `Zoned` instance being tied to this named time zone. By default, the input date is treated as a UTC value and then adjusted for the specified time zone. To interpret the input date as being in the specified time zone, you can use the `adjustForTimeZone` option. **Example** (Adjusting for Time Zone Interpretation) The following example assumes the code is executed on a system in Italy (CET timezone): ```ts twoslash import { DateTime } from "effect" // Interpret the input date as being in the specified time zone const zoned = DateTime.unsafeMakeZoned(new Date("2025-01-01 04:00:00"), { timeZone: "Europe/Rome", adjustForTimeZone: true }) console.log(zoned) // Output: DateTime.Zoned(2025-01-01T03:00:00.000+01:00[Europe/Rome]) console.log(zoned.zone) // Output: TimeZone.Named(Europe/Rome) ``` **Explanation** - **Without `adjustForTimeZone`**: The input date is interpreted as UTC and then adjusted to the specified time zone. For instance, `2025-01-01 04:00:00` in UTC becomes `2025-01-01T04:00:00.000+01:00` in CET (UTC+1). - **With `adjustForTimeZone: true`**: The input date is interpreted as being in the specified time zone. For example, `2025-01-01 04:00:00` in "Europe/Rome" (CET) is adjusted to its corresponding UTC time, resulting in `2025-01-01T03:00:00.000+01:00`. ### makeZoned The `makeZoned` function works similarly to [unsafeMakeZoned](#unsafemakezoned) but provides a safer approach. Instead of throwing an error when the input is invalid, it returns an `Option`. If the input is invalid, it returns `None`. If valid, it returns `Some` containing the `Zoned`. **Example** (Safely Creating a Zoned DateTime) ```ts twoslash import { DateTime, Option } from "effect" // ┌─── Option // ▼ const zoned = DateTime.makeZoned(new Date("2025-01-01 04:00:00"), { timeZone: "Europe/Rome" }) if (Option.isSome(zoned)) { console.log("The DateTime is valid") } ``` ### makeZonedFromString Creates a `Zoned` by parsing a string in the format `YYYY-MM-DDTHH:mm:ss.sss+HH:MM[IANA timezone identifier]`. If the input string is valid, the function returns a `Some` containing the `Zoned`. If the input is invalid, it returns `None`. **Example** (Parsing a Zoned DateTime from a String) ```ts twoslash import { DateTime, Option } from "effect" // ┌─── Option // ▼ const zoned = DateTime.makeZonedFromString( "2025-01-01T03:00:00.000+01:00[Europe/Rome]" ) if (Option.isSome(zoned)) { console.log("The DateTime is valid") } ``` ## Current Time ### now Provides the current UTC time as a `Effect`, using the [Clock](/docs/requirements-management/default-services/) service. **Example** (Retrieving the Current UTC Time) ```ts twoslash import { DateTime, Effect } from "effect" const program = Effect.gen(function* () { // ┌─── Utc // ▼ const currentTime = yield* DateTime.now }) ``` ### unsafeNow Retrieves the current UTC time immediately using `Date.now()`, without the [Clock](/docs/requirements-management/default-services/) service. **Example** (Getting the Current UTC Time Immediately) ```ts twoslash import { DateTime } from "effect" // ┌─── Utc // ▼ const currentTime = DateTime.unsafeNow() ``` ## Guards | Function | Description | | ------------------ | ---------------------------------------------- | | `isDateTime` | Checks if a value is a `DateTime`. | | `isTimeZone` | Checks if a value is a `TimeZone`. | | `isTimeZoneOffset` | Checks if a value is a `TimeZone.Offset`. | | `isTimeZoneNamed` | Checks if a value is a `TimeZone.Named`. | | `isUtc` | Checks if a `DateTime` is the `Utc` variant. | | `isZoned` | Checks if a `DateTime` is the `Zoned` variant. | **Example** (Validating a DateTime) ```ts twoslash import { DateTime } from "effect" function printDateTimeInfo(x: unknown) { if (DateTime.isDateTime(x)) { console.log("This is a valid DateTime") } else { console.log("Not a DateTime") } } ``` ## Time Zone Management | Function | Description | | --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ | | `setZone` | Creates a `Zoned` from `DateTime` by applying the given `TimeZone`. | | `setZoneOffset` | Creates a `Zoned` from `DateTime` using a fixed offset (in ms). | | `setZoneNamed` | Creates a `Zoned` from `DateTime` from an IANA time zone identifier or returns `None` if invalid. | | `unsafeSetZoneNamed` | Creates a `Zoned` from `DateTime` from an IANA time zone identifier or throws if invalid. | | `zoneUnsafeMakeNamed` | Creates a `TimeZone.Named` from a IANA time zone identifier or throws if the identifier is invalid. | | `zoneMakeNamed` | Creates a `TimeZone.Named` from a IANA time zone identifier or returns `None` if invalid. | | `zoneMakeNamedEffect` | Creates a `Effect` from a IANA time zone identifier failing with `IllegalArgumentException` if invalid | | `zoneMakeOffset` | Creates a `TimeZone.Offset` from a numeric offset in milliseconds. | | `zoneMakeLocal` | Creates a `TimeZone.Named` from the system's local time zone. | | `zoneFromString` | Attempts to parse a time zone from a string, returning `None` if invalid. | | `zoneToString` | Returns a string representation of a `TimeZone`. | **Example** (Applying a Time Zone to a DateTime) ```ts twoslash import { DateTime } from "effect" // Create a UTC DateTime // // ┌─── Utc // ▼ const utc = DateTime.unsafeMake("2024-01-01") // Create a named time zone for New York // // ┌─── TimeZone.Named // ▼ const zoneNY = DateTime.zoneUnsafeMakeNamed("America/New_York") // Apply it to the DateTime // // ┌─── Zoned // ▼ const zoned = DateTime.setZone(utc, zoneNY) console.log(zoned) // Output: DateTime.Zoned(2023-12-31T19:00:00.000-05:00[America/New_York]) ``` ### zoneFromString Parses a string to create a `DateTime.TimeZone`. This function attempts to interpret the input string as either: - A numeric time zone offset (e.g., "GMT", "+01:00") - An IANA time zone identifier (e.g., "Europe/London") If the string matches an offset format, it is converted into a `TimeZone.Offset`. Otherwise, it attempts to create a `TimeZone.Named` using the input. If the input string is invalid, `Option.none()` is returned. **Example** (Parsing a Time Zone from a String) ```ts twoslash import { DateTime, Option } from "effect" // Attempt to parse a numeric offset const offsetZone = DateTime.zoneFromString("+01:00") console.log(Option.isSome(offsetZone)) // Output: true // Attempt to parse an IANA time zone const namedZone = DateTime.zoneFromString("Europe/London") console.log(Option.isSome(namedZone)) // Output: true // Invalid input const invalidZone = DateTime.zoneFromString("Invalid/Zone") console.log(Option.isSome(invalidZone)) // Output: false ``` ## Comparisons | Function | Description | | -------------------------------------------- | ------------------------------------------------------------ | | `distance` | Returns the difference (in ms) between two `DateTime`s. | | `distanceDurationEither` | Returns a `Left` or `Right` `Duration` depending on order. | | `distanceDuration` | Returns a `Duration` indicating how far apart two times are. | | `min` | Returns the earlier of two `DateTime` values. | | `max` | Returns the later of two `DateTime` values. | | `greaterThan`, `greaterThanOrEqualTo`, etc. | Checks ordering between two `DateTime` values. | | `between` | Checks if a `DateTime` lies within the given bounds. | | `isFuture`, `isPast`, `unsafeIsFuture`, etc. | Checks if a `DateTime` is in the future or past. | **Example** (Finding the Distance Between Two DateTimes) ```ts twoslash import { DateTime } from "effect" const utc1 = DateTime.unsafeMake("2025-01-01T00:00:00Z") const utc2 = DateTime.add(utc1, { days: 1 }) console.log(DateTime.distance(utc1, utc2)) // Output: 86400000 (one day) console.log(DateTime.distanceDurationEither(utc1, utc2)) /* Output: { _id: 'Either', _tag: 'Right', right: { _id: 'Duration', _tag: 'Millis', millis: 86400000 } } */ console.log(DateTime.distanceDuration(utc1, utc2)) // Output: { _id: 'Duration', _tag: 'Millis', millis: 86400000 } ``` ## Conversions | Function | Description | | ---------------- | ----------------------------------------------------------------------- | | `toDateUtc` | Returns a JavaScript `Date` in UTC. | | `toDate` | Applies the time zone (if present) and converts to a JavaScript `Date`. | | `zonedOffset` | For a `Zoned` DateTime, returns the time zone offset in ms. | | `zonedOffsetIso` | For a `Zoned` DateTime, returns an ISO offset string like "+01:00". | | `toEpochMillis` | Returns the Unix epoch time in milliseconds. | | `removeTime` | Returns a `Utc` with the time cleared (only date remains). | ## Parts | Function | Description | | -------------------------- | ---------------------------------------------------------------------- | | `toParts` | Returns time zone adjusted date parts (including weekday). | | `toPartsUtc` | Returns UTC date parts (including weekday). | | `getPart` / `getPartUtc` | Retrieves a specific part (e.g., `"year"` or `"month"`) from the date. | | `setParts` / `setPartsUtc` | Updates certain parts of a date, preserving or ignoring the time zone. | **Example** (Extracting Parts from a DateTime) ```ts twoslash import { DateTime } from "effect" const zoned = DateTime.setZone( DateTime.unsafeMake("2024-01-01"), DateTime.zoneUnsafeMakeNamed("Europe/Rome") ) console.log(DateTime.getPart(zoned, "month")) // Output: 1 ``` ## Math | Function | Description | | ------------------ | ------------------------------------------------------------------------------------------ | | `addDuration` | Adds the given `Duration` to a `DateTime`. | | `subtractDuration` | Subtracts the given `Duration` from a `DateTime`. | | `add` | Adds numeric parts (e.g., `{ hours: 2 }`) to a `DateTime`. | | `subtract` | Subtracts numeric parts. | | `startOf` | Moves a `DateTime` to the start of the given unit (e.g., the beginning of a day or month). | | `endOf` | Moves a `DateTime` to the end of the given unit. | | `nearest` | Rounds a `DateTime` to the nearest specified unit. | ## Formatting | Function | Description | | ------------------ | -------------------------------------------------------------------- | | `format` | Formats a `DateTime` as a string using the `DateTimeFormat` API. | | `formatLocal` | Uses the system's local time zone and locale for formatting. | | `formatUtc` | Forces UTC formatting. | | `formatIntl` | Uses a provided `Intl.DateTimeFormat`. | | `formatIso` | Returns an ISO 8601 string in UTC. | | `formatIsoDate` | Returns an ISO date string, adjusted for the time zone. | | `formatIsoDateUtc` | Returns an ISO date string in UTC. | | `formatIsoOffset` | Formats a `Zoned` as a string with an offset like "+01:00". | | `formatIsoZoned` | Formats a `Zoned` in the form `YYYY-MM-DDTHH:mm:ss.sss+HH:MM[Zone]`. | ## Layers for Current Time Zone | Function | Description | | ------------------------ | -------------------------------------------------------------------- | | `CurrentTimeZone` | A service tag for the current time zone. | | `setZoneCurrent` | Sets a `DateTime` to use the current time zone. | | `withCurrentZone` | Provides an effect with a specified time zone. | | `withCurrentZoneLocal` | Uses the system's local time zone for the effect. | | `withCurrentZoneOffset` | Uses a fixed offset (in ms) for the effect. | | `withCurrentZoneNamed` | Uses a named time zone identifier (e.g., "Europe/London"). | | `nowInCurrentZone` | Retrieves the current time as a `Zoned` in the configured time zone. | | `layerCurrentZone` | Creates a Layer providing the `CurrentTimeZone` service. | | `layerCurrentZoneOffset` | Creates a Layer from a fixed offset. | | `layerCurrentZoneNamed` | Creates a Layer from a named time zone, failing if invalid. | | `layerCurrentZoneLocal` | Creates a Layer from the system's local time zone. | **Example** (Using the Current Time Zone in an Effect) ```ts twoslash import { DateTime, Effect } from "effect" // Retrieve the current time in the "Europe/London" time zone const program = Effect.gen(function* () { const zonedNow = yield* DateTime.nowInCurrentZone console.log(zonedNow) }).pipe(DateTime.withCurrentZoneNamed("Europe/London")) Effect.runFork(program) /* Example Output: DateTime.Zoned(2025-01-06T18:36:38.573+00:00[Europe/London]) */ ```
# [Duration](https://effect.website/docs/data-types/duration/)
## Overview The `Duration` data type data type is used to represent specific non-negative spans of time. It is commonly used to represent time intervals or durations in various operations, such as timeouts, delays, or scheduling. The `Duration` type provides a convenient way to work with time units and perform calculations on durations. ## Creating Durations The Duration module includes several constructors to create durations in different units. **Example** (Creating Durations in Various Units) ```ts twoslash import { Duration } from "effect" // Create a duration of 100 milliseconds const duration1 = Duration.millis(100) // Create a duration of 2 seconds const duration2 = Duration.seconds(2) // Create a duration of 5 minutes const duration3 = Duration.minutes(5) ``` You can create durations using units such as nanoseconds, microsecond, milliseconds, seconds, minutes, hours, days, and weeks. For an infinite duration, use `Duration.infinity`. **Example** (Creating an Infinite Duration) ```ts twoslash import { Duration } from "effect" console.log(String(Duration.infinity)) /* Output: Duration(Infinity) */ ``` Another option for creating durations is using the `Duration.decode` helper: - `number` values are treated as milliseconds. - `bigint` values are treated as nanoseconds. - Strings must follow the format `"${number} ${unit}"`. **Example** (Decoding Values into Durations) ```ts twoslash import { Duration } from "effect" Duration.decode(10n) // same as Duration.nanos(10) Duration.decode(100) // same as Duration.millis(100) Duration.decode(Infinity) // same as Duration.infinity Duration.decode("10 nanos") // same as Duration.nanos(10) Duration.decode("20 micros") // same as Duration.micros(20) Duration.decode("100 millis") // same as Duration.millis(100) Duration.decode("2 seconds") // same as Duration.seconds(2) Duration.decode("5 minutes") // same as Duration.minutes(5) Duration.decode("7 hours") // same as Duration.hours(7) Duration.decode("3 weeks") // same as Duration.weeks(3) ``` ## Getting the Duration Value You can retrieve the value of a duration in milliseconds using `Duration.toMillis`. **Example** (Getting Duration in Milliseconds) ```ts twoslash import { Duration } from "effect" console.log(Duration.toMillis(Duration.seconds(30))) // Output: 30000 ``` To get the value of a duration in nanoseconds, use `Duration.toNanos`. Note that `toNanos` returns an `Option` because the duration might be infinite. **Example** (Getting Duration in Nanoseconds) ```ts twoslash import { Duration } from "effect" console.log(Duration.toNanos(Duration.millis(100))) /* Output: { _id: 'Option', _tag: 'Some', value: 100000000n } */ ``` To get a `bigint` value without `Option`, use `Duration.unsafeToNanos`. However, it will throw an error for infinite durations. **Example** (Retrieving Nanoseconds Unsafely) ```ts twoslash import { Duration } from "effect" console.log(Duration.unsafeToNanos(Duration.millis(100))) // Output: 100000000n console.log(Duration.unsafeToNanos(Duration.infinity)) /* throws: Error: Cannot convert infinite duration to nanos ...stack trace... */ ``` ## Comparing Durations Use the following functions to compare two durations: | API | Description | | ---------------------- | ---------------------------------------------------------------------------- | | `lessThan` | Returns `true` if the first duration is less than the second. | | `lessThanOrEqualTo` | Returns `true` if the first duration is less than or equal to the second. | | `greaterThan` | Returns `true` if the first duration is greater than the second. | | `greaterThanOrEqualTo` | Returns `true` if the first duration is greater than or equal to the second. | **Example** (Comparing Two Durations) ```ts twoslash import { Duration } from "effect" const duration1 = Duration.seconds(30) const duration2 = Duration.minutes(1) console.log(Duration.lessThan(duration1, duration2)) // Output: true console.log(Duration.lessThanOrEqualTo(duration1, duration2)) // Output: true console.log(Duration.greaterThan(duration1, duration2)) // Output: false console.log(Duration.greaterThanOrEqualTo(duration1, duration2)) // Output: false ``` ## Performing Arithmetic Operations You can perform arithmetic operations on durations, like addition and multiplication. **Example** (Adding and Multiplying Durations) ```ts twoslash import { Duration } from "effect" const duration1 = Duration.seconds(30) const duration2 = Duration.minutes(1) // Add two durations console.log(String(Duration.sum(duration1, duration2))) /* Output: Duration(1m 30s) */ // Multiply a duration by a factor console.log(String(Duration.times(duration1, 2))) /* Output: Duration(1m) */ ```
# [Either](https://effect.website/docs/data-types/either/)
## Overview import { Aside } from "@astrojs/starlight/components" The `Either` data type represents two exclusive values: an `Either` can be a `Right` value or a `Left` value, where `R` is the type of the `Right` value, and `L` is the type of the `Left` value. ## Understanding Either and Exit Either is primarily used as a **simple discriminated union** and is not recommended as the main result type for operations requiring detailed error information. [Exit](/docs/data-types/exit/) is the preferred **result type** within Effect for capturing comprehensive details about failures. It encapsulates the outcomes of effectful computations, distinguishing between success and various failure modes, such as errors, defects and interruptions. ## Creating Eithers You can create an `Either` using the `Either.right` and `Either.left` constructors. Use `Either.right` to create a `Right` value of type `R`. **Example** (Creating a Right Value) ```ts twoslash import { Either } from "effect" const rightValue = Either.right(42) console.log(rightValue) /* Output: { _id: 'Either', _tag: 'Right', right: 42 } */ ``` Use `Either.left` to create a `Left` value of type `L`. **Example** (Creating a Left Value) ```ts twoslash import { Either } from "effect" const leftValue = Either.left("not a number") console.log(leftValue) /* Output: { _id: 'Either', _tag: 'Left', left: 'not a number' } */ ``` ## Guards Use `Either.isLeft` and `Either.isRight` to check whether an `Either` is a `Left` or `Right` value. **Example** (Using Guards to Check the Type of Either) ```ts twoslash import { Either } from "effect" const foo = Either.right(42) if (Either.isLeft(foo)) { console.log(`The left value is: ${foo.left}`) } else { console.log(`The Right value is: ${foo.right}`) } // Output: "The Right value is: 42" ``` ## Pattern Matching Use `Either.match` to handle both cases of an `Either` by specifying separate callbacks for `Left` and `Right`. **Example** (Pattern Matching with Either) ```ts twoslash import { Either } from "effect" const foo = Either.right(42) const message = Either.match(foo, { onLeft: (left) => `The left value is: ${left}`, onRight: (right) => `The Right value is: ${right}` }) console.log(message) // Output: "The Right value is: 42" ``` ## Mapping ### Mapping over the Right Value Use `Either.map` to transform the `Right` value of an `Either`. The function you provide will only apply to the `Right` value, leaving any `Left` value unchanged. **Example** (Transforming the Right Value) ```ts twoslash import { Either } from "effect" // Transform the Right value by adding 1 const rightResult = Either.map(Either.right(1), (n) => n + 1) console.log(rightResult) /* Output: { _id: 'Either', _tag: 'Right', right: 2 } */ // The transformation is ignored for Left values const leftResult = Either.map(Either.left("not a number"), (n) => n + 1) console.log(leftResult) /* Output: { _id: 'Either', _tag: 'Left', left: 'not a number' } */ ``` ### Mapping over the Left Value Use `Either.mapLeft` to transform the `Left` value of an `Either`. The provided function only applies to the `Left` value, leaving any `Right` value unchanged. **Example** (Transforming the Left Value) ```ts twoslash import { Either } from "effect" // The transformation is ignored for Right values const rightResult = Either.mapLeft(Either.right(1), (s) => s + "!") console.log(rightResult) /* Output: { _id: 'Either', _tag: 'Right', right: 1 } */ // Transform the Left value by appending "!" const leftResult = Either.mapLeft( Either.left("not a number"), (s) => s + "!" ) console.log(leftResult) /* Output: { _id: 'Either', _tag: 'Left', left: 'not a number!' } */ ``` ### Mapping over Both Values Use `Either.mapBoth` to transform both the `Left` and `Right` values of an `Either`. This function takes two separate transformation functions: one for the `Left` value and another for the `Right` value. **Example** (Transforming Both Left and Right Values) ```ts twoslash import { Either } from "effect" const transformedRight = Either.mapBoth(Either.right(1), { onLeft: (s) => s + "!", onRight: (n) => n + 1 }) console.log(transformedRight) /* Output: { _id: 'Either', _tag: 'Right', right: 2 } */ const transformedLeft = Either.mapBoth(Either.left("not a number"), { onLeft: (s) => s + "!", onRight: (n) => n + 1 }) console.log(transformedLeft) /* Output: { _id: 'Either', _tag: 'Left', left: 'not a number!' } */ ``` ## Interop with Effect The `Either` type works as a subtype of the `Effect` type, allowing you to use it with functions from the `Effect` module. While these functions are built to handle `Effect` values, they can also manage `Either` values correctly. ### How Either Maps to Effect | Either Variant | Mapped to Effect | Description | | -------------- | ------------------ | -------------------- | | `Left` | `Effect` | Represents a failure | | `Right` | `Effect` | Represents a success | **Example** (Combining `Either` with `Effect`) ```ts twoslash import { Effect, Either } from "effect" // Function to get the head of an array, returning Either const head = (array: ReadonlyArray): Either.Either => array.length > 0 ? Either.right(array[0]) : Either.left("empty array") // Simulated fetch function that returns Effect const fetchData = (): Effect.Effect => { const success = Math.random() > 0.5 return success ? Effect.succeed("some data") : Effect.fail("Failed to fetch data") } // Mixing Either and Effect const program = Effect.all([head([1, 2, 3]), fetchData()]) Effect.runPromise(program).then(console.log) /* Example Output: [ 1, 'some data' ] */ ``` ## Combining Two or More Eithers ### zipWith The `Either.zipWith` function lets you combine two `Either` values using a provided function. It creates a new `Either` that holds the combined value of both original `Either` values. **Example** (Combining Two Eithers into an Object) ```ts twoslash import { Either } from "effect" const maybeName: Either.Either = Either.right("John") const maybeAge: Either.Either = Either.right(25) // Combine the name and age into a person object const person = Either.zipWith(maybeName, maybeAge, (name, age) => ({ name: name.toUpperCase(), age })) console.log(person) /* Output: { _id: 'Either', _tag: 'Right', right: { name: 'JOHN', age: 25 } } */ ``` If either of the `Either` values is `Left`, the result will be `Left`, holding the first encountered `Left` value: **Example** (Combining Eithers with a Left Value) ```ts twoslash {4} import { Either } from "effect" const maybeName: Either.Either = Either.right("John") const maybeAge: Either.Either = Either.left("Oh no!") // Since maybeAge is a Left, the result will also be Left const person = Either.zipWith(maybeName, maybeAge, (name, age) => ({ name: name.toUpperCase(), age })) console.log(person) /* Output: { _id: 'Either', _tag: 'Left', left: 'Oh no!' } */ ``` ### all To combine multiple `Either` values without transforming their contents, you can use `Either.all`. This function returns an `Either` with a structure matching the input: - If you pass a tuple, the result will be a tuple of the same length. - If you pass a struct, the result will be a struct with the same keys. - If you pass an `Iterable`, the result will be an array. **Example** (Combining Multiple Eithers into a Tuple and Struct) ```ts twoslash import { Either } from "effect" const maybeName: Either.Either = Either.right("John") const maybeAge: Either.Either = Either.right(25) // ┌─── Either<[string, number], string> // ▼ const tuple = Either.all([maybeName, maybeAge]) console.log(tuple) /* Output: { _id: 'Either', _tag: 'Right', right: [ 'John', 25 ] } */ // ┌─── Either<{ name: string; age: number; }, string> // ▼ const struct = Either.all({ name: maybeName, age: maybeAge }) console.log(struct) /* Output: { _id: 'Either', _tag: 'Right', right: { name: 'John', age: 25 } } */ ``` If one or more `Either` values are `Left`, the first `Left` encountered is returned: **Example** (Handling Multiple Left Values) ```ts import { Either } from "effect" const maybeName: Either.Either = Either.left("name not found") const maybeAge: Either.Either = Either.left("age not found") // The first Left value will be returned console.log(Either.all([maybeName, maybeAge])) /* Output: { _id: 'Either', _tag: 'Left', left: 'name not found' } */ ``` ## gen Similar to [Effect.gen](/docs/getting-started/using-generators/), `Either.gen` provides a more readable, generator-based syntax for working with `Either` values, making code that involves `Either` easier to write and understand. This approach is similar to using `async/await` but tailored for `Either`. **Example** (Using `Either.gen` to Create a Combined Value) ```ts twoslash import { Either } from "effect" const maybeName: Either.Either = Either.right("John") const maybeAge: Either.Either