Practical functional programming in Effect


This is the 3rd entry in the Effect Series. The previous entry is Basic Effect programs.

TL;DR

Practical understanding of some common fp concepts will allow you to read Effect programs and the Effect codebase. Combined, they are a powerful tool to write clear, concise, maintainable code.

  • Module methods: Array.forEach(array, f) instead of array.forEach(f)
  • Dual functions:
    • Array.map((n: number) => n + 1) returns (array: number[]) => number[];
    • Array.map(array, (n: number) => n + 1) returns number[]
  • Both above concepts enable pipe, which passes the previous result as the first argument of the next function: pipe([1, 2, 3], Array.map(plusOne), Array.reduce(sum), console.log) will log 9.
  • map and flatMap are general fp concepts, useful beyond arrays:
    • map: transforms contained value: Effect.map(Effect.success(1), n => n + 1) => Effect<number> that will yield 2
    • flatMap: transforms the whole container, based on the contained value: Effect.flatMap(Effect.success(1), n => Effect.succeed(2)) => also Effect<number> that will yield 2. Use this when the transformation involves an Effect (i.e. for any async work, or work that may fail).
  • Either<A, B>: represents values that may be of two types, which is a very common occurrence. You may see it to represent success/failure scenarios.
  • Option<T>: represents a value that may or may not be. The Option module provides many useful methods.

In my introduction I mentioned that in some ways Effect feels like a new language. This is especially true if you don’t have experience with functional programming paradigms. My goal in this post is to cover some key concepts commonly present in Effect codebases and that constitute a powerful tool for writing terse yet maintainable code. This is not a functional programming class. The approach is primarily practical.

Module methods

You may be used to working with class instances, which expose methods to interact with their internal state. For example, [1, 2, 3].forEach(console.log): You access forEach in the array itself. The subject of the operation is implicit (in the forEach implementation, this is the array).

This remains true as long as you are working with JavaScript classes. There are JavaScript classes in the Effect library, and classes are sometimes the best tool for the job. However, most of the Effect interfaces are geared towards exposing methods that don’t have an implicit subject: i.e. they don’t hold their own state. Instead, module methods take that subject as their first argument.

Since we are all familiar with array operations, we can take as an example the Array module that Effect exposes as an alternative way to interact with arrays:

import {
import Array
Array
} from 'effect';
import Array
Array
.
const forEach: <number>(self: Iterable<number>, f: (a: number, i: number) => void) => void (+1 overload)

Performs a side-effect for each element of the Iterable.

Example

import { Array } from "effect"
Array.forEach([1, 2, 3], n => console.log(n)) // 1, 2, 3

@since2.0.0

forEach
(
// explicit "state"
[1, 2, 3],
// callback
n: number
n
=>
var console: Console

The console module provides a simple debugging console that is similar to the JavaScript console mechanism provided by web browsers.

The module exports two specific components:

  • A Console class with methods such as console.log(), console.error() andconsole.warn() that can be used to write to any Node.js stream.
  • A global console instance configured to write to process.stdout and process.stderr. The global console can be used without callingrequire('console').

Warning: The global console object's methods are neither consistently synchronous like the browser APIs they resemble, nor are they consistently asynchronous like all other Node.js streams. See the note on process I/O for more information.

Example using the global console:

console.log('hello world');
// Prints: hello world, to stdout
console.log('hello %s', 'world');
// Prints: hello world, to stdout
console.error(new Error('Whoops, something bad happened'));
// Prints error message and stack trace to stderr:
// Error: Whoops, something bad happened
// at [eval]:5:15
// at Script.runInThisContext (node:vm:132:18)
// at Object.runInThisContext (node:vm:309:38)
// at node:internal/process/execution:77:19
// at [eval]-wrapper:6:22
// at evalScript (node:internal/process/execution:76:60)
// at node:internal/main/eval_string:23:3
const name = 'Will Robinson';
console.warn(`Danger ${name}! Danger!`);
// Prints: Danger Will Robinson! Danger!, to stderr

Example using the Console class:

const out = getStreamSomehow();
const err = getStreamSomehow();
const myConsole = new console.Console(out, err);
myConsole.log('hello world');
// Prints: hello world, to out
myConsole.log('hello %s', 'world');
// Prints: hello world, to out
myConsole.error(new Error('Whoops, something bad happened'));
// Prints: [Error: Whoops, something bad happened], to err
const name = 'Will Robinson';
myConsole.warn(`Danger ${name}! Danger!`);
// Prints: Danger Will Robinson! Danger!, to err

@seesource

console
.
Console.log(message?: any, ...optionalParams: any[]): void

Prints to stdout with newline. Multiple arguments can be passed, with the first used as the primary message and all additional used as substitution values similar to printf(3) (the arguments are all passed to util.format()).

const count = 5;
console.log('count: %d', count);
// Prints: count: 5, to stdout
console.log('count:', count);
// Prints: count: 5, to stdout

See util.format() for more information.

@sincev0.1.100

log
(
n: number
n
)
);

Similarly, when you interact with Effect, you do it via the methods under the Effect namespace (e.g. Effect.provide(effect, layer), Effect.runPromise(effect)). Again, note how their first argument is an Effect.

Another example is the methods under the Schema namespace, such as Schema.decode, Schema.optional, also taking a Schema as their first argument.

pipe

The concept of “pipes” as a utility to pass data across processes or functions has become almost mainstream in the last few years. Some languages have had it for ages. Unix has the | operator to pass data between processes. Languages like Haskell, F#, Elixir, Julia and Elm (|>) use it for function application.

In JavaScript, there is a long list of libraries that have supported and encouraged some sort of pipe (lodash flow, Ramda and RxJS pipe), and there is a proposal to adopt them into the language.

While the operator doesn’t work exactly the same way in all languages and libraries, the core idea of taking something from the left side and applying it to the right side is universal. Pipe operators promote better organization of code, reusability, and readability. Wise usage of the pipe operator often yields code that conveys the flow of data at a glance. That said, it can take a while for your brain to get used to seeing that data flow without having to think, if you are not used to pipes.

pipe in Effect passes the result of the left-hand operation as the first argument of the right-hand operation.

So pipe(x, f) is equivalent to f(x). Of course, this is not useful with a single argument. Perhaps an example like the following illustrates it better:

import {
function pipe<A>(a: A): A (+19 overloads)

Pipes the value of an expression into a pipeline of functions.

Details

The pipe function is a utility that allows us to compose functions in a readable and sequential manner. It takes the output of one function and passes it as the input to the next function in the pipeline. This enables us to build complex transformations by chaining multiple functions together.

import { pipe } from "effect"
const result = pipe(input, func1, func2, ..., funcN)

In this syntax, input is the initial value, and func1, func2, ..., funcN are the functions to be applied in sequence. The result of each function becomes the input for the next function, and the final result is returned.

Here's an illustration of how pipe works:

┌───────┐ ┌───────┐ ┌───────┐ ┌───────┐ ┌───────┐ ┌────────┐
│ input │───►│ func1 │───►│ func2 │───►│ ... │───►│ funcN │───►│ result │
└───────┘ └───────┘ └───────┘ └───────┘ └───────┘ └────────┘

It's important to note that functions passed to pipe must have a single argument because they are only called with a single argument.

When to Use

This is useful in combination with data-last functions as a simulation of methods:

as.map(f).filter(g)

becomes:

import { pipe, Array } from "effect"
pipe(as, Array.map(f), Array.filter(g))

Example (Chaining Arithmetic Operations)

import { pipe } from "effect"
// Define simple arithmetic operations
const increment = (x: number) => x + 1
const double = (x: number) => x * 2
const subtractTen = (x: number) => x - 10
// Sequentially apply these operations using `pipe`
const result = pipe(5, increment, double, subtractTen)
console.log(result)
// Output: 2

@since2.0.0

pipe
} from 'effect'
24 collapsed lines
enum
enum Currency
Currency
{
function (enum member) Currency.Euro = 0
Euro
,
function (enum member) Currency.Dollar = 1
Dollar
,
}
interface
interface Item
Item
{
Item.description: string
description
: string;
Item.quantity: number
quantity
: number;
Item.price: number
price
: number;
}
type
type ShoppingCart = Item[]
ShoppingCart
=
interface Item
Item
[];
interface
interface UserPreferences
UserPreferences
{
UserPreferences.currency: Currency
currency
:
enum Currency
Currency
}
declare const
const calculateSubtotal: (shoppingCart: ShoppingCart) => number
calculateSubtotal
: (
shoppingCart: ShoppingCart
shoppingCart
:
type ShoppingCart = Item[]
ShoppingCart
) => number;
declare const
const applyDiscount: (min: number, discount: number) => (input: number) => number
applyDiscount
: (
min: number
min
: number,
discount: number
discount
: number) => (
input: number
input
: number) => number;
declare const
const addTax: (tax: number) => (input: number) => number
addTax
: (
tax: number
tax
: number) => (
input: number
input
: number) => number;
declare const
const formatCurrency: (userPreferences: UserPreferences) => (input: number) => string
formatCurrency
: (
userPreferences: UserPreferences
userPreferences
:
interface UserPreferences
UserPreferences
) => (
input: number
input
: number) => string;
declare const
const userPreferences: UserPreferences
userPreferences
:
interface UserPreferences
UserPreferences
;
declare const
const shoppingCart: ShoppingCart
shoppingCart
:
type ShoppingCart = Item[]
ShoppingCart
;
const
const invoice: string
invoice
=
pipe<ShoppingCart, number, number, number, string>(a: ShoppingCart, ab: (a: ShoppingCart) => number, bc: (b: number) => number, cd: (c: number) => number, de: (d: number) => string): string (+19 overloads)

Pipes the value of an expression into a pipeline of functions.

Details

The pipe function is a utility that allows us to compose functions in a readable and sequential manner. It takes the output of one function and passes it as the input to the next function in the pipeline. This enables us to build complex transformations by chaining multiple functions together.

import { pipe } from "effect"
const result = pipe(input, func1, func2, ..., funcN)

In this syntax, input is the initial value, and func1, func2, ..., funcN are the functions to be applied in sequence. The result of each function becomes the input for the next function, and the final result is returned.

Here's an illustration of how pipe works:

┌───────┐ ┌───────┐ ┌───────┐ ┌───────┐ ┌───────┐ ┌────────┐
│ input │───►│ func1 │───►│ func2 │───►│ ... │───►│ funcN │───►│ result │
└───────┘ └───────┘ └───────┘ └───────┘ └───────┘ └────────┘

It's important to note that functions passed to pipe must have a single argument because they are only called with a single argument.

When to Use

This is useful in combination with data-last functions as a simulation of methods:

as.map(f).filter(g)

becomes:

import { pipe, Array } from "effect"
pipe(as, Array.map(f), Array.filter(g))

Example (Chaining Arithmetic Operations)

import { pipe } from "effect"
// Define simple arithmetic operations
const increment = (x: number) => x + 1
const double = (x: number) => x * 2
const subtractTen = (x: number) => x - 10
// Sequentially apply these operations using `pipe`
const result = pipe(5, increment, double, subtractTen)
console.log(result)
// Output: 2

@since2.0.0

pipe
(
// The value we start with.
const shoppingCart: ShoppingCart
shoppingCart
,
// A function that takes the shopping cart and sums the amounts of the line
// items.
const calculateSubtotal: (shoppingCart: ShoppingCart) => number
calculateSubtotal
, // 100
// Returns a function that will take the result from the previous step
// (`calculateSubtotal`) and apply a discount of 0.10 if the subtotal is >
// 10,000 cents.
const applyDiscount: (min: number, discount: number) => (input: number) => number
applyDiscount
(10, 0.10), // 90
// Returns a function that will take the result from the previous step (having
// applied the discount), and apply an 8% tax.
const addTax: (tax: number) => (input: number) => number
addTax
(0.08), // 97.2
// Returns a function that will take the result from the previous step (having
// applied the tax), and formats it to the preferred currency.
const formatCurrency: (userPreferences: UserPreferences) => (input: number) => string
formatCurrency
(
const userPreferences: UserPreferences
userPreferences
) // "97.20 €"
);
var console: Console

The console module provides a simple debugging console that is similar to the JavaScript console mechanism provided by web browsers.

The module exports two specific components:

  • A Console class with methods such as console.log(), console.error() andconsole.warn() that can be used to write to any Node.js stream.
  • A global console instance configured to write to process.stdout and process.stderr. The global console can be used without callingrequire('console').

Warning: The global console object's methods are neither consistently synchronous like the browser APIs they resemble, nor are they consistently asynchronous like all other Node.js streams. See the note on process I/O for more information.

Example using the global console:

console.log('hello world');
// Prints: hello world, to stdout
console.log('hello %s', 'world');
// Prints: hello world, to stdout
console.error(new Error('Whoops, something bad happened'));
// Prints error message and stack trace to stderr:
// Error: Whoops, something bad happened
// at [eval]:5:15
// at Script.runInThisContext (node:vm:132:18)
// at Object.runInThisContext (node:vm:309:38)
// at node:internal/process/execution:77:19
// at [eval]-wrapper:6:22
// at evalScript (node:internal/process/execution:76:60)
// at node:internal/main/eval_string:23:3
const name = 'Will Robinson';
console.warn(`Danger ${name}! Danger!`);
// Prints: Danger Will Robinson! Danger!, to stderr

Example using the Console class:

const out = getStreamSomehow();
const err = getStreamSomehow();
const myConsole = new console.Console(out, err);
myConsole.log('hello world');
// Prints: hello world, to out
myConsole.log('hello %s', 'world');
// Prints: hello world, to out
myConsole.error(new Error('Whoops, something bad happened'));
// Prints: [Error: Whoops, something bad happened], to err
const name = 'Will Robinson';
myConsole.warn(`Danger ${name}! Danger!`);
// Prints: Danger Will Robinson! Danger!, to err

@seesource

console
.
Console.log(message?: any, ...optionalParams: any[]): void

Prints to stdout with newline. Multiple arguments can be passed, with the first used as the primary message and all additional used as substitution values similar to printf(3) (the arguments are all passed to util.format()).

const count = 5;
console.log('count: %d', count);
// Prints: count: 5, to stdout
console.log('count:', count);
// Prints: count: 5, to stdout

See util.format() for more information.

@sincev0.1.100

log
(
const invoice: string
invoice
) // "97.20 €"

Consider the alternatives of a complex nesting of function calls, or the creation of variables to hold intermediate values.

Tip

Not all code needs to be written this way. Imperative code can still be more clear or practical. Some teams lean into pipe when clearly there is some data being transformed along several steps, or to provision dependencies, but fall back to imperative code for most application logic.

Here are some common operations you’ll see piped. For now you don’t need to understand each of the operations. We’ll cover them later.

  • Effect.map, Effect.flatMap to transform values inside an Effect
  • Effect.match to transform the result and the error channels in an Effect
  • Effect.provide and Effect.provideService to provide dependencies (requirements) to an Effect
  • Layer.provide and Layer.provideMerge to combine dependency layers
  • Many Schema methods like Schema.decode, Schema.maxLength, Schema.optional, Schema.mutable

Here’s another example of a server handling a client request:

import {
import Data
Data
,
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
,
function pipe<A>(a: A): A (+19 overloads)

Pipes the value of an expression into a pipeline of functions.

Details

The pipe function is a utility that allows us to compose functions in a readable and sequential manner. It takes the output of one function and passes it as the input to the next function in the pipeline. This enables us to build complex transformations by chaining multiple functions together.

import { pipe } from "effect"
const result = pipe(input, func1, func2, ..., funcN)

In this syntax, input is the initial value, and func1, func2, ..., funcN are the functions to be applied in sequence. The result of each function becomes the input for the next function, and the final result is returned.

Here's an illustration of how pipe works:

┌───────┐ ┌───────┐ ┌───────┐ ┌───────┐ ┌───────┐ ┌────────┐
│ input │───►│ func1 │───►│ func2 │───►│ ... │───►│ funcN │───►│ result │
└───────┘ └───────┘ └───────┘ └───────┘ └───────┘ └────────┘

It's important to note that functions passed to pipe must have a single argument because they are only called with a single argument.

When to Use

This is useful in combination with data-last functions as a simulation of methods:

as.map(f).filter(g)

becomes:

import { pipe, Array } from "effect"
pipe(as, Array.map(f), Array.filter(g))

Example (Chaining Arithmetic Operations)

import { pipe } from "effect"
// Define simple arithmetic operations
const increment = (x: number) => x + 1
const double = (x: number) => x * 2
const subtractTen = (x: number) => x - 10
// Sequentially apply these operations using `pipe`
const result = pipe(5, increment, double, subtractTen)
console.log(result)
// Output: 2

@since2.0.0

pipe
,
import Schema
Schema
} from "effect"
20 collapsed lines
interface
interface Request
Request
{
Request.data: string
data
: string
}
const
const CreateAccountSchema: Schema.Struct<{
name: typeof Schema.String;
}>
CreateAccountSchema
=
import Schema
Schema
.
function Struct<{
name: typeof Schema.String;
}>(fields: {
name: typeof Schema.String;
}): Schema.Struct<{
name: typeof Schema.String;
}> (+1 overload)

@since3.10.0

Struct
({
name: typeof Schema.String
name
:
import Schema
Schema
.
class String
export String

@since3.10.0

String
})
const
const AccountSchema: Schema.Struct<{
id: typeof Schema.UUID;
name: typeof Schema.String;
}>
AccountSchema
=
import Schema
Schema
.
function Struct<{
id: typeof Schema.UUID;
name: typeof Schema.String;
}>(fields: {
id: typeof Schema.UUID;
name: typeof Schema.String;
}): Schema.Struct<{
id: typeof Schema.UUID;
name: typeof Schema.String;
}> (+1 overload)

