Logo

dev-resources.site

for different kinds of informations.

The Effect Tax

Published at
5/13/2024
Categories
webdev
javascript
effect
typescript
Author
datner
Author
6 person written this
datner
open
The Effect Tax

It's been over a year now since I took the effect pill and I'll probably never develop the same way as I did before. I love effect, and I am absolutely a power-user.

But at my core, I am a front-end oriented developer. I care about things like bundle size and perceived performance, and I took this into consideration when adopting effect into my stack. Although the latter point was never a concern to me with effect (I've seen complex, front-end applications running at 120 FPS powered by Effect and React), the former point seemed to be just a fact I'd have to contend with -- effect is large.

A couple of weeks ago I inherited a create-react-app project. It had many of the classic problems seen in medium-to-large React projects - AnyScript, class components, esoteric 1-off dependencies preventing updates, a million lint violations of the == kind, multiple overlapping component libraries as well as multiple overlapping styling solutions.

My task was to add four pages to the app as well as a whole bunch of features. Using the existing material was absolutely out of the question - I would have had to create my own lib from scratch if I wanted to have any shred of confidence in the app.

I started hacking, and it didn't even take a full hour before I had to wrangle untyped garbage from the legacy side of the fence for a simple HTTP call. Here were some of the problems I found almost immediately:

  • It wasn't parsed (i.e. there was no zod or other schema solution)
  • It wasn't even typed Promise<Something>, it was proudly typed as Promise<any> and then just consumed the untyped result
  • I wasn't there at the dawn of creation of the app, so I had no idea what the shape of the result even was - I could only follow function names and see the properties being arbitrarily accessed.

So I did research to find out what the shape was supposed to be and created a little utility:



const UnsafeTypeId: unique symbol = Symbol.for("@types/Unsafe");

/**
 * Brands `A` as an unsafe type.
 * An unsafe type is a type that was cast but never validated.
 *
 * @example
 * ```ts
 * const response = await axios.get(...)
 *
 * const data = response.data as Unsafe<Todo>
 * ```
 *
 * @author Datner<[email protected]>
 */
export type Unsafe<A> = A & {
  readonly [UnsafeTypeId]?: "Unsafe";
};


Enter fullscreen mode Exit fullscreen mode

Now I can at least give a proper shape to the result while also acknowledging to future developers that the shape of the type wrapped by Unsafe is not actually validated.

Then I encountered another situation that prompted me to implement a different utility ... and then another ... and then another ... and on and on it went. I found myself implementing infrastructure and utilities just so I would have the basic essentials to write usable software.

First I tried just coping with it, then I tried to just let go and get the project done, but these issues continued to linger in the back of my mind ... and then it dawned on me - I was just re-implementing effect.

Worse, I'm re-implementing effect by trying to glue libraries together that were never designed to work with one another.

Oh the axios client needs the current tenant? Ok, then it needs access to the context. But it can change in the lifetime of the application and also needs the bearer token, but it might need revalidation or even a re-log. Ah, but I make too many requests for the same thing so I also need some caching. It's been nearly a month now and I've only been building infrastructure! The boring kind too! Plus I hate the result and it's full of bugs I won't see until they pop up in production!

You gotta understand, effect does not start and end with Effect or any of the other fancy modules like Stream, Schema, or PubSub. It's packed full of useful toolbelt utils like you'll get with lodash, but with full support and interop with each other and ofc everything strictly typed.

So I gave up. Whatever the effect "tax" is, it's worth it. From what we've seen, the "tax" on bundle size is about 50kb. Depending on your use-case, this may seem like a lot or very little. But considering that I added around 20k LOC to the project, as well as some new dependencies, for me it wasn't even a consideration.



$ npm i effect @effect/schema @effect/platform @effect/platform-browser


Enter fullscreen mode Exit fullscreen mode

I began replacing all my imitations with the utils from effect.

To give a visual example, here is a combine function I created for a particular useQueries query from the great @tanstack/react-query (a dependency which I also added a week ago).



import { Array, Predicate, Option, pipe } from "effect"

// further down....
const combine = (
  queries: [
    UseSuspenseQueryResult<User[], Error>,
    UseSuspenseQueryResult<Recipient[], Error>,
  ],
) => {
  const [users, recipients] = queries;

  const initialRows = pipe(
    users.data,
    Array.filter((_) => Option.isSome(_.emailAddress)),
    Array.appendAll(recipients.data),
    Schema.decodeSync(Schema.Array(NormalizedRow)),
    Array.dedupeWith((a, b) => a.Email === b.Email && a.fromIDP),
  );

  const isRecipient = Predicate.or(RecipientRow.isRecipient, (_) =>
    recipients.data.some((r) => r.recepientId === _.id),
  );
  return {
    pending: queries.some((_) => _.isPending),
    initialRows,
    initialSelected: Array.filterMap(initialRows, (_) =>
      isRecipient(_) ? Option.some(_.id) : Option.none(),
    ),
  };
};


Enter fullscreen mode Exit fullscreen mode

This function takes the result of two collections that have some possible overlap, cleans up users from members that don't have an email, concatenates it to the recipients into a nice wholesome (User | Recipient)[] that is unusable, uses an @effect/schema NormalizedRow schema to homogenize it into an actually useful RecipientRow[], and remove duplicate rows, keeping only the User-originated ones.

This utility also then creates a derived collection that is just the ids of the RecipientRows that either represent a Recipient or represent a User that has a corresponding Recipient (remember we deduped them).

You could glue a solution that does this using lodash, zod, and some hand-crafted stuff, but it won't be this elegant considering the definition above. Especially the normalization part and the types. In this snippet it looks like it's trivial -- it's not. This function was over 100 LOC before.

Recently, I have seen effect being referred to as "a different language" or "hard to learn" or "unreadable", including commentary from people who have never even used the library. Effect is a complete toolkit for developing enterprise-grade applications. Not an RxJS simulator or a cool way to use Result from Rust.

I also have a ton of Effect-effect usage. With all the bells and whistles! From a quick search here's a partial list of modules I use in this project

  • Effect
  • Layer
  • Context
  • Predicate
  • Array
  • Cause
  • Option
  • Config
  • Scope
  • ManagedRuntime
  • GlobalValue
  • String
  • HttpClient
  • ClientRequest
  • ClientResponse
  • Schema
  • Data

"Oh no! Egad! Thats too much to learn!" - You, right now, totally ignoring that you probably have learned all of these concepts, and much more, piecemeal, from scratch on every project according to whatever 30-or-so packages are included in the project

Don't worry dear reader, you don't have to learn any of these, you can be productive
with effect even when using just a single module, and learn to use the rest at your own pace. For me its the missing standard library that is internally consistent and 100% interoperable.

Cut to yesterday, I've finally finished implementing all the features I needed. 80 changed files (mostly new files), around 12K LOC added by yours truly (trimmed a lot of fat thanks to effect, but react theres still a lot of React code and CSS).

So, nows the money time. How much did I pay for my hubris?

net 50kb difference

)

