Someone finally fixed Javascript
Tags: javascript, typescript
Published: 2024-04-08 13:05:27

The JavaScript ecosystem evolves at a breakneck pace. Just as you get comfortable with a certain technique, a slew of new methodologies emerges. Some, like TypeScript, gain widespread adoption, while others, such as CoffeeScript, quietly fade away. Each innovation initially stirs excitement, but over time, the community often splits, with detractors eventually spawning their own frameworks. This endless cycle has made me increasingly wary of the latest "magic" frameworks purported to solve all problems. I've shifted from seeking tools as solutions to embracing the understanding of patterns over the constant chase for new technology.

This is why I'm pointing you towards something special for your TypeScript projects, not just another tool but a paradigm that encourages good practices: [Effect](https://effect.website/).

Let's take a look at why you should take the leap.

## Colored functions
Have you ever asked yourself [what color is your function?](https://journal.stuffwithstuff.com/2015/02/01/what-color-is-your-function/)

Let me summarize it for you. Imagine you have blue and red functions in your code base. The rule is simple: you can use red functions inside your blue functions but not the other way. Wouldn't that be a nightmare? Now replace blue by "async". Yep, you got function coloring in Javascript. 

So how do we fight this coloring thing? If we want to remove colored functions, we would need to create some sort of wrapper that will use a Promise only when needed. Like "Future" or... "Effect"?

```ts
import { Effect, pipe } from "effect";

const fibonacci = (a: number): Effect.Effect<number> =>
  a <= 1
    ? Effect.succeed(a)
    : pipe(
        Effect.all([fibonacci(a - 1), fibonacci(a - 2)]),
        Effect.map(([a, b]) => a + b)
      );

await Effect.runPromise(fibonacci(10));
```

The key difference when using `Effect` instead of `Promise` lies in how concurrency is handled. Effect provides fibers, which are lightweight concurrency structures similar to green threads or goroutines. This feature allows us to perform long-running or asynchronous tasks without blocking the main thread, which can be initiated even within traditionally synchronous functions.

```ts
import { Effect, Console, pipe } from "effect";

const longRunningTask = pipe(
  Console.log("Start of long running task"),
  Effect.delay(1000),
  Effect.tap(Console.log("End of long running task"))
);

console.log("Start of program");
Effect.runFork(longRunningTask);
console.log("End of program");

/**
 * OUTPUT:
 * Start of program
 * End of program
 * Start of long running task
 * End of long running task
 */
```

While Effect does not eliminate the inherent async/sync distinctions (function coloring) in JavaScript, by using fibers to handle asynchronous operations, it allows synchronous functions to invoke asynchronous effects without becoming asynchronous themselves, thereby mitigating the "coloring" problem to a significant extent.

## Typesafe errors
Let's look at this function:
```ts
const divide = (a: number, b: number) => a / b;
```

We just introduced a problem here, we cannot divide by zero. So let's refactor the code a little bit: 
```ts
const divide = (a: number, b: number) => {
  if (b === 0) throw new Error('Cannot divide by zero.');
  return a / b;
}
```

Looks good to you? It is not. Because it is not typesafe. Someone that will want to use your function won't have any idea that your function can throw. This might look trivial with a simple function like this one but when you have dozens of potential errors, it can become a nightmare. Other more mature languages have notions such as `Either` or `Result` to have typesafe errors. It looks like that: 
```ts
type Result<T, E> = Ok<T> | Err<E>;

// With something like:
type Ok<T> = { kind: "Ok", data: T };
type Err<E> = { kind: "Err", err: E };
```

With Effect, you will have that out of the box: `Effect<T, E>`. You won't ever have to ask yourself what kind of errors can occur during the run, you can know it directly from the function signature. It also comes with helper functions to recover from errors.
```ts
const divide = (a: number, b: number): Effect<number, "DivisionByZeroError"> => {
  if (b === 0) return Effect.fail("DivisionByZeroError");
  return Effect.succeed(a / b);
}
```

## Newtypes or branded types
You know, looking back at my previous function I realize we could do better.
```ts
const divide = (a: number, b: NonZeroNumber) => ...
```
How do you define `NonZeroNumber` though? If you just do `type NonZeroNumber = number` it won't prevent people to call it with "0". There is a pattern for that: newtypes. And yeah, Effect supports that too: 
```ts
import { Brand } from "effect"
 
type NonZeroNumber = number & Brand.Brand<"NonZeroNumber">
 
const NonZeroNumber = Brand.refined<NonZeroNumber>(
  (n) => n !== 0, // Check if the value is a non-zero number
  (n) => Brand.error(`Expected ${n} to be a non-zero number`)
)
```
This way, you know your function **cannot** be called with any number: it expects a special type of number which exclude zero.

## Dependency injection
If you want to follow the "Inversion of Control" principle, you might want to look into "Dependency Injection". This concept is really simple: a function should have access to what it needs from its own context.
```ts
// With a singleton
const read = (filename) => FileReader.read(filename);

// With dependency injection
const read = (reader: FileReader) => (filename) => reader.read(filename);
```
It's better to do it this way for many reasons such as uncoupling things, allowing for easy testing, having different contexts etc.

While several frameworks assist with this, Effect really crushed it by making it straightforward: put your dependencies as the third parameter of an Effect.

```ts
const read = (filename): Effect<File, Error, FileReader> => {
  return Effect.flatMap(FileReader, fileReader => {
    return fileReader.read(filename);
  })
}
```

## Conclusion

There are many other reasons why you should consider Effect. For sure, it's not going to be easy at first, you will have to learn to code differently. But contrary to many frameworks that make you learn "their" way of doing something, Effect actually teaches you good patterns that have made their proofs in other languages. Actually, Effect is heavily inspired by ZIO from Scala which itself has been inspired by Haskell which is still today considered as one of the pinacle of good programming patterns.