@since3.10.0

Struct
({
id: typeof Schema.UUID
id
:
import Schema
Schema
.
class UUID

Represents a Universally Unique Identifier (UUID).

This schema ensures that the provided string adheres to the standard UUID format.

@since3.10.0

UUID
,
name: typeof Schema.String
name
:
import Schema
Schema
.
class String
export String

@since3.10.0

String
})
class
class AccountCreateError
AccountCreateError
extends
import Data
Data
.
const TaggedError: <"AccountCreateError">(tag: "AccountCreateError") => new <A>(args: Equals<A, {}> extends true ? void : { readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }) => YieldableError & {
readonly _tag: "AccountCreateError";
} & Readonly<A>

@since2.0.0

TaggedError
("AccountCreateError") {}
class
class HttpError
HttpError
extends
import Data
Data
.
const TaggedError: <"HttpError">(tag: "HttpError") => new <A>(args: Equals<A, {}> extends true ? void : { readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }) => YieldableError & {
readonly _tag: "HttpError";
} & Readonly<A>

@since2.0.0

TaggedError
("HttpError") {}
declare const
const toHttpError: <E>(e: E) => HttpError
toHttpError
: <
function (type parameter) E in <E>(e: E): HttpError
E
>(
e: E
e
:
function (type parameter) E in <E>(e: E): HttpError
E
) =>
class HttpError
HttpError
declare const
const createAccount: (createAccountInput: typeof CreateAccountSchema.Type) => Effect.Effect<typeof AccountSchema.Type, AccountCreateError>
createAccount
: (
createAccountInput: {
readonly name: string;
}
createAccountInput
: typeof
const CreateAccountSchema: Schema.Struct<{
name: typeof Schema.String;
}>
CreateAccountSchema
.
Schema<{ readonly name: string; }, { readonly name: string; }, never>.Type: {
readonly name: string;
}
Type
) =>
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
.
interface Effect<out A, out E = never, out R = never>

The Effect interface defines a value that describes a workflow or job, which can succeed or fail.

Details

The Effect interface represents a computation that can model a workflow involving various types of operations, such as synchronous, asynchronous, concurrent, and parallel interactions. It operates within a context of type R, and the result can either be a success with a value of type A or a failure with an error of type E. The Effect is designed to handle complex interactions with external resources, offering advanced features such as fiber-based concurrency, scheduling, interruption handling, and scalability. This makes it suitable for tasks that require fine-grained control over concurrency and error management.

To execute an Effect value, you need a Runtime, which provides the environment necessary to run and manage the computation.

@since2.0.0

@since2.0.0

Effect
<typeof
const AccountSchema: Schema.Struct<{
id: typeof Schema.UUID;
name: typeof Schema.String;
}>
AccountSchema
.
Schema<{ readonly name: string; readonly id: string; }, { readonly name: string; readonly id: string; }, never>.Type: {
readonly name: string;
readonly id: string;
}
Type
,
class AccountCreateError
AccountCreateError
>
const
const handleRequest: (request: Request) => Effect.Effect<{
readonly name: string;
readonly id: string;
}, HttpError, never>
handleRequest
= (
request: Request
request
:
interface Request
Request
) =>
pipe<string, Effect.Effect<{
readonly name: string;
}, ParseError, never>, Effect.Effect<{
readonly name: string;
readonly id: string;
}, AccountCreateError | ParseError, never>, Effect.Effect<{
readonly name: string;
readonly id: string;
}, HttpError, never>>(a: string, ab: (a: string) => Effect.Effect<{
readonly name: string;
}, ParseError, never>, bc: (b: Effect.Effect<{
readonly name: string;
}, ParseError, never>) => Effect.Effect<{
readonly name: string;
readonly id: string;
}, AccountCreateError | ParseError, never>, cd: (c: Effect.Effect<...>) => Effect.Effect<...>): Effect.Effect<...> (+19 overloads)

Pipes the value of an expression into a pipeline of functions.

Details

The pipe function is a utility that allows us to compose functions in a readable and sequential manner. It takes the output of one function and passes it as the input to the next function in the pipeline. This enables us to build complex transformations by chaining multiple functions together.

import { pipe } from "effect"
const result = pipe(input, func1, func2, ..., funcN)

In this syntax, input is the initial value, and func1, func2, ..., funcN are the functions to be applied in sequence. The result of each function becomes the input for the next function, and the final result is returned.

Here's an illustration of how pipe works:

┌───────┐ ┌───────┐ ┌───────┐ ┌───────┐ ┌───────┐ ┌────────┐
│ input │───►│ func1 │───►│ func2 │───►│ ... │───►│ funcN │───►│ result │
└───────┘ └───────┘ └───────┘ └───────┘ └───────┘ └────────┘

It's important to note that functions passed to pipe must have a single argument because they are only called with a single argument.

When to Use

This is useful in combination with data-last functions as a simulation of methods:

as.map(f).filter(g)

becomes:

import { pipe, Array } from "effect"
pipe(as, Array.map(f), Array.filter(g))

Example (Chaining Arithmetic Operations)

import { pipe } from "effect"
// Define simple arithmetic operations
const increment = (x: number) => x + 1
const double = (x: number) => x * 2
const subtractTen = (x: number) => x - 10
// Sequentially apply these operations using `pipe`
const result = pipe(5, increment, double, subtractTen)
console.log(result)
// Output: 2

@since2.0.0

pipe
(
// raw data from a network request
request: Request
request
.
Request.data: string
data
,
// validate and convert to internal representation
import Schema
Schema
.
const parseJson: <Schema.Struct<{
name: typeof Schema.String;
}>>(schema: Schema.Struct<{
name: typeof Schema.String;
}>, options?: Schema.ParseJsonOptions) => Schema.transform<Schema.SchemaClass<unknown, string, never>, Schema.Struct<{
name: typeof Schema.String;
}>> (+1 overload)

The ParseJson combinator provides a method to convert JSON strings into the unknown type using the underlying functionality of JSON.parse. It also utilizes JSON.stringify for encoding.

You can optionally provide a ParseJsonOptions to configure both JSON.parse and JSON.stringify executions.

Optionally, you can pass a schema Schema<A, I, R> to obtain an A type instead of unknown.

@example

import * as assert from "node:assert"
import * as Schema from "effect/Schema"
assert.deepStrictEqual(Schema.decodeUnknownSync(Schema.parseJson())(`{"a":"1"}`), { a: "1" })
assert.deepStrictEqual(Schema.decodeUnknownSync(Schema.parseJson(Schema.Struct({ a: Schema.NumberFromString })))(`{"a":"1"}`), { a: 1 })

@since3.10.0

parseJson
(
const CreateAccountSchema: Schema.Struct<{
name: typeof Schema.String;
}>
CreateAccountSchema
).
Pipeable.pipe<Schema.transform<Schema.SchemaClass<unknown, string, never>, Schema.Struct<{
name: typeof Schema.String;
}>>, (i: string, overrideOptions?: ParseOptions) => Effect.Effect<{
readonly name: string;
}, ParseError, never>>(this: Schema.transform<...>, ab: (_: Schema.transform<Schema.SchemaClass<unknown, string, never>, Schema.Struct<{
name: typeof Schema.String;
}>>) => (i: string, overrideOptions?: ParseOptions) => Effect.Effect<{
readonly name: string;
}, ParseError, never>): (i: string, overrideOptions?: ParseOptions) => Effect.Effect<{
readonly name: string;
}, ParseError, never> (+21 overloads)
pipe
(
import Schema
Schema
.
const decode: <A, I, R>(schema: Schema.Schema<A, I, R>, options?: ParseOptions) => (i: I, overrideOptions?: ParseOptions) => Effect.Effect<A, ParseError, R>

@since3.10.0

decode
),
// actually create the account
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
.
const andThen: <{
readonly name: string;
}, Effect.Effect<{
readonly name: string;
readonly id: string;
}, AccountCreateError, never>>(f: (a: {
readonly name: string;
}) => Effect.Effect<{
readonly name: string;
readonly id: string;
}, AccountCreateError, never>) => <E, R>(self: Effect.Effect<{
readonly name: string;
}, E, R>) => Effect.Effect<{
readonly name: string;
readonly id: string;
}, AccountCreateError | E, R> (+3 overloads)

Chains two actions, where the second action can depend on the result of the first.

Syntax

const transformedEffect = pipe(myEffect, Effect.andThen(anotherEffect))
// or
const transformedEffect = Effect.andThen(myEffect, anotherEffect)
// or
const transformedEffect = myEffect.pipe(Effect.andThen(anotherEffect))

When to Use

Use andThen when you need to run multiple actions in sequence, with the second action depending on the result of the first. This is useful for combining effects or handling computations that must happen in order.

Details

The second action can be:

  • A constant value (similar to

as

)

  • A function returning a value (similar to

map

)

  • A Promise
  • A function returning a Promise
  • An Effect
  • A function returning an Effect (similar to

flatMap

)

Note: andThen works well with both Option and Either types, treating them as effects.

Example (Applying a Discount Based on Fetched Amount)

import { pipe, Effect } from "effect"
// Function to apply a discount safely to a transaction amount
const applyDiscount = (
total: number,
discountRate: number
): Effect.Effect<number, Error> =>
discountRate === 0
? Effect.fail(new Error("Discount rate cannot be zero"))
: Effect.succeed(total - (total * discountRate) / 100)
// Simulated asynchronous task to fetch a transaction amount from database
const fetchTransactionAmount = Effect.promise(() => Promise.resolve(100))
// Using Effect.map and Effect.flatMap
const result1 = pipe(
fetchTransactionAmount,
Effect.map((amount) => amount * 2),
Effect.flatMap((amount) => applyDiscount(amount, 5))
)
Effect.runPromise(result1).then(console.log)
// Output: 190
// Using Effect.andThen
const result2 = pipe(
fetchTransactionAmount,
Effect.andThen((amount) => amount * 2),
Effect.andThen((amount) => applyDiscount(amount, 5))
)
Effect.runPromise(result2).then(console.log)
// Output: 190

@since2.0.0

andThen
(
const createAccount: (createAccountInput: typeof CreateAccountSchema.Type) => Effect.Effect<typeof AccountSchema.Type, AccountCreateError>
createAccount
),
// map possible errors to errors that make sense as an http response
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
.
const mapError: <AccountCreateError | ParseError, HttpError>(f: (e: AccountCreateError | ParseError) => HttpError) => <A, R>(self: Effect.Effect<A, AccountCreateError | ParseError, R>) => Effect.Effect<A, HttpError, R> (+1 overload)

Transforms or modifies the error produced by an effect without affecting its success value.

When to Use

This function is helpful when you want to enhance the error with additional information, change the error type, or apply custom error handling while keeping the original behavior of the effect's success values intact. It only operates on the error channel and leaves the success channel unchanged.

Example

import { Effect } from "effect"
// ┌─── Effect<number, string, never>
// ▼
const simulatedTask = Effect.fail("Oh no!").pipe(Effect.as(1))
// ┌─── Effect<number, Error, never>
// ▼
const mapped = Effect.mapError(
simulatedTask,
(message) => new Error(message)
)

@seemap for a version that operates on the success channel.

@seemapBoth for a version that operates on both channels.

@seeorElseFail if you want to replace the error with a new one.

@since2.0.0

mapError
(
const toHttpError: <E>(e: E) => HttpError
toHttpError
)
)
Tip