Net 50kb.

effect, @effect/schema, @effect/platform, @effect/platform-browser, and react-query to boot.

80 files, 4 new routes, nearly all new code.

This was not an isolated case, as effect makes its way into more and more areas of your application, its size amortizes because you inevitably trim a lot of "fat" from your application. I removed almost all the custom utilities I had previously built and was able to uninstall several packages.

For example, my production server clocks in at just under 90kb.

Grief and effort was saved from implementing and fixing bugs in tools that I get for free just by using effect.

Additionally, effect is 100% tree-shakeable, so I didn't end up with unnecessary code in my final application bundle.

Like MooTools, knockoutjs, jquery, express, angularjs, react, redux, nextjs, rxjs, and many other powerful abstraction that changed how we write JavaScript code, I can say without hesitation that the effect "tax" was 100% worth it.

effect Article's
30 articles in total
Favicon
Wrapping up 2024
Favicon
This Week in Effect - 2024-12-27
Favicon
Effect 3.12 (Release)
Favicon
Effect 3.11 (Release)
Favicon
Cause & Effect Podcast #1
Favicon
Mapping Operations in Effect-TS Optionals
Favicon
Using do Notation in Effect-TS Optionals
Favicon
Exploring Option Getters in Effect-TS
Favicon
Exploring Option Conversions in Effect-TS
Favicon
Using match with Option in Effect
Favicon
Using match with Option in Effect
Favicon
Effect in React
Favicon
Understanding Type Guards in Effect-TS: Ensuring Safe Option Handling
Favicon
Exploring Option Constructors in Effect-TS
Favicon
Introduction to Options in Effect
Favicon
Effects in Ember
Favicon
How we migrated our codebase from fp-ts to Effect
Favicon
Synthetic Turf Benefits: Cost-Effective Solutions for Public Spaces
Favicon
The Effect Tax
Favicon
Effect 3.0
Favicon
Effect Days conference on Feb 23, 2024 in Vienna
Favicon
Link: OCAML 5 is Out + Effects Tutorial
Favicon
How To Fade In And Out In Premiere Pro
Favicon
React - Don't update parent state in the rendering phase of the child
Favicon
Encoding HKTs in TypeScript (Once Again)
Favicon
Developing A Wrapper Around Node.Js FS
Favicon
Contentlayer & Effect — Architectural Patterns And Decisions.
Favicon
JavaScript photo efffect fun project
Favicon
Matrix (and perlish) background effect in Javascript :)
Favicon
Social Media Buttons with HTML and CSS ( blur effect ).

Featured ones: