Effect-TS Core: ZIO-Prelude Inspired Typeclasses & Module Structure
Tags: typescript, functional
Published: 2020-10-06 10:43:34

In the first article of the [series](https://dev.to/mikearnaldi/series/9079) we described the principles behind the unique encoding of HKTs used in `@effect-ts/core`, it's now time to take a look at the details.

We will start by exploring the `Type-Classes` available and we will progressively make some examples of usage.

At the end we will discuss the module structure and what's available a-la-carte.

## Project Setup
Let's start with a simple new project (be bothered only if you come from Scala/Haskell, ignore if you know TS):

```bash
mkdir effect-ts-series; 
cd effect-ts-series;
npm init -y;
yarn add typescript@next @effect-ts/core @types/node;
mkdir src;
```

Let's create a file `tsconfig.json` as follow:

```json
{
  "compilerOptions": {
    "strict": true,
    "target": "ES5",
    "outDir": "lib",
    "lib": ["ESNext"]
  },
  "include": ["src/**/*.ts"]
}
```

Let's create a file `src/index.ts` with the following content:

```ts
import * as T from "@effect-ts/core/Effect";
import { pipe } from "@effect-ts/core/Function";

pipe(
  T.effectTotal(() => {
    console.log("Hello world");
  }),
  T.runMain
);
```

and add a build script to your `package.json` as follows:

```json
{
  "name": "effect-ts-series",
  "version": "1.0.0",
  "description": "Effect-TS Series",
  "main": "lib/index.js",
  "scripts": {
    "build": "tsc",
    "start": "node lib/index.js"
  },
  "keywords": [],
  "author": "Michael Arnaldi",
  "license": "MIT",
  "dependencies": {
    "@effect-ts/core": "^0.2.0",
    "@types/node": "^14.11.2",
    "typescript": "^4.1.0-dev.20201004"
  }
}
```

We should be able to compile the project with:

```bash
yarn build
```

And run it:

```bash
$ yarn start
yarn run v1.22.4
$ node lib/index.js
Hello world
Done in 0.46s.
```

## Introduction to ZIO-Prelude's Type-Classes

First of all let's start with a little bit of theory and reasons why to revise the classical type-classes hierarchy.

Statically Typed Functional Programming as we know it today effectively has roots in haskell and in its design principles; for years we have, as a community, gone through an exercise of borrowing principles one by one and finding its way into different languages.

The process of porting features from one language to another is not an easy one and it requires multiple steps, the first of which is finding similar encodings and secondly improving upon the basics.

Haskell’s type-system is inspired by category theory, but mathematically speaking it's only an “approximation” that focuses on a specific subset of the theory that makes sense in languages of the HM family. We should not be blind to the rest of the theory especially when extending the concepts to different languages because the same assumptions made in haskell might not hold in ours (like for example all of the functions being curried).

ZIO Prelude can be considered the second step of abstraction and adaptation of functional programming concepts to Scala, it is designed for Scala and leverages all the features available in the language.

Lucky for us the features of Scala as a language are very similar to the features of TypeScript at the type-system level and in some cases the TypeScript type-system is even more flexible (i.e. supporting intersection & union types).

Furthermore ZIO Prelude takes a look at a broader range of constructs from mathematics that have previously been perceived as secondary.

Let's take a look at `Functor` from `fp-ts`, we will list only one definition to keep things small:

```ts
export interface Functor<F> {
  readonly URI: F
  readonly map: <A, B>(fa: HKT<F, A>, f: (a: A) => B) => HKT<F, B>
}
```

Similarly defined in other fp-languages like `purescript` & `haskell` this typeclass shows a bias, in fact in category theory a Functor can be Covariant or Contravariant while here we associate the `Functor` name with a specific case

Let's now take a look at how a `Functor` is defined categorically:

![Cova-Contra (1)](https://dev-to-uploads.s3.amazonaws.com/i/k283oaeel4qzz5xvk1el.png)

A `Functor` between `Categories` is a mapping of both `objects` and `morphisms` that preserves the categorical structure, there are at least 2 types of `Functors`, one that preserves the direction of the `morphisms` and one that inverts the direction.

Those are called `Covariant Functor` & `Contravariant Functor`.

From the above definition from `fp-ts` we realise the `haskell` bias, everything is pointed towards `Covariant` `Functors`.

ZIO Prelude use different naming and leverages an extremely orthogonal design (i.e. minimal type-classes, easily composable), conceptually the same but more close to the actual laws the typeclass respect.

## Covariant

Let's take a look at the equivalent of `Functor` in `@effect-ts/core`:

```ts
export interface Covariant<F extends HKT.URIS, C = HKT.Auto> extends HKT.Base<F, C> {
  readonly map: <A, B>(
    f: (a: A) => B
  ) => <N extends string, K, Q, W, X, I, S, R, E>(
    fa: HKT.Kind<F, C, N, K, Q, W, X, I, S, R, E, A>
  ) => HKT.Kind<F, C, N, K, Q, W, X, I, S, R, E, B>
}
```
Code at: [core/src/Prelude/Covariant/index.ts](https://github.com/Matechs-Garage/matechs-effect/blob/master/packages/core/src/Prelude/Covariant/index.ts)

The name used is `Covariant` as in `Covariant Functor`.

Let's take a look at some instances for known data-types:

```ts
export const Covariant = P.instance<P.Covariant<[EitherURI], V>>({
  map: E.map
})
```

Where `E` is the `Either` module, `V = Prelude.V<"E", "+">` to indicate the covariance of the parameter `E` (in `Either` the error channel `E` mixes with union type as we will see later).

## Monad
Let's take a look at the dear loved `Monad`:

```ts
export type Monad<F extends URIS, C = Auto> = IdentityFlatten<F, C> & Covariant<F, C>

export type IdentityFlatten<F extends URIS, C = Auto> = AssociativeFlatten<F, C> &
  Any<F, C>

export interface Any<F extends HKT.URIS, C = HKT.Auto> extends HKT.Base<F, C> {
  readonly any: <
    N extends string = HKT.Initial<C, "N">,
    K = HKT.Initial<C, "K">,
    Q = HKT.Initial<C, "Q">,
    W = HKT.Initial<C, "W">,
    X = HKT.Initial<C, "X">,
    I = HKT.Initial<C, "I">,
    S = HKT.Initial<C, "S">,
    R = HKT.Initial<C, "R">,
    E = HKT.Initial<C, "E">
  >() => HKT.Kind<F, C, N, K, Q, W, X, I, S, R, E, any>
}

export interface AssociativeFlatten<F extends HKT.URIS, C = HKT.Auto>
  extends HKT.Base<F, C> {
  readonly flatten: <
    N extends string,
    K,
    Q,
    W,
    X,
    I,
    S,
    R,
    E,
    A,
    N2 extends string,
    K2,
    Q2,
    W2,
    X2,
    I2,
    S2,
    R2,
    E2
  >(
    ffa: HKT.Kind<
      F,
      C,
      N2,
      K2,
      Q2,
      W2,
      X2,
      I2,
      S2,
      R2,
      E2,
      HKT.Kind<
        F,
        C,
        HKT.Intro<C, "N", N2, N>,
        HKT.Intro<C, "K", K2, K>,
        HKT.Intro<C, "Q", Q2, Q>,
        HKT.Intro<C, "W", W2, W>,
        HKT.Intro<C, "X", X2, X>,
        HKT.Intro<C, "I", I2, I>,
        HKT.Intro<C, "S", S2, S>,
        HKT.Intro<C, "R", R2, R>,
        HKT.Intro<C, "E", E2, E>,
        A
      >
    >
  ) => HKT.Kind<
    F,
    C,
    HKT.Mix<C, "N", [N2, N]>,
    HKT.Mix<C, "K", [K2, K]>,
    HKT.Mix<C, "Q", [Q2, Q]>,
    HKT.Mix<C, "W", [W2, W]>,
    HKT.Mix<C, "X", [X2, X]>,
    HKT.Mix<C, "I", [I2, I]>,
    HKT.Mix<C, "S", [S2, S]>,
    HKT.Mix<C, "R", [R2, R]>,
    HKT.Mix<C, "E", [E2, E]>,
    A
  >
}
```
Code at: [core/src/Prelude/Monad/index.ts](https://github.com/Matechs-Garage/matechs-effect/blob/master/packages/core/src/Prelude/Monad/index.ts)

Apart from being slightly verbose, `@effect-ts/core` supports up to 10 different type parameters that can mix dynamically based on the variance annotation specified at the instance level.

We can see how well `Monad` is separated orthogonally across different, more specific, lawful type-classes.

We read `Monad` is a `Covariant` functor with an `identity` and an `Associative` flatten operation.

Pretty much describes itself the laws a `Monad` has to respect.  

Let's take a look at a few instances of `Monad` for various `data-types` and let's have a look at how variance works.

We will first introduce a generic operation to showcase how to write code that works with any kind, we will take a look at the generic `chain` function that given an instance of `Monad` performs a series of operations where the second operation depends on the result of the first.

```ts
export function chainF<F extends HKT.URIS, C = HKT.Auto>(
  F: Monad<F, C>
): <N2 extends string, K2, Q2, W2, X2, I2, S2, R2, E2, A, B>(
  f: (a: A) => HKT.Kind<F, C, N2, K2, Q2, W2, X2, I2, S2, R2, E2, B>
) => <N extends string, K, Q, W, X, I, S, R, E>(
  fa: HKT.Kind<
    F,
    C,
    HKT.Intro<C, "N", N2, N>,
    HKT.Intro<C, "K", K2, K>,
    HKT.Intro<C, "Q", Q2, Q>,
    HKT.Intro<C, "W", W2, W>,
    HKT.Intro<C, "X", X2, X>,
    HKT.Intro<C, "I", I2, I>,
    HKT.Intro<C, "S", S2, S>,
    HKT.Intro<C, "R", R2, R>,
    HKT.Intro<C, "E", E2, E>,
    A
  >
) => HKT.Kind<
  F,
  C,
  HKT.Mix<C, "N", [N2, N]>,
  HKT.Mix<C, "K", [K2, K]>,
  HKT.Mix<C, "Q", [Q2, Q]>,
  HKT.Mix<C, "W", [W2, W]>,
  HKT.Mix<C, "X", [X2, X]>,
  HKT.Mix<C, "I", [I2, I]>,
  HKT.Mix<C, "S", [S2, S]>,
  HKT.Mix<C, "R", [R2, R]>,
  HKT.Mix<C, "E", [E2, E]>,
  B
>
export function chainF<F>(F: Monad<HKT.UHKT<F>>) {
  return <A, B>(f: (a: A) => HKT.HKT<F, B>) => flow(F.map(f), F.flatten)
}
```
Code at: [core/src/Prelude/DSL/dsl.ts](https://github.com/Matechs-Garage/matechs-effect/blob/master/packages/core/src/Prelude/DSL/dsl.ts#L79)

Let's use this generic `chainF` function on a few different instances:

```ts
import * as IO from "@effect-ts/core/XPure/XIO";
import * as Either from "@effect-ts/core/Classic/Either";
import * as Effect from "@effect-ts/core/Effect";
import { pipe } from "@effect-ts/core/Function";
import { chainF } from "@effect-ts/core/Prelude/DSL";

const chainIO = chainF(IO.Monad);
const chainEither = chainF(Either.Monad);
const chainEffect = chainF(Effect.Monad);

// IO.XIO<number>
const io = pipe(
  IO.succeed(0),
  chainIO((n) => IO.succeed(n + 1))
);

const checkPositive = (n: number): Either.Either<string, number> =>
  n > 0 ? Either.right(n) : Either.left("error");

// Either.Either<string, number>
const either = (n: number) =>
  pipe(
    n,
    checkPositive,
    chainEither((n) => Either.right(n + 1))
  );

// Effect.Effect<{ s: string; } & { n: number; }, string | number, number>
const effect = pipe(
  Effect.accessM((_: { n: number }) =>
    Effect.ifM(Effect.succeed(_.n > 0))(() => Effect.succeed(_.n))(() =>
      Effect.fail("error")
    )
  ),
  chainEffect((n) =>
    Effect.accessM((_: { s: string }) =>
      Effect.ifM(Effect.succeed(_.s.length > 1))(() =>
        Effect.succeed(n + _.s.length)
      )(() => Effect.fail(0))
    )
  )
);
```

As we can see parameters `R`, `E` are mixed differently depending on the variance of the instance specified as:

```ts
// for Effect
export type V = P.V<"R", "-"> & P.V<"E", "+">

// for Either
export type V = P.V<"E", "+">
```

## Applicative
Let's take a look at the good old friend `Applicative`, the first thing to note is that `Applicative` is completely independent from `Monad` not really like in `Haskell` land!

```ts
export type Applicative<F extends URIS, C = Auto> = IdentityBoth<F, C> & Covariant<F, C>

export type IdentityBoth<F extends URIS, C = Auto> = AssociativeBoth<F, C> & Any<F, C>

export interface AssociativeBoth<F extends HKT.URIS, C = HKT.Auto>
  extends HKT.Base<F, C> {
  readonly both: <N2 extends string, K2, Q2, W2, X2, I2, S2, R2, E2, B>(
    fb: HKT.Kind<F, C, N2, K2, Q2, W2, X2, I2, S2, R2, E2, B>
  ) => <N extends string, K, Q, W, X, I, S, R, E, A>(
    fa: HKT.Kind<
      F,
      C,
      HKT.Intro<C, "N", N2, N>,
      HKT.Intro<C, "K", K2, K>,
      HKT.Intro<C, "Q", Q2, Q>,
      HKT.Intro<C, "W", W2, W>,
      HKT.Intro<C, "X", X2, X>,
      HKT.Intro<C, "I", I2, I>,
      HKT.Intro<C, "S", S2, S>,
      HKT.Intro<C, "R", R2, R>,
      HKT.Intro<C, "E", E2, E>,
      A
    >
  ) => HKT.Kind<
    F,
    C,
    HKT.Mix<C, "N", [N2, N]>,
    HKT.Mix<C, "K", [K2, K]>,
    HKT.Mix<C, "Q", [Q2, Q]>,
    HKT.Mix<C, "W", [W2, W]>,
    HKT.Mix<C, "X", [X2, X]>,
    HKT.Mix<C, "I", [I2, I]>,
    HKT.Mix<C, "S", [S2, S]>,
    HKT.Mix<C, "R", [R2, R]>,
    HKT.Mix<C, "E", [E2, E]>,
    readonly [A, B]
  >
}
```
Code at: [core/src/Prelude/Applicative/index.ts](https://github.com/Matechs-Garage/matechs-effect/blob/master/packages/core/src/Prelude/Applicative/index.ts)

Nothing easier, as we read an `Applicative` is a `Covariant` functor with an identity and an `Associative` operation `Both`.

It is theoretically the same as the classic variant with `ap` but much more clear from the laws point of view and from the usability standpoint.

Also if we go by the theory, we can read from [ncatlab.org](https://ncatlab.org/nlab/show/applicative+functor#:~:text=In%20computer%20science%2C%20applicative%20functors,applicative%20functors%20result%20from%20monads.):

> In computer science, applicative functors (also known as idioms) are the programming equivalent of lax monoidal functors with a tensorial strength in category theory.

If you know the terms involved you will recognise that this definition at the end is much closer to the theory compared to the classic `ap`.

Let's take a look at some DSL available for `Applicative` functors:

```ts
import * as Either from "@effect-ts/core/Classic/Either";
import * as DSL from "@effect-ts/core/Prelude/DSL";

const struct = DSL.structF(Either.Applicative);
const tupled = DSL.tupledF(Either.Applicative);

// Either.Either<never, { a: number; b: number; c: number; }>
const resultStruct = struct({
  a: Either.right(0),
  b: Either.right(1),
  c: Either.right(2),
});

// Either.Either<never, [number, number, number]>
const resultTupled = tupled(Either.right(0), Either.right(1), Either.right(2));
```

We leave it as an exercise for the reader to derive the `Monad` & `Applicative` declarations of `fp-ts` from this one and vice versa (hint: you can use functions available in `Prelude/DSL`).

## Traversable
Let's take a look at the dear old friend `Traversable`:

```ts
export interface Foreach<F extends HKT.URIS, C = HKT.Auto> {
  <G extends HKT.URIS, GC = HKT.Auto>(G: IdentityBoth<G, GC> & Covariant<G, GC>): <
    GN extends string,
    GK,
    GQ,
    GW,
    GX,
    GI,
    GS,
    GR,
    GE,
    A,
    B
  >(
    f: (a: A) => HKT.Kind<G, GC, GN, GK, GQ, GW, GX, GI, GS, GR, GE, B>
  ) => <FN extends string, FK, FQ, FW, FX, FI, FS, FR, FE>(
    fa: HKT.Kind<F, C, FN, FK, FQ, FW, FX, FI, FS, FR, FE, A>
  ) => HKT.Kind<
    G,
    GC,
    GN,
    GK,
    GQ,
    GW,
    GX,
    GI,
    GS,
    GR,
    GE,
    HKT.Kind<F, C, FN, FK, FQ, FW, FX, FI, FS, FR, FE, B>
  >
}

export interface Traversable<F extends HKT.URIS, C = HKT.Auto>
  extends HKT.Base<F, C>,
    Covariant<F, C> {
  readonly foreachF: Foreach<F, C>
}
```
Code at: [core/src/Prelude/Traversable/index.ts](https://github.com/Matechs-Garage/matechs-effect/blob/master/packages/core/src/Prelude/Traversable/index.ts)

Nothing exceptionally different from the classic version apart from the name of the `foreachF` function (originally called `traverse`).

Let's take a look at its usage:

```ts
import * as Either from "@effect-ts/core/Classic/Either";
import * as Array from "@effect-ts/core/Classic/Array";
import * as Record from "@effect-ts/core/Classic/Record";
import { pipe } from "@effect-ts/core/Function";
import { sequenceF } from "@effect-ts/core/Prelude";

const foreachArray = Array.Traversable.foreachF(Either.Applicative);

// Either.Either<string, Array.Array<number>>
const resultArray = pipe(
  [0, 1, 2, 3],
  foreachArray((n) => (n > 2 ? Either.left("error") : Either.right(n)))
);

const foreachRecord = Record.Traversable.foreachF(Either.Applicative);

// Either.Either<string, Readonly<Record<"a" | "b" | "c" | "d", number>>>
const resultRecord = pipe(
  {
    a: 0,
    b: 0,
    c: 0,
    d: 0,
  },
  foreachRecord((n) => (n > 2 ? Either.left("error") : Either.right(n)))
);

const sequenceArray = sequenceF(Array.Traversable)(Either.Applicative);

// Either.Either<string, Array.Array<number>>
const sequenceArrayResult = sequenceArray([
  Either.left("error"),
  Either.right(0),
  Either.right(1),
  Either.right(2),
]);
```

## Identity
The dear old `Monoid`:

```ts
export interface Identity<A> extends Associative<A> {
  readonly identity: A
}

export interface Associative<A> extends Closure<A> {
  readonly Associative: "Associative"
}

export interface Closure<A> {
  combine(r: A): (l: A) => A
}
```

Like before without previously knowing the laws we can read that a `Monoid` has a `combine` associative operation with an `identity` element.

## Foldable
Nothing special about `Foldable`:

```ts
export type Foldable<F extends URIS, C = Auto> = ReduceRight<F, C> &
  Reduce<F, C> &
  FoldMap<F, C>

export interface Reduce<F extends HKT.URIS, C = HKT.Auto> extends HKT.Base<F, C> {
  readonly reduce: <A, B>(
    b: B,
    f: (b: B, a: A) => B
  ) => <N extends string, K, Q, W, X, I, S, R, E>(
    fa: HKT.Kind<F, C, N, K, Q, W, X, I, S, R, E, A>
  ) => B
}

export interface ReduceRight<F extends HKT.URIS, C = HKT.Auto> extends HKT.Base<F, C> {
  readonly reduceRight: <A, B>(
    b: B,
    f: (a: A, b: B) => B
  ) => <N extends string, K, Q, W, X, I, S, R, E>(
    fa: HKT.Kind<F, C, N, K, Q, W, X, I, S, R, E, A>
  ) => B
}

export interface FoldMap<F extends HKT.URIS, C = HKT.Auto> extends HKT.Base<F, C> {
  readonly foldMap: FoldMapFn<F, C>
}

export interface FoldMapFn<F extends HKT.URIS, C = HKT.Auto> {
  <M>(I: Identity<M>): <A>(
    f: (a: A) => M
  ) => <N extends string, K, Q, W, X, I, S, R, E>(
    fa: HKT.Kind<F, C, N, K, Q, W, X, I, S, R, E, A>
  ) => M
}
```

Let's take a look at using some `Foldable` instances.

```ts
import * as Array from "@effect-ts/core/Classic/Array";
import * as Record from "@effect-ts/core/Classic/Record";
import * as Identity from "@effect-ts/core/Classic/Identity";

const fromArray = Record.fromFoldable(Identity.string, Array.Foldable);

// Readonly<Record<string, string>>
const record = fromArray([
  ["a", "foo"],
  ["b", "bar"],
]);
```

## Module Structure
The `@effect-ts/core` package is organized in directories as follow:
- `@effect-ts/core/Classic` : lightweight modules and commonly used type-classes, to be used everywhere (browser, node)
- `@effect-ts/core/Effect` : effect based modules, primarily targeting node development this set of modules is a full suite to structure highly concurrent & well testable services with a variety of data types including: `Fiber, FiberRef, Layer, Managed, Promise, Queue, Ref, RefM, Schedule, Scope, Semaphore, Stream, Supervisor` . It can be used in frontend development too but there is a cost-benefit to be considered, if the project is large enough it might be beneficial because of project based amortisation in smaller projects and specific use data types from `Classic` like `Async` are preferrable. 
- `@effect-ts/core/Function` : function based utilities like `pipe`
- `@effect-ts/core/Newtype`: newtype definition and common newtypes
- `@effect-ts/core/Utils`: small set of utilities for pattern matching and intersection
- `@effect-ts/core/XPure`: data-types based on `XPure`, an efficient synchronous data-type that support Contravariant State Input, Covariant State Output, Contravariant Reader, Covariant Error, Output. The purpose of `XPure` is to serve as a basis to construct multiple data-types that can satisfy specific capabilities. It is also very lightweight and can be especially efficient if used across different data-types. `XPure` is also used to back the `Classic/Sync` data-type that is natively included as a primitive of `Effect`.
- `@effect-ts/core/Modules`: internal usage