For convenience, many objects in the Effect library have a pipe method. That will often be sufficient, and you don’t need to import pipe directly:

import {
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
} from 'effect';
declare const
const addOne: (n: number) => number
addOne
: (
n: number
n
: number) => number;
declare const
const timesTwo: (n: number) => number
timesTwo
: (
n: number
n
: number) => number;
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
.
const succeed: <number>(value: number) => Effect.Effect<number, never, never>

Creates an Effect that always succeeds with a given value.

When to Use

Use this function when you need an effect that completes successfully with a specific value without any errors or external dependencies.

Example (Creating a Successful Effect)

import { Effect } from "effect"
// Creating an effect that represents a successful scenario
//
// ┌─── Effect<number, never, never>
// ▼
const success = Effect.succeed(42)

@seefail to create an effect that represents a failure.

@since2.0.0

succeed
(1).
Pipeable.pipe<Effect.Effect<number, never, never>, Effect.Effect<number, never, never>, Effect.Effect<number, never, never>>(this: Effect.Effect<number, never, never>, ab: (_: Effect.Effect<number, never, never>) => Effect.Effect<number, never, never>, bc: (_: Effect.Effect<number, never, never>) => Effect.Effect<number, never, never>): Effect.Effect<number, never, never> (+21 overloads)
pipe
(
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
.
const map: <number, number>(f: (a: number) => number) => <E, R>(self: Effect.Effect<number, E, R>) => Effect.Effect<number, E, R> (+1 overload)

Transforms the value inside an effect by applying a function to it.

Syntax

const mappedEffect = pipe(myEffect, Effect.map(transformation))
// or
const mappedEffect = Effect.map(myEffect, transformation)
// or
const mappedEffect = myEffect.pipe(Effect.map(transformation))

Details

map takes a function and applies it to the value contained within an effect, creating a new effect with the transformed value.

It's important to note that effects are immutable, meaning that the original effect is not modified. Instead, a new effect is returned with the updated value.

Example (Adding a Service Charge)

import { pipe, Effect } from "effect"
const addServiceCharge = (amount: number) => amount + 1
const fetchTransactionAmount = Effect.promise(() => Promise.resolve(100))
const finalAmount = pipe(
fetchTransactionAmount,
Effect.map(addServiceCharge)
)
Effect.runPromise(finalAmount).then(console.log)
// Output: 101

@seemapError for a version that operates on the error channel.

@seemapBoth for a version that operates on both channels.

@seeflatMap or andThen for a version that can return a new effect.

@since2.0.0

map
(
const addOne: (n: number) => number
addOne
),
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
.
const map: <number, number>(f: (a: number) => number) => <E, R>(self: Effect.Effect<number, E, R>) => Effect.Effect<number, E, R> (+1 overload)

Transforms the value inside an effect by applying a function to it.

Syntax

const mappedEffect = pipe(myEffect, Effect.map(transformation))
// or
const mappedEffect = Effect.map(myEffect, transformation)
// or
const mappedEffect = myEffect.pipe(Effect.map(transformation))

Details

map takes a function and applies it to the value contained within an effect, creating a new effect with the transformed value.

It's important to note that effects are immutable, meaning that the original effect is not modified. Instead, a new effect is returned with the updated value.

Example (Adding a Service Charge)

import { pipe, Effect } from "effect"
const addServiceCharge = (amount: number) => amount + 1
const fetchTransactionAmount = Effect.promise(() => Promise.resolve(100))
const finalAmount = pipe(
fetchTransactionAmount,
Effect.map(addServiceCharge)
)
Effect.runPromise(finalAmount).then(console.log)
// Output: 101

@seemapError for a version that operates on the error channel.

@seemapBoth for a version that operates on both channels.

@seeflatMap or andThen for a version that can return a new effect.

@since2.0.0

map
(
const timesTwo: (n: number) => number
timesTwo
)
)

Dual functions

Because JavaScript doesn’t support currying natively, you can’t magically take any function and elide its first argument to conveniently use it in your pipe. In other words, the following is not possible:

import {
function pipe<A>(a: A): A (+19 overloads)

Pipes the value of an expression into a pipeline of functions.

Details

The pipe function is a utility that allows us to compose functions in a readable and sequential manner. It takes the output of one function and passes it as the input to the next function in the pipeline. This enables us to build complex transformations by chaining multiple functions together.

import { pipe } from "effect"
const result = pipe(input, func1, func2, ..., funcN)

In this syntax, input is the initial value, and func1, func2, ..., funcN are the functions to be applied in sequence. The result of each function becomes the input for the next function, and the final result is returned.

Here's an illustration of how pipe works:

┌───────┐ ┌───────┐ ┌───────┐ ┌───────┐ ┌───────┐ ┌────────┐
│ input │───►│ func1 │───►│ func2 │───►│ ... │───►│ funcN │───►│ result │
└───────┘ └───────┘ └───────┘ └───────┘ └───────┘ └────────┘

It's important to note that functions passed to pipe must have a single argument because they are only called with a single argument.

When to Use

This is useful in combination with data-last functions as a simulation of methods:

as.map(f).filter(g)

becomes:

import { pipe, Array } from "effect"
pipe(as, Array.map(f), Array.filter(g))

Example (Chaining Arithmetic Operations)

import { pipe } from "effect"
// Define simple arithmetic operations
const increment = (x: number) => x + 1
const double = (x: number) => x * 2
const subtractTen = (x: number) => x - 10
// Sequentially apply these operations using `pipe`
const result = pipe(5, increment, double, subtractTen)
console.log(result)
// Output: 2

@since2.0.0

pipe
} from 'effect'
const
const divide: (n1: number, n2: number) => number
divide
= (
n1: number
n1
: number,
n2: number
n2
: number) =>
n1: number
n1
/
n2: number
n2
;
pipe<number, never>(a: number, ab: (a: number) => never): never (+19 overloads)

Pipes the value of an expression into a pipeline of functions.

Details

The pipe function is a utility that allows us to compose functions in a readable and sequential manner. It takes the output of one function and passes it as the input to the next function in the pipeline. This enables us to build complex transformations by chaining multiple functions together.

import { pipe } from "effect"
const result = pipe(input, func1, func2, ..., funcN)

