Effect by Example: Reading Files

Tags:

Note: All examples on this page require the FileSystem service to be provided, you can do this by providing the implementation of FileSystem for your platform at any point in your program

import {
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
} from "effect";
import {
import FileSystem
FileSystem
} from "@effect/platform";
import {
import NodeFileSystem
NodeFileSystem
} from "@effect/platform-node";
declare const
const main: Effect.Effect<void, never, FileSystem.FileSystem>
main
:
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
<void, never,
import FileSystem
FileSystem
.
interface FileSystem

@since1.0.0

@since1.0.0

FileSystem
>;
const
const runnable: Effect.Effect<void, never, never>
runnable
=
const main: Effect.Effect<void, never, FileSystem.FileSystem>
main
.
Pipeable.pipe<Effect.Effect<void, never, FileSystem.FileSystem>, Effect.Effect<void, never, never>>(this: Effect.Effect<...>, ab: (_: Effect.Effect<void, never, FileSystem.FileSystem>) => Effect.Effect<...>): Effect.Effect<...> (+21 overloads)
pipe
(
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
.
const provide: <FileSystem.FileSystem, never, never>(layer: Layer<FileSystem.FileSystem, never, never>) => <A, E, R>(self: Effect.Effect<A, E, R>) => Effect.Effect<...> (+9 overloads)

Provides necessary dependencies to an effect, removing its environmental requirements.

Details

This function allows you to supply the required environment for an effect. The environment can be provided in the form of one or more Layers, a Context, a Runtime, or a ManagedRuntime. Once the environment is provided, the effect can run without requiring external dependencies.

You can compose layers to create a modular and reusable way of setting up the environment for effects. For example, layers can be used to configure databases, logging services, or any other required dependencies.

Example

import { Context, Effect, Layer } from "effect"
class Database extends Context.Tag("Database")<
Database,
{ readonly query: (sql: string) => Effect.Effect<Array<unknown>> }
>() {}
const DatabaseLive = Layer.succeed(
Database,
{
// Simulate a database query
query: (sql: string) => Effect.log(`Executing query: ${sql}`).pipe(Effect.as([]))
}
)
// ┌─── Effect<unknown[], never, Database>
// ▼
const program = Effect.gen(function*() {
const database = yield* Database
const result = yield* database.query("SELECT * FROM users")
return result
})
// ┌─── Effect<unknown[], never, never>
// ▼
const runnable = Effect.provide(program, DatabaseLive)
Effect.runPromise(runnable).then(console.log)
// Output:
// timestamp=... level=INFO fiber=#0 message="Executing query: SELECT * FROM users"
// []

@seeprovideService for providing a service to an effect.

@since2.0.0

provide
(
import NodeFileSystem
NodeFileSystem
.
const layer: Layer<FileSystem.FileSystem, never, never>

@since1.0.0

layer
));

Reading a File as Bytes

import {
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
,
import Console
Console
} from "effect";
import {
import FileSystem
FileSystem
} from "@effect/platform";
const
const main: Effect.Effect<void, PlatformError, FileSystem.FileSystem>
main
=
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
.
const gen: <YieldWrap<Tag<FileSystem.FileSystem, FileSystem.FileSystem>> | YieldWrap<Effect.Effect<Uint8Array<ArrayBufferLike>, PlatformError, never>>, void>(f: (resume: Effect.Adapter) => Generator<...>) => Effect.Effect<...> (+1 overload)

Provides a way to write effectful code using generator functions, simplifying control flow and error handling.

When to Use

Effect.gen allows you to write code that looks and behaves like synchronous code, but it can handle asynchronous tasks, errors, and complex control flow (like loops and conditions). It helps make asynchronous code more readable and easier to manage.

The generator functions work similarly to async/await but with more explicit control over the execution of effects. You can yield* values from effects and return the final result at the end.

Example

import { Effect } from "effect"
const addServiceCharge = (amount: number) => amount + 1
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)
const fetchTransactionAmount = Effect.promise(() => Promise.resolve(100))
const fetchDiscountRate = Effect.promise(() => Promise.resolve(5))
export const program = Effect.gen(function* () {
const transactionAmount = yield* fetchTransactionAmount
const discountRate = yield* fetchDiscountRate
const discountedAmount = yield* applyDiscount(
transactionAmount,
discountRate
)
const finalAmount = addServiceCharge(discountedAmount)
return `Final amount to charge: ${finalAmount}`
})

@since2.0.0

gen
(function* () {
const
const fs: FileSystem.FileSystem
fs
= yield*
import FileSystem
FileSystem
.
const FileSystem: Tag<FileSystem.FileSystem, FileSystem.FileSystem>

@since1.0.0

@since1.0.0

FileSystem
;
const
const bytes: Uint8Array<ArrayBufferLike>
bytes
= yield*
const fs: FileSystem.FileSystem
fs
.
FileSystem.readFile: (path: string) => Effect.Effect<Uint8Array, PlatformError>

Read the contents of a file.

readFile
("file.txt");
});

Reading a File as Text

import {
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
,
import Console
Console
} from "effect";
import {
import FileSystem
FileSystem
} from "@effect/platform";
const
const main: Effect.Effect<void, PlatformError, FileSystem.FileSystem>
main
=
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
.
const gen: <YieldWrap<Tag<FileSystem.FileSystem, FileSystem.FileSystem>> | YieldWrap<Effect.Effect<string, PlatformError, never>>, void>(f: (resume: Effect.Adapter) => Generator<...>) => Effect.Effect<...> (+1 overload)

Provides a way to write effectful code using generator functions, simplifying control flow and error handling.

When to Use

Effect.gen allows you to write code that looks and behaves like synchronous code, but it can handle asynchronous tasks, errors, and complex control flow (like loops and conditions). It helps make asynchronous code more readable and easier to manage.

The generator functions work similarly to async/await but with more explicit control over the execution of effects. You can yield* values from effects and return the final result at the end.

Example

import { Effect } from "effect"
const addServiceCharge = (amount: number) => amount + 1
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)
const fetchTransactionAmount = Effect.promise(() => Promise.resolve(100))
const fetchDiscountRate = Effect.promise(() => Promise.resolve(5))
export const program = Effect.gen(function* () {
const transactionAmount = yield* fetchTransactionAmount
const discountRate = yield* fetchDiscountRate
const discountedAmount = yield* applyDiscount(
transactionAmount,
discountRate
)
const finalAmount = addServiceCharge(discountedAmount)
return `Final amount to charge: ${finalAmount}`
})

@since2.0.0

gen
(function* () {
const
const fs: FileSystem.FileSystem
fs
= yield*
import FileSystem
FileSystem
.
const FileSystem: Tag<FileSystem.FileSystem, FileSystem.FileSystem>

@since1.0.0

@since1.0.0

FileSystem
;
const
const string: string
string
= yield*
const fs: FileSystem.FileSystem
fs
.
FileSystem.readFileString: (path: string, encoding?: string) => Effect.Effect<string, PlatformError>

Read the contents of a file.

readFileString
("file.txt");
});

Reading a File Incrementally

import {
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
} from "effect";
import {
import FileSystem
FileSystem
} from "@effect/platform";
const
const main: Effect.Effect<void, PlatformError, FileSystem.FileSystem>
main
=
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
.
const gen: <YieldWrap<Tag<FileSystem.FileSystem, FileSystem.FileSystem>> | YieldWrap<Effect.Effect<FileSystem.File, PlatformError, Scope>> | YieldWrap<...> | YieldWrap<...> | YieldWrap<...>, void>(f: (resume: Effect.Adapter) => Generator<...>) => Effect.Effect<...> (+1 overload)

Provides a way to write effectful code using generator functions, simplifying control flow and error handling.

When to Use

Effect.gen allows you to write code that looks and behaves like synchronous code, but it can handle asynchronous tasks, errors, and complex control flow (like loops and conditions). It helps make asynchronous code more readable and easier to manage.

The generator functions work similarly to async/await but with more explicit control over the execution of effects. You can yield* values from effects and return the final result at the end.

Example

import { Effect } from "effect"
const addServiceCharge = (amount: number) => amount + 1
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)
const fetchTransactionAmount = Effect.promise(() => Promise.resolve(100))
const fetchDiscountRate = Effect.promise(() => Promise.resolve(5))
export const program = Effect.gen(function* () {
const transactionAmount = yield* fetchTransactionAmount
const discountRate = yield* fetchDiscountRate
const discountedAmount = yield* applyDiscount(
transactionAmount,
discountRate
)
const finalAmount = addServiceCharge(discountedAmount)
return `Final amount to charge: ${finalAmount}`
})

@since2.0.0

gen
(function* () {
const
const fs: FileSystem.FileSystem
fs
= yield*
import FileSystem
FileSystem
.
const FileSystem: Tag<FileSystem.FileSystem, FileSystem.FileSystem>

@since1.0.0

@since1.0.0

FileSystem
;
// opening a file is a scoped operation
const
const file: FileSystem.File
file
= yield*
const fs: FileSystem.FileSystem
fs
.
FileSystem.open: (path: string, options?: FileSystem.OpenFileOptions) => Effect.Effect<FileSystem.File, PlatformError, Scope>

Open a file at path with the specified options.

The file handle will be automatically closed when the scope is closed.

open
("file.txt");
// seek 4 bytes from the start
yield*
const file: FileSystem.File
file
.
File.seek: (offset: FileSystem.SizeInput, from: FileSystem.SeekMode) => Effect.Effect<void>
seek
(4, "start");
// read into a buffer
const
const buffer: Uint8Array<ArrayBuffer>
buffer
= new
var Uint8Array: Uint8ArrayConstructor
new (length: number) => Uint8Array<ArrayBuffer> (+6 overloads)
Uint8Array
(5);
const
const sizeRead: FileSystem.Size
sizeRead
= yield*
const file: FileSystem.File
file
.
File.read: (buffer: Uint8Array) => Effect.Effect<FileSystem.Size, PlatformError>
read
(
const buffer: Uint8Array<ArrayBuffer>
buffer
);
// seek 2 bytes from the current position
yield*
const file: FileSystem.File
file
.
File.seek: (offset: FileSystem.SizeInput, from: FileSystem.SeekMode) => Effect.Effect<void>
seek
(2, "current");
// read and allocate a buffer for result
const
const buffer2: Option<Uint8Array<ArrayBufferLike>>
buffer2
= yield*
const file: FileSystem.File
file
.
File.readAlloc: (size: FileSystem.SizeInput) => Effect.Effect<Option<Uint8Array>, PlatformError>
readAlloc
(5);
}).
Pipeable.pipe<Effect.Effect<void, PlatformError, FileSystem.FileSystem | Scope>, Effect.Effect<void, PlatformError, FileSystem.FileSystem>>(this: Effect.Effect<...>, ab: (_: Effect.Effect<...>) => Effect.Effect<...>): Effect.Effect<...> (+21 overloads)
pipe
(
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
.
const scoped: <A, E, R>(effect: Effect.Effect<A, E, R>) => Effect.Effect<A, E, Exclude<R, Scope>>

Scopes all resources used in an effect to the lifetime of the effect.

Details

This function ensures that all resources used within an effect are tied to its lifetime. Finalizers for these resources are executed automatically when the effect completes, whether through success, failure, or interruption. This guarantees proper resource cleanup without requiring explicit management.

@since2.0.0

scoped
);
// ensures the file is closed after the effect ends