Skip to content

Myths About Effect

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 does perform 500x slower if you are comparing:

const
const result: number
result
= 1 + 1

to

import {
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
} from "effect"
const
const result: number
result
=
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
.
const runSync: <number, never>(effect: Effect.Effect<number, never, never>) => number

Executes an effect synchronously, running it immediately and returning the result.

When to Use

Use runSync to run an effect that does not fail and does not include any asynchronous operations.

If the effect fails or involves asynchronous work, it will throw an error, and execution will stop where the failure or async operation occurs.

@seerunSyncExit for a version that returns an Exit type instead of throwing an error.

@example

// Title: Synchronous Logging
import { Effect } from "effect"
const program = Effect.sync(() => {
console.log("Hello, World!")
return 1
})
const result = Effect.runSync(program)
// Output: Hello, World!
console.log(result)
// Output: 1

@example

// Title: Incorrect Usage with Failing or Async Effects import { Effect } from "effect"

try { // Attempt to run an effect that fails Effect.runSync(Effect.fail("my error")) } catch (e) { console.error(e) } // Output: // (FiberFailure) Error: my error

try { // Attempt to run an effect that involves async work Effect.runSync(Effect.promise(() => Promise.resolve(1))) } catch (e) { console.error(e) } // Output: // (FiberFailure) AsyncFiberException: Fiber #0 cannot be resolved synchronously. This is caused by using runSync on an effect that performs async work

@since2.0.0

runSync
(
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
.
const zipWith: <number, never, never, number, never, never, number>(self: Effect.Effect<number, never, never>, that: Effect.Effect<number, never, never>, f: (a: number, b: number) => number, options?: {
readonly concurrent?: boolean | undefined;
readonly batching?: boolean | "inherit" | undefined;
readonly concurrentFinalizers?: boolean | undefined;
}) => Effect.Effect<...> (+1 overload)

Combines two effects sequentially and applies a function to their results to produce a single value.

When to Use

The zipWith function is similar to

zip

, but instead of returning a tuple of results, it applies a provided function to the results of the two effects, combining them into a single value.

Concurrency

By default, the effects are run sequentially. To execute them concurrently, use the { concurrent: true } option.

@example

// Title: Combining Effects with a Custom Function
import { Effect } from "effect"
const task1 = Effect.succeed(1).pipe(
Effect.delay("200 millis"),
Effect.tap(Effect.log("task1 done"))
)
const task2 = Effect.succeed("hello").pipe(
Effect.delay("100 millis"),
Effect.tap(Effect.log("task2 done"))
)
const task3 = Effect.zipWith(
task1,
task2,
// Combines results into a single value
(number, string) => number + string.length
)
Effect.runPromise(task3).then(console.log)
// Output:
// timestamp=... level=INFO fiber=#3 message="task1 done"
// timestamp=... level=INFO fiber=#2 message="task2 done"
// 6

@since2.0.0

zipWith
(
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.

@seefail to create an effect that represents a failure.

@example

// Title: 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)

@since2.0.0

succeed
(1),
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.

@seefail to create an effect that represents a failure.

@example

// Title: 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)

@since2.0.0

succeed
(1), (
a: number
a
,
b: number
b
) =>
a: number
a
+
b: number
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.

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.

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.

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:

A short list of commonly used modules:

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.

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.