In this syntax, input is the initial value, and func1, func2, ..., funcN are the functions to be applied in sequence. The result of each function becomes the input for the next function, and the final result is returned.

Here's an illustration of how pipe works:

┌───────┐ ┌───────┐ ┌───────┐ ┌───────┐ ┌───────┐ ┌────────┐
│ input │───►│ func1 │───►│ func2 │───►│ ... │───►│ funcN │───►│ result │
└───────┘ └───────┘ └───────┘ └───────┘ └───────┘ └────────┘

It's important to note that functions passed to pipe must have a single argument because they are only called with a single argument.

When to Use

This is useful in combination with data-last functions as a simulation of methods:

as.map(f).filter(g)

becomes:

import { pipe, Array } from "effect"
pipe(as, Array.map(f), Array.filter(g))

Example (Chaining Arithmetic Operations)

import { pipe } from "effect"
// Define simple arithmetic operations
const increment = (x: number) => x + 1
const double = (x: number) => x * 2
const subtractTen = (x: number) => x - 10
// Sequentially apply these operations using `pipe`
const result = pipe(5, increment, double, subtractTen)
console.log(result)
// Output: 2

@since2.0.0

pipe
(1, divide(2))
Error ts(2554) ― Expected 2 arguments, but got 1.
Error ts(2345) ― Argument of type 'number' is not assignable to parameter of type '(a: number) => never'.

You can always make a version of divide to fit that signature:

const
const curriedDivide: (n2: number) => (n1: number) => number
curriedDivide
= (
n2: number
n2
: number) => (
n1: number
n1
: number) =>
n1: number
n1
/
n2: number
n2
;

However, because TypeScript supports function overloads, it is possible to do better and support both signatures at the same time, under the divide name. This is indeed what Effect does with many of the methods it exposes. This is what makes Array.map, Effect.map, Effect.provide and others suitable for both direct usage and usage inside a pipe.

Note

While many common Effect methods are dual, you will not typically have to implement dual functions yourself for application code. However, if you do, check out Function.dual, which makes it easy to do this in a consistent way, for example to implement a divide function that support both divide(n1, n2) and divide(n2)(n1).

map

map transforms an inner value into another inner value. It’s commonplace now in many languages to transform arrays. In JavaScript, it’s not surprising that [1, 2, 3].map(n => n + 1) yields [2, 3, 4]. But the concept doesn’t only apply to arrays. It can also apply to other types of value containers, including those that have only a single inner value, such as an Effect or a Stream.

It might make more sense if you think about map from a type perspective. You can think of Array.map as one that transforms Array<I> via a function (I) => O into Array<O>:

type
type ArrayMap = <I, O>(container: Array<I>, operation: (inner: I) => O) => Array<O>
ArrayMap
= <
function (type parameter) I in <I, O>(container: Array<I>, operation: (inner: I) => O): Array<O>
I
,
function (type parameter) O in <I, O>(container: Array<I>, operation: (inner: I) => O): Array<O>
O
>(
container: I[]
container
:
interface Array<T>
Array
<
function (type parameter) I in <I, O>(container: Array<I>, operation: (inner: I) => O): Array<O>
I
>,
operation: (inner: I) => O
operation
: (
inner: I
inner
:
function (type parameter) I in <I, O>(container: Array<I>, operation: (inner: I) => O): Array<O>
I
) =>
function (type parameter) O in <I, O>(container: Array<I>, operation: (inner: I) => O): Array<O>
O
) =>
interface Array<T>
Array
<
function (type parameter) O in <I, O>(container: Array<I>, operation: (inner: I) => O): Array<O>
O
>

In the section above about module methods, Array.forEach was used as an example of what Effect exposes under its Array module. But Array.map also exists:

import {
import Array
Array
} from 'effect';
const result =
import Array
Array
.
const map: <number[], number>(self: number[], f: (a: number, i: number) => number) => number[] (+1 overload)

@since2.0.0

map
([1, 2, 3],
n: number
n
=>
n: number
n
+ 1) // [2, 3, 4]
const result: number[]
Note

Array.map follows the module method convention, with the subject (the array), being its first argument. Array.map is also a dual function, so you can naturally use it in a pipe:

import {
import Array
Array
,
function pipe<A>(a: A): A (+19 overloads)

Pipes the value of an expression into a pipeline of functions.

Details

The pipe function is a utility that allows us to compose functions in a readable and sequential manner. It takes the output of one function and passes it as the input to the next function in the pipeline. This enables us to build complex transformations by chaining multiple functions together.

import { pipe } from "effect"
const result = pipe(input, func1, func2, ..., funcN)

In this syntax, input is the initial value, and func1, func2, ..., funcN are the functions to be applied in sequence. The result of each function becomes the input for the next function, and the final result is returned.

Here's an illustration of how pipe works:

┌───────┐ ┌───────┐ ┌───────┐ ┌───────┐ ┌───────┐ ┌────────┐
│ input │───►│ func1 │───►│ func2 │───►│ ... │───►│ funcN │───►│ result │
└───────┘ └───────┘ └───────┘ └───────┘ └───────┘ └────────┘

It's important to note that functions passed to pipe must have a single argument because they are only called with a single argument.

When to Use

This is useful in combination with data-last functions as a simulation of methods:

as.map(f).filter(g)

becomes:

import { pipe, Array } from "effect"
pipe(as, Array.map(f), Array.filter(g))

Example (Chaining Arithmetic Operations)

import { pipe } from "effect"
// Define simple arithmetic operations
const increment = (x: number) => x + 1
const double = (x: number) => x * 2
const subtractTen = (x: number) => x - 10
// Sequentially apply these operations using `pipe`
const result = pipe(5, increment, double, subtractTen)
console.log(result)
// Output: 2

@since2.0.0

pipe
} from 'effect'
const
const result: number[]
result
=
pipe<number[], number[]>(a: number[], ab: (a: number[]) => number[]): number[] (+19 overloads)

Pipes the value of an expression into a pipeline of functions.

Details

The pipe function is a utility that allows us to compose functions in a readable and sequential manner. It takes the output of one function and passes it as the input to the next function in the pipeline. This enables us to build complex transformations by chaining multiple functions together.

import { pipe } from "effect"
const result = pipe(input, func1, func2, ..., funcN)

In this syntax, input is the initial value, and func1, func2, ..., funcN are the functions to be applied in sequence. The result of each function becomes the input for the next function, and the final result is returned.

Here's an illustration of how pipe works:

┌───────┐ ┌───────┐ ┌───────┐ ┌───────┐ ┌───────┐ ┌────────┐
│ input │───►│ func1 │───►│ func2 │───►│ ... │───►│ funcN │───►│ result │
└───────┘ └───────┘ └───────┘ └───────┘ └───────┘ └────────┘

It's important to note that functions passed to pipe must have a single argument because they are only called with a single argument.

When to Use

This is useful in combination with data-last functions as a simulation of methods:

as.map(f).filter(g)

becomes:

import { pipe, Array } from "effect"
pipe(as, Array.map(f), Array.filter(g))

Example (Chaining Arithmetic Operations)

import { pipe } from "effect"
// Define simple arithmetic operations
const increment = (x: number) => x + 1
const double = (x: number) => x * 2
const subtractTen = (x: number) => x - 10
// Sequentially apply these operations using `pipe`
const result = pipe(5, increment, double, subtractTen)
console.log(result)
// Output: 2

@since2.0.0

pipe
(
[1, 2, 3],
import Array
Array
.
const map: <number[], number>(f: (a: number, i: number) => number) => (self: number[]) => number[] (+1 overload)

@since2.0.0

map
(
n: number
n
=> 1 +
n: number
n
)
)

Let’s put both signatures of Array.map together for clarity:

interface
interface ArrayMap
ArrayMap
{
<
function (type parameter) I in <I, O>(container: Array<I>, operation: (inner: I) => O): Array<O>
I
,
function (type parameter) O in <I, O>(container: Array<I>, operation: (inner: I) => O): Array<O>
O
>(
container: I[]
container
:
interface Array<T>
Array
<
function (type parameter) I in <I, O>(container: Array<I>, operation: (inner: I) => O): Array<O>
I
>,
operation: (inner: I) => O
operation
: (
inner: I
inner
:
function (type parameter) I in <I, O>(container: Array<I>, operation: (inner: I) => O): Array<O>
I
) =>
function (type parameter) O in <I, O>(container: Array<I>, operation: (inner: I) => O): Array<O>
O
):
interface Array<T>
Array
<
function (type parameter) O in <I, O>(container: Array<I>, operation: (inner: I) => O): Array<O>
O
>;
<
function (type parameter) I in <I, O>(operation: (inner: I) => O): (container: Array<I>) => Array<O>
I
,
function (type parameter) O in <I, O>(operation: (inner: I) => O): (container: Array<I>) => Array<O>
O
>(
operation: (inner: I) => O
operation
: (
inner: I
inner
:
function (type parameter) I in <I, O>(operation: (inner: I) => O): (container: Array<I>) => Array<O>
I
) =>
function (type parameter) O in <I, O>(operation: (inner: I) => O): (container: Array<I>) => Array<O>
O
): (
container: I[]
container
:
interface Array<T>
Array
<
function (type parameter) I in <I, O>(operation: (inner: I) => O): (container: Array<I>) => Array<O>
I
>) =>
interface Array<T>
Array
<
function (type parameter) O in <I, O>(operation: (inner: I) => O): (container: Array<I>) => Array<O>
O
>;
}

Like I said, arrays are not the only thing you can map over. You can map over an Effect, or a Stream, for example. What would the signature of Effect.map look like?

import type {
interface Effect<out A, out E = never, out R = never>

The Effect interface defines a value that describes a workflow or job, which can succeed or fail.

Details

The Effect interface represents a computation that can model a workflow involving various types of operations, such as synchronous, asynchronous, concurrent, and parallel interactions. It operates within a context of type R, and the result can either be a success with a value of type A or a failure with an error of type E. The Effect is designed to handle complex interactions with external resources, offering advanced features such as fiber-based concurrency, scheduling, interruption handling, and scalability. This makes it suitable for tasks that require fine-grained control over concurrency and error management.

To execute an Effect value, you need a Runtime, which provides the environment necessary to run and manage the computation.

@since2.0.0

@since2.0.0

Effect
} from 'effect/Effect';
type
type ArrayMap = <I, O>(container: Array<I>, operation: (inner: I) => O) => Array<O>
ArrayMap
= <
function (type parameter) I in <I, O>(container: Array<I>, operation: (inner: I) => O): Array<O>
I
,
function (type parameter) O in <I, O>(container: Array<I>, operation: (inner: I) => O): Array<O>
O
>(
container: I[]
container
:
interface Array<T>
Array
<
function (type parameter) I in <I, O>(container: Array<I>, operation: (inner: I) => O): Array<O>
I
>,
operation: (inner: I) => O
operation
: (
inner: I
inner
:
function (type parameter) I in <I, O>(container: Array<I>, operation: (inner: I) => O): Array<O>
I
) =>
function (type parameter) O in <I, O>(container: Array<I>, operation: (inner: I) => O): Array<O>
O
) =>
interface Array<T>
Array
<
function (type parameter) O in <I, O>(container: Array<I>, operation: (inner: I) => O): Array<O>
O
>
// ↓ ↓ ↓
type
type EffectMap = <I, O>(container: Effect<I>, operation: (inner: I) => O) => Effect<O>
EffectMap
= <
function (type parameter) I in <I, O>(container: Effect<I>, operation: (inner: I) => O): Effect<O>
I
,
function (type parameter) O in <I, O>(container: Effect<I>, operation: (inner: I) => O): Effect<O>
O
>(
container: Effect<I, never, never>
container
:
interface Effect<out A, out E = never, out R = never>

The Effect interface defines a value that describes a workflow or job, which can succeed or fail.

Details

The Effect interface represents a computation that can model a workflow involving various types of operations, such as synchronous, asynchronous, concurrent, and parallel interactions. It operates within a context of type R, and the result can either be a success with a value of type A or a failure with an error of type E. The Effect is designed to handle complex interactions with external resources, offering advanced features such as fiber-based concurrency, scheduling, interruption handling, and scalability. This makes it suitable for tasks that require fine-grained control over concurrency and error management.

To execute an Effect value, you need a Runtime, which provides the environment necessary to run and manage the computation.

@since2.0.0

@since2.0.0

Effect
<
function (type parameter) I in <I, O>(container: Effect<I>, operation: (inner: I) => O): Effect<O>
I
>,
operation: (inner: I) => O
operation
: (
inner: I
inner
:
function (type parameter) I in <I, O>(container: Effect<I>, operation: (inner: I) => O): Effect<O>
I
) =>
function (type parameter) O in <I, O>(container: Effect<I>, operation: (inner: I) => O): Effect<O>
O
) =>
interface Effect<out A, out E = never, out R = never>

The Effect interface defines a value that describes a workflow or job, which can succeed or fail.

Details

The Effect interface represents a computation that can model a workflow involving various types of operations, such as synchronous, asynchronous, concurrent, and parallel interactions. It operates within a context of type R, and the result can either be a success with a value of type A or a failure with an error of type E. The Effect is designed to handle complex interactions with external resources, offering advanced features such as fiber-based concurrency, scheduling, interruption handling, and scalability. This makes it suitable for tasks that require fine-grained control over concurrency and error management.

To execute an Effect value, you need a Runtime, which provides the environment necessary to run and manage the computation.

@since2.0.0

@since2.0.0

Effect
<
function (type parameter) O in <I, O>(container: Effect<I>, operation: (inner: I) => O): Effect<O>
O
>

With Effect.map, you transform the success value in the Effect. Let’s say you have an Effect<number>. With Effect.map, you can transform that number into another number, or another type entirely:

import {
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
} from 'effect';
const numberToNumber =
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
.
const map: <number, never, never, number>(self: Effect.Effect<number, never, never>, f: (a: number) => number) => Effect.Effect<number, never, never> (+1 overload)

Transforms the value inside an effect by applying a function to it.

Syntax

const mappedEffect = pipe(myEffect, Effect.map(transformation))
// or
const mappedEffect = Effect.map(myEffect, transformation)
// or
const mappedEffect = myEffect.pipe(Effect.map(transformation))

Details

map takes a function and applies it to the value contained within an effect, creating a new effect with the transformed value.

It's important to note that effects are immutable, meaning that the original effect is not modified. Instead, a new effect is returned with the updated value.

Example (Adding a Service Charge)

import { pipe, Effect } from "effect"
const addServiceCharge = (amount: number) => amount + 1
const fetchTransactionAmount = Effect.promise(() => Promise.resolve(100))
const finalAmount = pipe(
fetchTransactionAmount,
Effect.map(addServiceCharge)
)
Effect.runPromise(finalAmount).then(console.log)
// Output: 101

@seemapError for a version that operates on the error channel.

@seemapBoth for a version that operates on both channels.

@seeflatMap or andThen for a version that can return a new effect.

@since2.0.0

map
(
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
.
const succeed: <number>(value: number) => Effect.Effect<number, never, never>

Creates an Effect that always succeeds with a given value.

When to Use

Use this function when you need an effect that completes successfully with a specific value without any errors or external dependencies.

Example (Creating a Successful Effect)

import { Effect } from "effect"
// Creating an effect that represents a successful scenario
//
// ┌─── Effect<number, never, never>
// ▼
const success = Effect.succeed(42)

@seefail to create an effect that represents a failure.

@since2.0.0

succeed
(1),
n: number
n
=>
n: number
n
+ 1)
const numberToNumber: Effect.Effect<number, never, never>
// Would yield 2
const stringToString =
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
.
const map: <string, never, never, string>(self: Effect.Effect<string, never, never>, f: (a: string) => string) => Effect.Effect<string, never, never> (+1 overload)

Transforms the value inside an effect by applying a function to it.

Syntax

const mappedEffect = pipe(myEffect, Effect.map(transformation))
// or
const mappedEffect = Effect.map(myEffect, transformation)
// or
const mappedEffect = myEffect.pipe(Effect.map(transformation))

Details

map takes a function and applies it to the value contained within an effect, creating a new effect with the transformed value.

It's important to note that effects are immutable, meaning that the original effect is not modified. Instead, a new effect is returned with the updated value.

Example (Adding a Service Charge)

import { pipe, Effect } from "effect"
const addServiceCharge = (amount: number) => amount + 1
const fetchTransactionAmount = Effect.promise(() => Promise.resolve(100))
const finalAmount = pipe(
fetchTransactionAmount,
Effect.map(addServiceCharge)
)
Effect.runPromise(finalAmount).then(console.log)
// Output: 101

@seemapError for a version that operates on the error channel.

@seemapBoth for a version that operates on both channels.

@seeflatMap or andThen for a version that can return a new effect.

@since2.0.0

map
(
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
.
const succeed: <string>(value: string) => Effect.Effect<string, never, never>

Creates an Effect that always succeeds with a given value.

When to Use

Use this function when you need an effect that completes successfully with a specific value without any errors or external dependencies.

Example (Creating a Successful Effect)

import { Effect } from "effect"
// Creating an effect that represents a successful scenario
//
// ┌─── Effect<number, never, never>
// ▼
const success = Effect.succeed(42)

@seefail to create an effect that represents a failure.

@since2.0.0

succeed
('Hello'),
word: string
word
=>
word: string
word
[0]!)
const stringToString: Effect.Effect<string, never, never>
// Would yield "H"
const stringToNumber =
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
.
const map: <string, never, never, number>(self: Effect.Effect<string, never, never>, f: (a: string) => number) => Effect.Effect<number, never, never> (+1 overload)

