DEV Community

Tanguy Bernard
Tanguy Bernard

Posted on

Functional Programming and Side Effect Management with Effect-ts

Side-effect management and code composition are major challenges in programming, particularly in the development of complex applications. Effect-ts is a library that provides elegant solutions to these problems, based on the principles of functional programming.

Let's take a look at how Effect-ts can improve our code, using the example of a post retrieval function.

The initial problem

Let's first consider a classic, trivial implementation of a getPostById function without Effect-ts:

async function getPostById(id) {
    try {
        const response = await fetch(`https://jsonplaceholder.typicode.com/posts/${id}`);
        if (response.status === 404) {
            throw new PostNotFoundError(“”);
        }
        if (!response.ok) {
            throw new HttpError(`HTTP error: ${response.status}`);
        }
        try {
            return await response.json();
        }
        catch (e) {
            throw new JsonParseError(JSON parsing error)
        }
    } catch (error) {
        if (error instanceof PostNotFoundError) {
            console.log(`Post missing. Falling back to a default.`)
            return { title: Sorry, post does not exist ! };
        }
       else if (error instanceof HttpError) {
            console.error(Exiting.);
        }
        throw error;
    }
}
Enter fullscreen mode Exit fullscreen mode

This approach has several drawbacks:

  1. Error handling is verbose and difficult to follow.
  2. Side effects (network calls, JSON parsing) are mixed in with business logic.
  3. Error typing is not explicit in the function signature.

The solution with Effect-ts

Here's how we can rewrite this function using Effect-ts:

const fetchWithEffect = (id: number): Effect.Effect<Response, HttpError, never> =>
    pipe(
        Effect.promise(() => fetch(`https://jsonplaceholder.typicode.com/posts/${id}`)),
        Effect.mapError((error) => new HttpError(error.message))
    )

const manageResponse = (res: Response): Effect.Effect<Post, HttpError | PostNotFoundError | JsonParseError, never> =>
    res.status === 404
        ? Effect.fail(new PostNotFoundError(""))
        : res.ok
            ? pipe(
                Effect.promise(() => res.json()),
                Effect.mapError(() => new JsonParseError("JSON parsing error"))
            )
            : Effect.fail(new HttpError(`Erreur HTTP: ${res.status}`));


const getPostIdWithEffect = (id: number): Effect.Effect<string, Error, never> =>
    pipe(
        fetchWithEffect(id),
        Effect.flatMap(manageResponse),
        Effect.catchTags({
            HttpError: (error) =>
                Effect.logError(`Exiting.`).pipe(
                    Effect.flatMap(() => Effect.fail(error))
                ),
            PostNotFoundError: () =>
                Effect.log(`Post missing. Falling back to a default.`).pipe(
                    Effect.map(() => ({title: "Sorry, post does not exist !"}))
                ),
        }),
        Effect.flatMap((post: Post) => Effect.succeed(post.title)),
        Effect.orElseFail(() => new Error("Request failed and no fallback available !"))
    );
Enter fullscreen mode Exit fullscreen mode

Advantages of the Effect-ts approach

  1. Safe error typing: Error types are explicitly declared in the effect signature, making the function's contract clearer.

  2. Better handling of side effects: Side effects are encapsulated in Effect structures, allowing a clear separation between effect description (what needs to be done) from its actual execution.

  3. Easier composition: The use of pipe and operators such as flatMap enable effects to be composed in a readable and maintainable way.

  4. Declarative error handling: The use of catchTags allows errors to be handled in a more declarative and typed way.

  5. Separation of concerns: The logic of data retrieval, error handling and transformation is separated into different functions.

Conclusion

By adopting Effect-ts and the principles of functional programming, we obtain more robust code that's easier to test and maintain.
Explicit side-effect management and functional composition allow us to reason more easily about our code, while benefiting from a powerful type system that catches potential errors right at compile-time.
This approach, although initially more verbose, offers better scalability and maintainability for complex projects, by providing stronger guarantees on the behavior of our code.

Further Exploration

Effect-ts is much more than just a functional library for managing side effects. It's actually a complete ecosystem offering a multitude of tools for the development of modern, robust TypeScript applications.

In addition to effects management and code composition, Effect-ts offers data structures such as Either and Option, as well as tools for data validation, database access and API development. These abstractions enable the creation of more robust and maintainable applications, a topic we may explore further in a future article.

Thanks

Thank you Paul Couthouis for introducing me to this library.

https://effect.website/

AWS GenAI LIVE image

How is generative AI increasing efficiency?

Join AWS GenAI LIVE! to find out how gen AI is reshaping productivity, streamlining processes, and driving innovation.

Learn more

Top comments (0)

Qodo Takeover

Introducing Qodo Gen 1.0: Transform Your Workflow with Agentic AI

Rather than just generating snippets, our agents understand your entire project context, can make decisions, use tools, and carry out tasks autonomously.

Read full post

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay