The Case for TS+
Tags: typescript, functional, programming, language
Published: 2022-04-10 15:00:51

Note from 2023:

TS+ is an experimental fork of TS, don't use it in prod.

---

Hi!

So, what's this post gonna be about? 

Well, with the Effect community, we've been hard at work for the last ~6 months on something new which I am now truly proud to introduce in detail for the first time.

## Table Of Contents
  1. [Why TS+](#why)
    - [The Development of Fluent APIs](#fluent-apis)
    - [The Development of Pipeable APIs](#pipeable-apis)
  2. [Introducting TS+](#intro)
  3. [Installing TS+](#install)
  4. [Fluent and Static Extensions](#fluent-and-static-extensions)
  5. [Call Extensions](#call-extensions)
  6. [Binary Operators](#binary-operators)
  7. [Lazy Arguments](#lazy-arguments)
  8. [Type Unification](#type-unification)
  9. [Global Imports](#global-imports)
  10. [Runtime Types and Derivation](#runtime-types-and-derivation)
  11. [Module Structure](#module-structure)
  12. [Compiled Output](#compiled-output)
  13. [What's Next](#whats-next)

## Why TS+? <a name="why"></a>

Let's begin with a little bit of a back story. For the last four years (yeah, it's already been **that** long...), we have been working hard on the development of [Effect](https://github.com/Effect-TS). 

Originally, Effect started as a small project inspired by the ideas brought forth by the developers of ZIO in Scala. However, it progressively evolved to a full blown port of all the core modules of ZIO, together with a very rich set of extension modules that bridges the gap between the data structures available in Scala and in TypeScript.

During all this work we have grown to love much of the TypeScript language and we were very surprised since the very first days how much we could do, and how safe and pleasant from a developer experience perspective the language was to use.

However, we also encountered a decent amount of limitations - not really limitations on what we could do in the language, but rather what we could do **efficiently** in the language, both in terms of verbosity and in terms of tree-shakeability of the produced code.

Let's start with an example:

### The Development of Fluent APIs <a name="fluent-apis"></a>

A fluent API in TypeScript might look something like:

```ts
export class IO<A> {
  static succeed = <A>(a: A) => new IO(() => a);
  static succeedWith = <A>(a: () => A) => new IO(a);

  private constructor(readonly io: () => A) {}

  map<A, B>(this: IO<A>, f: (a: A) => B): IO<B> {
    return new IO(() => f(this.io()));
  }

  flatMap<A, B>(this: IO<A>, f: (a: A) => IO<B>): IO<B> {
    return new IO(() => f(this.io()).io());
  }

  zip<A, B>(this: IO<A>, b: IO<B>): IO<[A, B]> {
    return new IO(() => [this.io(), b.io()]);
  }

  run<A>(this: IO<A>) {
    return this.io();
  }
}
```

which can be used like:

```ts
export const program = IO.succeed(0)
  .map((n) => n + 1)
  .flatMap((r) =>
    IO.succeedWith(() => {
      console.log(`result: ${r}`);
    })
  );

program.run();
```

In this approach we have a data-type `IO<A>` together with a "companion object" `IO`. This approach lends itself to a certain  organization of code:
- Constructors like `succeed` and `succeedWith` are placed in the "companion object" as static members
- Methods like `map`, `flatMap`, and `zip` are placed in the class instance as members of the type `IO<A>`

This approach is very common in object-oriented languages and its advantages lie in the ease of use. In fact, for the user of such a module, the editor experience is beautiful - if you want to construct an `IO` you type `IO.` and get a full list of suggestions on ways you can construct an `IO`. In addition, when you want to do something with a value of type `IO<A>`, you type `value.` and you are again prompted with a nice list of options.

This fluent type of API is "a dream", but also has a few drawbacks that have led the biggest part of the FP community away from it.

The limitations are staggering:
- All methods have to be inside the class (or inside a common abstract class for ADTs)
- Once a class is defined adding methods to it from the outside requires module augmentation and unsafe mutation of object prototypes
- Worst of all, none of it can be optimized from a tree shaking perspective, so you'll end up with a huge bundle size that becomes prohibitive at a certain point

The current "solution" that the FP community has adopted is the use of pipeable APIs.

### The Development of Pipeable APIs <a name="pipeable-apis"></a>

In a pipeable API we would rewrite the prior example to:

```ts
// file: IO.ts
export class IO<A> {
  constructor(readonly io: () => A) {}
}

export const succeed = <A>(a: A) => new IO(() => a);

export const succeedWith = <A>(a: () => A) => new IO(a);

export function map<A, B>(f: (a: A) => B): (self: IO<A>) => IO<B> {
  return (self: IO<A>) => new IO(() => f(self.io()));
}

export function flatMap<A, B>(f: (a: A) => IO<B>): (self: IO<A>) => IO<B> {
  return (self) => new IO(() => f(self.io()).io());
}

export function zip<A, B>(b: IO<B>): (self: IO<A>) => IO<[A, B]> {
  return (self) => new IO(() => [self.io(), b.io()]);
}

export function run<A>(self: IO<A>) {
  return self.io();
}

// file: Function.ts

// omitted definition of pipe due to its length
// For the curious, take a look at https://github.com/Effect-TS/core/blob/master/packages/system/src/Function/pipe.ts

// file: Program.ts
import * as IO from "./IO";
import { pipe } from "./Function";

export const program = pipe(
  IO.succeed(0),
  IO.map((n) => n + 1),
  IO.flatMap((r) =>
    IO.succeedWith(() => {
      console.log(`result: ${r}`);
    })
  )
);

IO.run(program);
```

What we have essentially done is extracted all the constructors and methods outside of the class, and moved the `this` parameter to be a normal curried function parameter that the `pipe` function will carry through each function call.

The resulting API is still quite nice visually, but it does have a few drawbacks. 

First of all, auto-imports do not work well with namespaced imports (recently folks at Unsplash have been [working on language service plugins for this](https://twitter.com/OliverJAsh/status/1509621766666215428)). Even if you do get auto-imports to work, you'll end up with tons of imports. 

Additionally, we have lost the meaning and categorization of `IO` - namely we no longer have a data-type `IO<A>` and a "companion object" `IO`. We now only have a data-type `IO<A>` and a module of functions that include both constructors, such as `IO.succeed`, and pipeable functions (which are also called _aspects_), such as `IO.flatMap`. So when programming a user needs to know **exactly** which module to import, **exactly** which functions are constructors of the datatype and which are methods for the datatype, and **exactly** how to use them. 

Finally, having had the experience of teaching those concepts to other developers, it seems fairly common that folks sometimes have issues reading pipeable function signatures.

## Introducing TS+ <a name="intro"></a>

TS+ is a new language developed as a super-set of TypeScript and maintained as a fork of the original TypeScript compiler that is rebased daily.

In order to guarantee full support with the TypeScript ecosystem, we limit what can be extended. TS+ emits standard declaration files consumable from plain TypeScript and consumes plain definition files. TS+ also does not add any new syntax.

TS+ diverges from TypeScript in that we do not limit ourselves from emitting code using type information. Instead, we leverage the type information as much as possible to produce both highly optimized code and improve the developer experience.

With those decisions comes a set of trade-offs. TS+ can be compiled only with the TS+ compiler (a fork of `tsc`) and cannot be compiled by tools like babel which "simply" remove the types. 

We suggest using a compilation pipeline where `tsc` emits ES2022 modules, followed by another tool such as `esbuild`/`swc`/`babel` that takes over from there leveraging the powerful infrastructure of project references to optimize compilation.

## Installing TS+ <a name="install"></a>

To install TS+, you must first add it as a dev dependency with:

```bash
yarn add -D @tsplus/installer
# or
npm i -D @tsplus/installer
``` 

Then, you can add a `postinstall` script to your `package.json`. For example: 

```json
{
  "scripts": {
    "postinstall": "tsplus-install"
  }
}
``` 

This will replace the TypeScript that is installed in your `node_modules` with the TS+ version. 

**Note**: this install process is temporary until a better mechanism for installation is determined.

After installing, you will want to also ensure that your IDE uses the TypeScript language service from the workspace and not the default one.

If you want to start playing around with a pre-configured repository, you can open up https://github.com/ts-plus/starter-lib in GitPod.

## Using TS+ (Fluent Methods) <a name="fluent-and-static-extensions"></a>

Using our initial example, we will start by extracting all methods from the main `IO` class:

```ts
/**
 * @tsplus type article.IO
 */
export class IO<A> {
  static succeed = <A>(a: A) => new IO(() => a);
  static succeedWith = <A>(a: () => A) => new IO(a);

  constructor(readonly io: () => A) {}
}

/**
 * @tsplus fluent article.IO map
 */
export function map<A, B>(self: IO<A>, f: (a: A) => B): IO<B> {
  return new IO(() => f(self.io()));
}

/**
 * @tsplus fluent article.IO flatMap
 */
export function flatMap<A, B>(self: IO<A>, f: (a: A) => IO<B>): IO<B> {
  return new IO(() => f(self.io()).io());
}

/**
 * @tsplus fluent article.IO zip
 */
export function zip<A, B>(self: IO<A>, b: IO<B>): IO<[A, B]> {
  return new IO(() => [self.io(), b.io()]);
}

/**
 * @tsplus fluent article.IO run
 */
export function run<A>(self: IO<A>) {
  return self.io();
}
```

One thing that you may notice in the example above is that we have added a JSDoc annotation to our `IO` class. This annotation identifies the `IO` type in TS+ with a "type tag" of `article.IO`. The "type tag" for each type you define with TS+ should be globally unique. Prefixing type tags with your package's name is common, since non-prefixed tags should be reserved for the [TS+ standard library](https://github.com/ts-plus/stdlib).

Now that we have added our type tag to the `IO` class, we can begin by extracting each method from inside the class to a function, renaming `this` to something else (in this case we have chosen to use `self`).

We further added a JSDoc annotation above each function, which associates the function with a type in TS+. For example, to associate the `flatMap` function to the `IO` datatype, we annotate `flatMap` with `@tsplus fluent article.IO flatMap`. This essentially tells the compiler "put this fluent method in any type that matches the tag article.IO and name it flatMap".

Having done that we can already use `IO` exactly as before:

```ts
export const program = IO.succeed(0)
  .map((n) => n + 1)
  .flatMap((r) =>
    IO.succeedWith(() => {
      console.log(`result: ${r}`);
    })
  );

program.run();
```

But we can already note from the IDE the following:

![Fluent Method](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/govk3d67ia3qrtsg11tu.png)

We see from the IDE quick documentation that this is a "fluent" extension and not a classical "method".

That's about it for fluent extensions, they are declared as normal functions that you can even call directly like `map(a, f)`. They can be declared anywhere you want and the path to the method will be resolved during compilation.

In order to support the folks who prefer a pipeable API, we also have a macro to derive a pipeable function from a data first one. This can be used as follows:

```ts
export function map<A, B>(self: IO<A>, f: (a: A) => B): IO<B> {
  return new IO(() => f(self.io()));
}

const mapPipeable = Pipeable(map)
```

**Note**: this is a compiler extension that will deal with the generics of the signature in a correct way, it doesn't use conditional types.

Additionally fluent methods can be defined directly using pipeable functions like:

```ts
/**
 * @tsplus pipeable article.IO flatMap
 */
export function flatMap<A, B>(f: (a: A) => IO<B>): (self: IO<A>) => IO<B> {
  return self => new IO(() => f(self.io()).io());
}
```

This only solves half of the problem though. We managed to extract methods from a datatype, but constructors are still static members of the "companion object" (non extensible and non tree-shakable). 

Let's solve that next:

```ts
/**
 * @tsplus type article.IO
 * @tsplus companion article.IO/Ops
 */
export class IO<A> {
  constructor(readonly io: () => A) {}
}

/**
 * @tsplus static article.IO/Ops succeed
 */
export const succeed = <A>(a: A) => new IO(() => a);

/**
 * @tsplus static article.IO/Ops succeedWith
 */
export const succeedWith = <A>(a: () => A) => new IO(a);
```

Here we identified a "companion" object for the `IO` dataype by annotation the `IO` class with with `@tsplus companion article.IO/Ops`. Then, to extract the constructors from the class, we used `@tsplus static article.IO/Ops succeed` to say "put this value as a static member of any type tagged as article.IO/Ops"

**Note**: there is no difference between a type tag and one defined with companion, but a class has 2 types the instance type and the constructor type so we need a way of distinguishing which tag links to what.

An alternative pattern for those who prefer to avoid using a class is the following:

```ts
/**
 * @tsplus type article.IO
 */
export interface IO<A> {
  readonly io: () => A;
}

/**
 * @tsplus type article.IO/Ops
 */
export interface IOOps {
  <A>(io: () => A): IO<A>;
}

export const IO: IOOps = io => ({ io });

/**
 * @tsplus static article.IO/Ops succeed
 */
export const succeed = <A>(a: A) => IO(() => a);

/**
 * @tsplus static article.IO/Ops succeedWith
 */
export const succeedWith = <A>(a: () => A) => IO(a);

/**
 * @tsplus fluent article.IO map
 */
export function map<A, B>(self: IO<A>, f: (a: A) => B): IO<B> {
  return IO(() => f(self.io()));
}

/**
 * @tsplus pipeable article.IO flatMap
 */
export function flatMap<A, B>(f: (a: A) => IO<B>): (self: IO<A>) => IO<B> {
  return self => IO(() => f(self.io()).io());
}

/**
 * @tsplus fluent article.IO zip
 */
export function zip<A, B>(self: IO<A>, b: IO<B>): IO<[A, B]> {
  return IO(() => [self.io(), b.io()]);
}

/**
 * @tsplus fluent article.IO run
 */
export function run<A>(self: IO<A>) {
  return self.io();
}

//
// Usage
//

export const program = IO.succeed(0)
  .map((n) => n + 1)
  .flatMap((r) =>
    IO.succeedWith(() => {
      console.log(`result: ${r}`);
    })
  );

program.run();
```

Another thing we usually do is to put related types to `IO` like  for example a type like `ExtractResult` in a namespace named `IO` like:

```ts
/**
 * @tsplus type article.IO
 */
export interface IO<A> {
  readonly io: () => A;
}

export declare namespace IO {
  export type ExtractResult<I extends IO<any>> = [I] extends [IO<infer A>] ? A : never;
}

/**
 * @tsplus type article.IO/Ops
 */
export interface IOOps {
  <A>(io: () => A): IO<A>;
}

export const IO: IOOps = io => ({ io });
```

Basically giving `IO` 3 meanings:
1. As an interface/type (i.e. for methods)
2. As a term/value (i.e. for constructors)
3. As a namespace (i.e. for related types)

All of this together provides an extensible way of developing easily to usable and discoverable APIs that are fully tree-shakable and optimized. In fact, the program that we have written here is a valid program that utilizes the `IO` module that we have defined.

## Using TS+ (Call Extension) <a name="call-extensions"></a>

There are cases where we would like to add a call signature to something that isn't a function, for example we could refactor the above constructor like:

```ts
/**
 * @tsplus type article.IO/Ops
 */
export interface IOOps {}
export const IO: IOOps = {};

/**
 * @tsplus static article.IO/Ops __call
 */
export function make<A>(io: () => A): IO<A> {
  return { io };
}
```

This allows us to construct values for a datatype using the `__call` expression that we define. For example, we can create an `IO<string>` using the `__call` expression for the `IO` datatype above:

```ts
const io: IO<string> = IO(() => "Hello, World!")
```

The name `__call` is a special name that basically says "use the function as the call signature for a type". In the example above, TS+ will resolve the `__call` expression for the `IO` datatype to the `make` function that we defined for `IO`.

In some extreme cases you may want access to the "this" and for that you can use a "fluent" variant of a `__call` expression instead of a "static" one.

```ts
/**
 * @tsplus fluent article.IO/Ops __call
 */
export function make<A>(self: IOOps, io: () => A): IO<A> {
  return { io };
}
```

You may think the set of features we have described thus far is enticing enough... ohh but we have only just started.

## Using TS+ (Binary Operators) <a name="binary-operators"></a>

There are many binary operators in JS - it's a shame there isn't a way of extending those (and the proposal to do so is inefficient in terms of tree shaking, limited, and may never gonna come to fruition)... Yeah, we can do that too :)

Looking at the `IO` type that we defined above, the `zip` combinator that we defined for `IO` looks quite a bit like a binary operator. Given that the "+" symbol doesn't make any sense when used between two `IO` types in plain JavaScript/TypeScript, we can override it in TS+:

```ts
/**
 * @tsplus fluent article.IO zip
 * @tsplus operator article.IO +
 */
export function zip<A, B>(self: IO<A>, b: IO<B>): IO<[A, B]> {
  return IO(() => [self.io(), b.io()]);
}
```

With just that additional JSDoc annotation we can now call `zip` on two `IO` types using the `+` operator:

```ts
const zipped = IO.succeed(0) + IO.succeed(1);
```

And looking at the quick info from VSCode:

![Operator](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mqpjcv8sybrt83ch8b37.png)

Furthermore, you can define multiple operators and multiple fluent extensions with the same target type by specify an operator precedence. This enables neat DSL's, such as the ones we have created for [`@tsplus/stdlib/collections`](https://github.com/ts-plus/stdlib/tree/main/packages/stdlib/_src/collections). (by the way, if you are here you should also probably install `@tsplus/stdlib`).

![Collection DSL](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6rkwffj3so46q9n06t67.png) 

You can take a more detailed look at how these operators are used in the test suite for the TS+ standard library. For example: [List.test.ts](https://github.com/ts-plus/stdlib/blob/main/packages/stdlib/_test/List.test.ts) (and the other tests).

One last thing - we've also implemented go-to-definition for custom operators, so you can click an operator and use your IDE's go-to-definition to quickly navigate to the implementation of that operator.

## Using TS+ (Lazy Arguments) <a name="lazy-arguments"></a>

In JavaScript/TypeScript, lazy evaluation of a computation is generally implemented via a thunk (i.e. `() => A`). Deferring evaluation of computations can be quite beneficial in a variety of circumstances, particularly when we want to avoid eager computation of a value. This paradigm of "lazy" programming becomes even more powerful when combined with effect systems.

However, if you attempt to write a truly lazy program in JavaScript/TypeScript, you'll quickly find yourself writing a lot of annoying arrow functions like `T.succeed(() => console.log("A"))`. This is because we want the effect system to assume control over whether or not the `console.log` is actually called. 

To avoid this, TS+ allows us to define function parameters as "lazy":

```ts
/**
* @tsplus type LazyArgument
*/
interface LazyArg<A> {
  (): A
}

/**
 * @tsplus static Effect/Ops succeed
 */
function succeed<A>(f: LazyArg<A>) {
  f()
}
```

When calling `Effect.succeed(x)`, if `x` is not already evaluated lazily (i.e. if `x` is not already a thunk), the compiler will transform it to `Effect.succeed(() => x)` making it possible to trim annoying boilerplate. 

So something like:

```ts
Effect.succeed(() => console.log("Hello, world!"))
```

becomes

```ts
Effect.succeed(console.log("Hello, world!"))
```

Note that the behavior is only added to function arguments of a type that has a type tag of value `LazyArgument` and not generally to any function arguments.

## Using TS+ (Type Unification) <a name="type-unification"></a>

Ever ended up with something like `Left<0> | Left<1> | Right<string>` in something that should have been `Either<number, string>`?

That's because TypeScript assumes that the right-most type is always the strictest (which is a good assumption, if you manually type it, it compiles).

We can be explicit about type unification in TS+ though:

```ts
/**
 * @tsplus type Option
 */
export type Option<A> = None | Some<A>;

/**
 * @tsplus unify Option
 * @tsplus unify Option/Some
 * @tsplus unify Option/None
 */
export function unifyOption<X extends Option<any>>(
  self: X
): Option<[X] extends [Option<infer A>] ? A : never> {
  return self;
}
```

or

```ts
/**
 * @tsplus type Eval
 */
export interface Eval<A> {
  readonly _A: () => A;
  readonly _TypeId: unique symbol;
}

/**
 * @tsplus unify Eval
 */
export function unifyEval<X extends Eval<any>>(self: X): Eval<[X] extends [Eval<infer AX>] ? AX : never> {
  return self;
}
```

and TS+ will use the result of the unify function to unify any time a union is generated.

So that for example:

![Type Unification](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9mi9ien5ryd2f5sumfi1.png)
 
## Using TS+ (Global Imports) <a name="global-imports"></a>

As mentioned before, having a lot of imports can be quite painful. To avoid this, we have found that many users of Effect, for example, define their own "prelude" or "utils" files that re-export commonly used modules. Unfortunately, this often leads to edge cases in tree-shaking algorithms used by bundlers that have only recently begin to improve shaking of deeply nested dependency trees.

With TS+, we solve this problem using the concept of global imports. 

A global import is an import defined in a declaration file (`.d.ts`) using the following syntax:

```ts
/**
 * @tsplus global
 */
import { Chunk } from "@tsplus/stdlib/collections/Chunk/definition";
```

When defining a type as "global", TS+ will make the type and its associated constructors/methods available globally in the project. However, during compilation TS+ will resolve usage of the constructors/methods associated with a datatype and add relevant imports to each file using the datatype. However, note that imports will only be added to a file during compilation if the global datatype is actually used in that file.

It is a common practice to define a `prelude.d.ts` in your root and add it to the "files" portion of the "tsconfig.json". For example:

```json
// tsconfig.json
{
  "files": [
    "./prelude.d.ts"
  ]
}
```

Also you can share your preludes across your projects like we do with [@tsplus/stdlib-global](https://github.com/ts-plus/stdlib/blob/03511f8f5a56aa3536e0fdf4203ea60031b996ad/packages/stdlib-global/types.d.ts) that if you import in your prelude file it will give you access to the full blown standard library everywhere in your project.

## Using TS+ (Runtime Types and Derivation) <a name="runtime-types-and-derivation"></a>

Thought it couldn't get any better? close.

One of the biggest pain points we've ever experienced in app development is the definition of types that are safe at runtime. Many solutions have been attempted so far, including but not limited to: [`io-ts`](https://github.com/gcanti/io-ts), [`morphic-ts`](https://github.com/sledorze/morphic-ts), [`zod`](https://github.com/colinhacks/zod), [`@effect-ts/schema`](https://github.com/Effect-TS/schema), [`@effect-ts/morphic`](https://github.com/Effect-TS/morphic), etc.

All of the mentioned libraries are lovely because they are all attempting to solve a huge problem - encoding, decoding, guarding (and potentially generating arbitraries) for types.

They all use the same trick, instead of defining a type you define some sort of value from which the type is derived. The difference between the libraries lies in the details of how that sort of value is modeled.

This leads to a set of trade-offs where the libraries can end up emitting unoptimized types, end up being verbose, hard to extend, and sometimes quite painful to use.

To be clear they are still better than the alternative which would require manual implementation of everything.

After months of work we think we finally have a solution to (limited/structural) custom typeclass derivation so that you can forget the problem!

Let's dig into it:

```ts
export interface Person {
  name: string;
  age: Option<number>;
  friends: Chunk<Person>;
}

export const PersonEncoder: Encoder<Person> = Derive();
export const PersonDecoder: Decoder<Person> = Derive();
export const PersonGuard: Guard<Person> = Derive();
```

That's it, you can now do:

```ts
const encoded = PersonEncoder.encodeJSON({
  name: "Mike",
  age: Option.some(30),
  friends: Chunk()
});

const decoded = PersonDecoder.decodeJSON(encoded);

if (decoded.isRight()) {
  //
}

const maybePerson = {};

if (PersonGuard.is(maybePerson)) {
  maybePerson.age;
}
```

You may ask, how?? well the compiler can tell you, add a "explain" parameter to a call to `Derive` like:

![Derivation](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zfp03hyj3h93lzxspziv.png)

And the best thing is that there is nothing special to the 3 instances above in fact all the rules are custom and extensible:
[Guard.ts](https://github.com/ts-plus/stdlib/blob/03511f8f5a56aa3536e0fdf4203ea60031b996ad/packages/stdlib/_src/runtime/Guard.ts)
[Encoder.ts](https://github.com/ts-plus/stdlib/blob/03511f8f5a56aa3536e0fdf4203ea60031b996ad/packages/stdlib/_src/runtime/Encoder.ts)
[Decoder.ts](https://github.com/ts-plus/stdlib/blob/03511f8f5a56aa3536e0fdf4203ea60031b996ad/packages/stdlib/_src/runtime/Decoder.ts)

Each derivable type is defined as an interface with a tag like:

```ts
/**
 * A Guard<A> is a type representing the ability to identify when a value is of type A at runtime
 *
 * @tsplus type Guard
 */
export interface Guard<A> {
  readonly is: (u: unknown) => u is A;
}
```

Then implicit instances are defined as:

```ts
/**
 * Guard for a number
 *
 * @tsplus implicit
 */
export const number: Guard<number> = Guard((u): u is number => typeof u === "number");
```

And rules as:

```ts
/**
 * @tsplus derive Guard lazy
 */
export function deriveLazy<A>(
  fn: (_: Guard<A>) => Guard<A>
): Guard<A> {
  let cached: Guard<A> | undefined;
  const guard: Guard<A> = Guard((u): u is A => {
    if (!cached) {
      cached = fn(guard);
    }
    return cached.is(u);
  });
  return guard;
}

/**
 * @tsplus derive Guard<_> 10
 */
export function deriveLiteral<A extends string | number>(
  ...[value]: Check<Check.IsLiteral<A> & Check.Not<Check.IsUnion<A>>> extends Check.True ? [value: A] : never
): Guard<A> {
  return Guard((u): u is A => u === value);
}

/**
 * @tsplus derive Guard[Option]<_> 10
 */
export function deriveOption<A extends Option<any>>(
  ...[element]: [A] extends [Option<infer _A>] ? [element: Guard<_A>]
    : never
): Guard<A> {
  return Guard((u): u is A => {
    if (u instanceof None) {
      return true;
    }
    if (u instanceof Some) {
      return element.is(u.value);
    }
    return false;
  });
}
```

The only special rule here is the one called `lazy` which is used when the compiler encounters a recursive derivation, the remaining are all custom.

A rule is a function with a `rule` tag which has the following format: 

`@tsplus derive Guard[Option]<_> 10` 

Where:
- `Guard` is the tag of the typeclass we want to derive
- `[Option]` lets you (if not omitted) further scope when the rule is applied (in this case to types like `Show<Option<A>>`)
- `<_>` tells the compiler how to call the function arguments and has to be specified per each generic of the typeclass we are deriving (for example `Refinement<_,_>`), this can be 
  - `_` calls it with the type, this case "Option<A>"
  - `|` match a union and calls with a tuple of the members
  - `&` match an intersection and calls with a tuple of the members
  - `[]` match a tuple and calls with a tuple of the members
- 10 is a priority that defines how early this rule is applied

## Using TS+ (Module Structure and Config) <a name="module-structure"></a>

We've been going trough a list of example and by now you probably noticed we tend to use fully qualified imports like `"@tsplus/stdlib/collections/Chunk/definition"` even for local references.

You are not forced to do the same. However, for global imports and extensions in general your files must also be importable via a fully qualified name, the mapping between "file => fully qualified import" together with a map of "file => tracing name" is defined in a dedicated config like the following: [tsplus.config.json](https://github.com/ts-plus/starter-lib/blob/main/tsplus.config.json).

The trace map is needed because in TS+ you can support compile time tracing of your function calls, for example if you have a function like:

```ts
function hello(message: string, __tsPlusTrace?: string) {
}
```

when a call expression like `hello("world")` is found if a trace is not passed explicitly the compiler fill it up with `hello("world", "file_trace_as_per_in_map:line:col")`.

While this is not something that can be relied upon as always present (because clearly a non TS+ user will not have a compiler that fill up the trace), when it is present this can be a powerful tool to build easily debuggable systems. For example, the tracing system inside Effect allows for rendering full stack traces, even for programs with async operations.

## Using TS+ (Compiled Output) <a name="compiled-output"></a>

So how does it compile in practice? When an exported value has a tag like static/fluent/implicit/derivation etc when we emit definition files `.d.ts` per each function we add a further jsdoc annotation called "location" like:

```ts
/**
 * @tsplus fluent Decoder decode
 * @tsplus location "@tsplus/stdlib/runtime/Decoder"
 */
export declare function decode<A>(decoder: Decoder<A>, value: unknown): import("../data/Either").Either<string, A>;
```

This is how we know how to import things and from where, in a JS file when something like that is used an import is introduced accordingly.

This design ensures modules are always imported as close to the declaration as possible and it helps preventing circular references, it may lead to a big number of imports in the compiled `js` files but tree-shaking can inline very well.

## What's next for TS+ <a name="whats-next"></a>

We think we are pretty much feature ready for the first decent release, we have to write tons of tests and finish extracting out the standard library from the effect codebase before this is considered usable at a decent level externally (internally effect is already written in TS+) but we feel very confident and the TS folks have been amazing in offering valuable advise.