Transforms the value inside an effect by applying a function to it.

Syntax

const mappedEffect = pipe(myEffect, Effect.map(transformation))
// or
const mappedEffect = Effect.map(myEffect, transformation)
// or
const mappedEffect = myEffect.pipe(Effect.map(transformation))

Details

map takes a function and applies it to the value contained within an effect, creating a new effect with the transformed value.

It's important to note that effects are immutable, meaning that the original effect is not modified. Instead, a new effect is returned with the updated value.

Example (Adding a Service Charge)

import { pipe, Effect } from "effect"
const addServiceCharge = (amount: number) => amount + 1
const fetchTransactionAmount = Effect.promise(() => Promise.resolve(100))
const finalAmount = pipe(
fetchTransactionAmount,
Effect.map(addServiceCharge)
)
Effect.runPromise(finalAmount).then(console.log)
// Output: 101

@seemapError for a version that operates on the error channel.

@seemapBoth for a version that operates on both channels.

@seeflatMap or andThen for a version that can return a new effect.

@since2.0.0

map
(
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
.
const succeed: <string>(value: string) => Effect.Effect<string, never, never>

Creates an Effect that always succeeds with a given value.

When to Use

Use this function when you need an effect that completes successfully with a specific value without any errors or external dependencies.

Example (Creating a Successful Effect)

import { Effect } from "effect"
// Creating an effect that represents a successful scenario
//
// ┌─── Effect<number, never, never>
// ▼
const success = Effect.succeed(42)

@seefail to create an effect that represents a failure.

@since2.0.0

succeed
('Hello'),
word: string
word
=>
word: string
word
.
String.length: number

Returns the length of a String object.

length
)
const stringToNumber: Effect.Effect<number, never, never>
// Would yield 5

As an exercise, what do you think is the return type of Stream.map in this function?

Stream.map(Stream.succeed(1), (n) => n + 1)

flatMap

A closely related concept is flatMap. It is different from map in that the return type of the callback is the same as the container, not the contained value.

For an array, the flatMap functor (the function that applies the transformation, the callback), must return an Array:

import {
import Array
Array
} from "effect"
const result =
import Array
Array
.
const flatMap: <number, number>(self: readonly [number, ...number[]], f: (a: number, i: number) => readonly [number, ...number[]]) => [number, ...number[]] (+2 overloads)

Applies a function to each element in an array and returns a new array containing the concatenated mapped elements.

@since2.0.0

flatMap
([1, 2, 3],
x: number
x
=> [
x: number
x
])
const result: [number, ...number[]]
var console: Console

The console module provides a simple debugging console that is similar to the JavaScript console mechanism provided by web browsers.

The module exports two specific components:

  • A Console class with methods such as console.log(), console.error() andconsole.warn() that can be used to write to any Node.js stream.
  • A global console instance configured to write to process.stdout and process.stderr. The global console can be used without callingrequire('console').

Warning: The global console object's methods are neither consistently synchronous like the browser APIs they resemble, nor are they consistently asynchronous like all other Node.js streams. See the note on process I/O for more information.

Example using the global console:

console.log('hello world');
// Prints: hello world, to stdout
console.log('hello %s', 'world');
// Prints: hello world, to stdout
console.error(new Error('Whoops, something bad happened'));
// Prints error message and stack trace to stderr:
// Error: Whoops, something bad happened
// at [eval]:5:15
// at Script.runInThisContext (node:vm:132:18)
// at Object.runInThisContext (node:vm:309:38)
// at node:internal/process/execution:77:19
// at [eval]-wrapper:6:22
// at evalScript (node:internal/process/execution:76:60)
// at node:internal/main/eval_string:23:3
const name = 'Will Robinson';
console.warn(`Danger ${name}! Danger!`);
// Prints: Danger Will Robinson! Danger!, to stderr

Example using the Console class:

const out = getStreamSomehow();
const err = getStreamSomehow();
const myConsole = new console.Console(out, err);
myConsole.log('hello world');
// Prints: hello world, to out
myConsole.log('hello %s', 'world');
// Prints: hello world, to out
myConsole.error(new Error('Whoops, something bad happened'));
// Prints: [Error: Whoops, something bad happened], to err
const name = 'Will Robinson';
myConsole.warn(`Danger ${name}! Danger!`);
// Prints: Danger Will Robinson! Danger!, to err

@seesource

console
.
Console.log(message?: any, ...optionalParams: any[]): void

Prints to stdout with newline. Multiple arguments can be passed, with the first used as the primary message and all additional used as substitution values similar to printf(3) (the arguments are all passed to util.format()).

const count = 5;
console.log('count: %d', count);
// Prints: count: 5, to stdout
console.log('count:', count);
// Prints: count: 5, to stdout

See util.format() for more information.

@sincev0.1.100

log
(
const result: [number, ...number[]]
result
) // logs [1, 2, 3]

Back to Effect, we can replicate the earlier example where we added 1 to the success value in an Effect, but using Effect.flatMap. The functor will have to return an Effect instead of just a number:

import {
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
} from 'effect';
const result =
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
.
const flatMap: <number, never, never, number, never, never>(self: Effect.Effect<number, never, never>, f: (a: number) => Effect.Effect<number, never, never>) => Effect.Effect<number, never, never> (+1 overload)

Chains effects to produce new Effect instances, useful for combining operations that depend on previous results.

Syntax

const flatMappedEffect = pipe(myEffect, Effect.flatMap(transformation))
// or
const flatMappedEffect = Effect.flatMap(myEffect, transformation)
// or
const flatMappedEffect = myEffect.pipe(Effect.flatMap(transformation))

Details

flatMap lets you sequence effects so that the result of one effect can be used in the next step. It is similar to flatMap used with arrays but works specifically with Effect instances, allowing you to avoid deeply nested effect structures.

Since effects are immutable, flatMap always returns a new effect instead of changing the original one.

When to Use

Use flatMap when you need to chain multiple effects, ensuring that each step produces a new Effect while flattening any nested effects that may occur.

Example

import { pipe, Effect } from "effect"
// Function to apply a discount safely to a transaction amount
const applyDiscount = (
total: number,
discountRate: number
): Effect.Effect<number, Error> =>
discountRate === 0
? Effect.fail(new Error("Discount rate cannot be zero"))
: Effect.succeed(total - (total * discountRate) / 100)
// Simulated asynchronous task to fetch a transaction amount from database
const fetchTransactionAmount = Effect.promise(() => Promise.resolve(100))
// Chaining the fetch and discount application using `flatMap`
const finalAmount = pipe(
fetchTransactionAmount,
Effect.flatMap((amount) => applyDiscount(amount, 5))
)
Effect.runPromise(finalAmount).then(console.log)
// Output: 95

@seetap for a version that ignores the result of the effect.

@since2.0.0

flatMap
(
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
.
const succeed: <number>(value: number) => Effect.Effect<number, never, never>

Creates an Effect that always succeeds with a given value.

When to Use

Use this function when you need an effect that completes successfully with a specific value without any errors or external dependencies.

Example (Creating a Successful Effect)

import { Effect } from "effect"
// Creating an effect that represents a successful scenario
//
// ┌─── Effect<number, never, never>
// ▼
const success = Effect.succeed(42)

@seefail to create an effect that represents a failure.

@since2.0.0

succeed
(1),
n: number
n
=>
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
.
const succeed: <number>(value: number) => Effect.Effect<number, never, never>

Creates an Effect that always succeeds with a given value.

When to Use

Use this function when you need an effect that completes successfully with a specific value without any errors or external dependencies.

Example (Creating a Successful Effect)

import { Effect } from "effect"
// Creating an effect that represents a successful scenario
//
// ┌─── Effect<number, never, never>
// ▼
const success = Effect.succeed(42)

@seefail to create an effect that represents a failure.

@since2.0.0

succeed
(
n: number
n
+ 1))
const result: Effect.Effect<number, never, never>

This is very useful when the transformation itself is an effectful computation (i.e. an Effect, that may be async, or fail).

Tip

If you find yourself dealing with an Effect wrapping an Effect (Effect<Effect<A>>), you might have used Effect.map where you should have used Effect.flatMap.

import {
import Data
Data
,
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
} from 'effect';
11 collapsed lines
interface
interface Item
Item
{
Item.id: string
id
: string;
Item.name: string
name
: string;
}
class
class FetchItemError
FetchItemError
extends
import Data
Data
.
const TaggedError: <"FetchItemError">(tag: "FetchItemError") => new <A>(args: Equals<A, {}> extends true ? void : { readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }) => YieldableError & {
readonly _tag: "FetchItemError";
} & Readonly<A>

@since2.0.0

TaggedError
("FetchItemError") {}
class
class FetchPriceError
FetchPriceError
extends
import Data
Data
.
const TaggedError: <"FetchPriceError">(tag: "FetchPriceError") => new <A>(args: Equals<A, {}> extends true ? void : { readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }) => YieldableError & {
readonly _tag: "FetchPriceError";
} & Readonly<A>

