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@categorymodels@since2.0.0@categorymodels@since2.0.0@categorymodels
Effect
} from "effect";
import { import FileSystemFileSystem } from "@effect/platform"; import { import NodeFileSystemNodeFileSystem } from "@effect/platform-node"; declare const const main: Effect.Effect<void, never, FileSystem.FileSystem>main: import Effect
@since2.0.0@categorymodels@since2.0.0@categorymodels@since2.0.0@categorymodels
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@categoryModels@since2.0.0
Effect
<void, never, import FileSystemFileSystem.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@categorymodels@since2.0.0@categorymodels@since2.0.0@categorymodels
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 `Layer`s, 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.
@see{@link provideService} for providing a service to an effect.@example```ts 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" // [] ```@since2.0.0@categoryContext
provide
(import NodeFileSystemNodeFileSystem.const layer: Layer<FileSystem.FileSystem, never, never>
@since1.0.0@categorylayer
layer
));

Reading a File as Bytes

import { import Effect
@since2.0.0@categorymodels@since2.0.0@categorymodels@since2.0.0@categorymodels
Effect
, import ConsoleConsole } from "effect";
import { import FileSystemFileSystem } from "@effect/platform"; const const main: Effect.Effect<void, PlatformError, FileSystem.FileSystem>main = import Effect
@since2.0.0@categorymodels@since2.0.0@categorymodels@since2.0.0@categorymodels
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```ts 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@categoryCreating Effects
gen
(function* () {
const const fs: FileSystem.FileSystemfs = yield* import FileSystemFileSystem.const FileSystem: Tag<FileSystem.FileSystem, FileSystem.FileSystem>
@since1.0.0@categorymodel@since1.0.0@categorytag
FileSystem
;
const const bytes: Uint8Array<ArrayBufferLike>bytes = yield* const fs: FileSystem.FileSystemfs.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@categorymodels@since2.0.0@categorymodels@since2.0.0@categorymodels
Effect
, import ConsoleConsole } from "effect";
import { import FileSystemFileSystem } from "@effect/platform"; const const main: Effect.Effect<void, PlatformError, FileSystem.FileSystem>main = import Effect
@since2.0.0@categorymodels@since2.0.0@categorymodels@since2.0.0@categorymodels
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```ts 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@categoryCreating Effects
gen
(function* () {
const const fs: FileSystem.FileSystemfs = yield* import FileSystemFileSystem.const FileSystem: Tag<FileSystem.FileSystem, FileSystem.FileSystem>
@since1.0.0@categorymodel@since1.0.0@categorytag
FileSystem
;
const const string: stringstring = yield* const fs: FileSystem.FileSystemfs.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@categorymodels@since2.0.0@categorymodels@since2.0.0@categorymodels
Effect
} from "effect";
import { import FileSystemFileSystem } from "@effect/platform"; const const main: Effect.Effect<void, PlatformError, FileSystem.FileSystem>main = import Effect
@since2.0.0@categorymodels@since2.0.0@categorymodels@since2.0.0@categorymodels
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```ts 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@categoryCreating Effects
gen
(function* () {
const const fs: FileSystem.FileSystemfs = yield* import FileSystemFileSystem.const FileSystem: Tag<FileSystem.FileSystem, FileSystem.FileSystem>
@since1.0.0@categorymodel@since1.0.0@categorytag
FileSystem
;
// opening a file is a scoped operation const const file: FileSystem.Filefile = yield* const fs: FileSystem.FileSystemfs.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.Filefile.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> (+5 overloads)
Uint8Array
(5);
const const sizeRead: FileSystem.SizesizeRead = yield* const file: FileSystem.Filefile.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.Filefile.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.Filefile.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@categorymodels@since2.0.0@categorymodels@since2.0.0@categorymodels
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@categoryScoping, Resources & Finalization
scoped
);
// ensures the file is closed after the effect ends