October 6, 2020

After describing the principles behind the unique encoding of HKTs 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.

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

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

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

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

We should be able to compile the project with:

And run it:

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:

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:

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.

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

Code at: 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:

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).</"e",>

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

Code at: 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.

Code at: core/src/Prelude/DSL/dsl.ts

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

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

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!

Code at: 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:

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:

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).

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

Code at: 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:

The dear old Monoid:

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

Nothing special about Foldable:

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

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