@since2.0.0

TaggedError
("FetchPriceError") {}
declare const
const fetchBestSellerItem: Effect.Effect<Item, FetchItemError, never>
fetchBestSellerItem
:
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
.
interface Effect<out A, out E = never, out R = never>

The Effect interface defines a value that describes a workflow or job, which can succeed or fail.

Details

The Effect interface represents a computation that can model a workflow involving various types of operations, such as synchronous, asynchronous, concurrent, and parallel interactions. It operates within a context of type R, and the result can either be a success with a value of type A or a failure with an error of type E. The Effect is designed to handle complex interactions with external resources, offering advanced features such as fiber-based concurrency, scheduling, interruption handling, and scalability. This makes it suitable for tasks that require fine-grained control over concurrency and error management.

To execute an Effect value, you need a Runtime, which provides the environment necessary to run and manage the computation.

@since2.0.0

@since2.0.0

Effect
<
interface Item
Item
,
class FetchItemError
FetchItemError
>;
declare const
const fetchItemCurrentPrice: (item: Item) => Effect.Effect<number, FetchPriceError>
fetchItemCurrentPrice
: (
item: Item
item
:
interface Item
Item
) =>
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
.
interface Effect<out A, out E = never, out R = never>

The Effect interface defines a value that describes a workflow or job, which can succeed or fail.

Details

The Effect interface represents a computation that can model a workflow involving various types of operations, such as synchronous, asynchronous, concurrent, and parallel interactions. It operates within a context of type R, and the result can either be a success with a value of type A or a failure with an error of type E. The Effect is designed to handle complex interactions with external resources, offering advanced features such as fiber-based concurrency, scheduling, interruption handling, and scalability. This makes it suitable for tasks that require fine-grained control over concurrency and error management.

To execute an Effect value, you need a Runtime, which provides the environment necessary to run and manage the computation.

@since2.0.0

@since2.0.0

Effect
<number,
class FetchPriceError
FetchPriceError
>;
declare const
const applyUserDiscounts: (value: number) => Effect.Effect<number>
applyUserDiscounts
: (
value: number
value
: number) =>
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
.
interface Effect<out A, out E = never, out R = never>

The Effect interface defines a value that describes a workflow or job, which can succeed or fail.

Details

The Effect interface represents a computation that can model a workflow involving various types of operations, such as synchronous, asynchronous, concurrent, and parallel interactions. It operates within a context of type R, and the result can either be a success with a value of type A or a failure with an error of type E. The Effect is designed to handle complex interactions with external resources, offering advanced features such as fiber-based concurrency, scheduling, interruption handling, and scalability. This makes it suitable for tasks that require fine-grained control over concurrency and error management.

To execute an Effect value, you need a Runtime, which provides the environment necessary to run and manage the computation.

@since2.0.0

@since2.0.0

Effect
<number>;
const result =
const fetchBestSellerItem: Effect.Effect<Item, FetchItemError, never>
fetchBestSellerItem
.
Pipeable.pipe<Effect.Effect<Item, FetchItemError, never>, Effect.Effect<number, FetchItemError | FetchPriceError, never>, Effect.Effect<number, FetchItemError | FetchPriceError, never>>(this: Effect.Effect<...>, ab: (_: Effect.Effect<Item, FetchItemError, never>) => Effect.Effect<number, FetchItemError | FetchPriceError, never>, bc: (_: Effect.Effect<number, FetchItemError | FetchPriceError, never>) => Effect.Effect<...>): Effect.Effect<...> (+21 overloads)
pipe
(
const result: Effect.Effect<number, FetchItemError | FetchPriceError, never>
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
.
const flatMap: <Item, number, FetchPriceError, never>(f: (a: Item) => Effect.Effect<number, FetchPriceError, never>) => <E, R>(self: Effect.Effect<Item, E, R>) => Effect.Effect<number, FetchPriceError | E, R> (+1 overload)

Chains effects to produce new Effect instances, useful for combining operations that depend on previous results.

Syntax

const flatMappedEffect = pipe(myEffect, Effect.flatMap(transformation))
// or
const flatMappedEffect = Effect.flatMap(myEffect, transformation)
// or
const flatMappedEffect = myEffect.pipe(Effect.flatMap(transformation))

Details

flatMap lets you sequence effects so that the result of one effect can be used in the next step. It is similar to flatMap used with arrays but works specifically with Effect instances, allowing you to avoid deeply nested effect structures.

Since effects are immutable, flatMap always returns a new effect instead of changing the original one.

When to Use

Use flatMap when you need to chain multiple effects, ensuring that each step produces a new Effect while flattening any nested effects that may occur.

Example

import { pipe, Effect } from "effect"
// Function to apply a discount safely to a transaction amount
const applyDiscount = (
total: number,
discountRate: number
): Effect.Effect<number, Error> =>
discountRate === 0
? Effect.fail(new Error("Discount rate cannot be zero"))
: Effect.succeed(total - (total * discountRate) / 100)
// Simulated asynchronous task to fetch a transaction amount from database
const fetchTransactionAmount = Effect.promise(() => Promise.resolve(100))
// Chaining the fetch and discount application using `flatMap`
const finalAmount = pipe(
fetchTransactionAmount,
Effect.flatMap((amount) => applyDiscount(amount, 5))
)
Effect.runPromise(finalAmount).then(console.log)
// Output: 95

@seetap for a version that ignores the result of the effect.

@since2.0.0

flatMap
(
const fetchItemCurrentPrice: (item: Item) => Effect.Effect<number, FetchPriceError>
fetchItemCurrentPrice
),
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
.
const flatMap: <number, number, never, never>(f: (a: number) => Effect.Effect<number, never, never>) => <E, R>(self: Effect.Effect<number, E, R>) => Effect.Effect<number, E, R> (+1 overload)

Chains effects to produce new Effect instances, useful for combining operations that depend on previous results.

Syntax

const flatMappedEffect = pipe(myEffect, Effect.flatMap(transformation))
// or
const flatMappedEffect = Effect.flatMap(myEffect, transformation)
// or
const flatMappedEffect = myEffect.pipe(Effect.flatMap(transformation))

Details

flatMap lets you sequence effects so that the result of one effect can be used in the next step. It is similar to flatMap used with arrays but works specifically with Effect instances, allowing you to avoid deeply nested effect structures.

Since effects are immutable, flatMap always returns a new effect instead of changing the original one.

When to Use

Use flatMap when you need to chain multiple effects, ensuring that each step produces a new Effect while flattening any nested effects that may occur.

Example

import { pipe, Effect } from "effect"
// Function to apply a discount safely to a transaction amount
const applyDiscount = (
total: number,
discountRate: number
): Effect.Effect<number, Error> =>
discountRate === 0
? Effect.fail(new Error("Discount rate cannot be zero"))
: Effect.succeed(total - (total * discountRate) / 100)
// Simulated asynchronous task to fetch a transaction amount from database
const fetchTransactionAmount = Effect.promise(() => Promise.resolve(100))
// Chaining the fetch and discount application using `flatMap`
const finalAmount = pipe(
fetchTransactionAmount,
Effect.flatMap((amount) => applyDiscount(amount, 5))
)
Effect.runPromise(finalAmount).then(console.log)
// Output: 95

@seetap for a version that ignores the result of the effect.

@since2.0.0

flatMap
(
const applyUserDiscounts: (value: number) => Effect.Effect<number>
applyUserDiscounts
)
)
Note

Effect.flatMap will combine the errors from the original Effect and the Effect used in the callback.

Either<A, B>

Describes a value that may be of two types: A (“right”) and B (“left”). Having these allows representing common scenarios like success/failure or certain unions.

Note

Yes, it si confusing that, looking at the type signature, “right” is the first one, to the left, and left is the second one, to the right. Either<Right, Left>. One way to think about it is: the first argument is the “right” (as in correct) value, whereas the other one is the “not right” value.

You can obtain A with Either.getRight, and B with Either.getLeft. Either.isRight and Either.isLeft are type guards. You guessed it: you can also map with Either.map! There is also Either.mapLeft and Either.mapRight. You will find many more useful methods under the Either namespace.

Tip

Many Effect methods will interpret Either<A, B> as Effect<A, B>, meaning the “left” case is interpreted as a failure. This makes it very easy to work with Either alongside Effect.

Option<T>

Option is defined as Option<A> and allows representing both A and the lack of A. The Option namespace exposes many useful methods to unwrap the value: Option.getOrNull, Option.getOrThrow… You can find out if there is a value or not via Option.isSome or Option.isNone.

Tip

Make sure to import Option from the effect package when working with these methods, since normally Option will already exist as a global in your module, because it’s part of the Web standards.

Tip

Many Effect methods will happily take Option, interpreting the none case as a failure of type NoSuchElementException.