Repository: gcanti/io-ts
Pull request #471 (2020-05-20 13:51:18)

version 2.2.3


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index dff0679..7435b5f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -14,6 +14,31 @@
 **Note**: Gaps between patch versions are faulty/broken releases.
 **Note**: A feature tagged as Experimental is in a high state of flux, you're at risk of it changing without notice.
 
+# 2.2.3
+
+- **Polish**
+  - use the built-in `Readonly` type in `readonly` combinator, closes #472 (@gcanti)
+- **Experimental**
+  - split instances (@gcanti)
+  - add `Type` experimental module, #464 (@gcanti)
+  - restore the `O` type parameter to `Encoder`, #469 (@gcanti)
+  - add the `O` type parameter to `Codec` (@gcanti)
+  - add `JsonCodec` experimental module (@gcanti)
+  - add `JsonEncoder` experimental module (@gcanti)
+  - `Schemable`
+    - rename `Schemable` interface to `Schemable1` (@gcanti)
+    - rename `WithUnion` interface to `WithUnion1` (@gcanti)
+    - add `Schemable` interface (@gcanti)
+    - add `WithUnion` interface (@gcanti)
+    - add `WithRefinement` / `WithRefinement1` interfaces (@gcanti)
+    - add `WithUnknownContainers` / `WithUnknownContainers1` interfaces (@gcanti)
+      - move `UnknownArray` and `UnknownRecord` to `WithUnknownContainers` (@gcanti)
+  - `Schema`
+    - refactoring with the new `Schemable` interface (@gcanti)
+    - add `interpreter` function (@gcanti)
+- **Internal**
+  - upgrade to `typescript@3.9.3` (@gcanti)
+
 # 2.2.2
 
 - **Experimental**
diff --git a/Codec.md b/Codec.md
index f3cf40d..9efabb5 100644
--- a/Codec.md
+++ b/Codec.md
@@ -2,8 +2,6 @@
 <!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
 
 - [Codec interface](#codec-interface)
-- [Built-in primitive codecs](#built-in-primitive-codecs)
-- [Combinators](#combinators)
 
 <!-- END doctoc generated TOC please keep comment here to allow auto update -->
 
@@ -27,34 +25,17 @@ You can build a new codec using the `make` helper
 ```ts
 import * as C from 'io-ts/lib/Codec'
 import * as D from 'io-ts/lib/Decoder'
+import * as E from 'io-ts/lib/Encoder'
 import { left, right } from 'fp-ts/lib/Either'
 
-const NumberFromString: C.Codec<number> = C.make(
-  D.parse(D.string, (s) => {
-    const n = parseFloat(s)
-    return isNaN(n) ? left(`cannot decode ${JSON.stringify(s)}, should be parsable into a number`) : right(n)
-  }),
-  { encode: String }
-)
-```
+const decoder: D.Decoder<number> = D.parse(D.string, (s) => {
+  const n = parseFloat(s)
+  return isNaN(n) ? left(`cannot decode ${JSON.stringify(s)}, should be parsable into a number`) : right(n)
+})
+
+const encoder: E.Encoder<string, unknown> = {
+  encode: String
+}
 
-# Built-in primitive codecs
-
-- `string: Decoder<string>`
-- `number: Decoder<number>`
-- `boolean: Decoder<boolean>`
-- `UnknownArray: Decoder<Array<unknown>>`
-- `UnknownRecord: Decoder<Record<string, unknown>>`
-
-# Combinators
-
-- `literal`
-- `nullable`
-- `type`
-- `partial`
-- `record`
-- `array`
-- `tuple`
-- `intersection`
-- `sum`
-- `lazy`
+export const NumberFromString: C.Codec<string, number> = C.make(decoder, encoder)
+```
diff --git a/Encoder.md b/Encoder.md
index bc63223..cbeb0f4 100644
--- a/Encoder.md
+++ b/Encoder.md
@@ -2,15 +2,14 @@
 <!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
 
 - [Encoder interface](#encoder-interface)
-- [Combinators](#combinators)
 
 <!-- END doctoc generated TOC please keep comment here to allow auto update -->
 
 # Encoder interface
 
 ```ts
-export interface Encoder<A> {
-  readonly encode: (a: A) => unknown
+export interface Encoder<O, A> {
+  readonly encode: (a: A) => O
 }
 ```
 
@@ -21,21 +20,9 @@ An encoder representing a nullable value
 ```ts
 import * as E from 'io-ts/lib/Encoder'
 
-export function nullable<A>(or: E.Encoder<A>): E.Encoder<null | A> {
+export function nullable<O, A>(or: E.Encoder<O, A>): E.Encoder<null | O, null | A> {
   return {
-    encode: (a) => (a === null ? a : or.encode(a))
+    encode: (a) => (a === null ? null : or.encode(a))
   }
 }
 ```
-
-# Combinators
-
-- `nullable`
-- `type`
-- `partial`
-- `record`
-- `array`
-- `tuple`
-- `intersection`
-- `sum`
-- `lazy`
diff --git a/README.md b/README.md
index 5d9cb74..bc60435 100644
--- a/README.md
+++ b/README.md
@@ -1,43 +1,39 @@
 [![build status](https://img.shields.io/travis/gcanti/io-ts/master.svg?style=flat-square)](https://travis-ci.org/gcanti/io-ts)
 [![dependency status](https://img.shields.io/david/gcanti/io-ts.svg?style=flat-square)](https://david-dm.org/gcanti/io-ts)
 ![npm downloads](https://img.shields.io/npm/dm/io-ts.svg)
-[![Minified Size](https://badgen.net/bundlephobia/minzip/io-ts)](https://bundlephobia.com/result?p=io-ts)
 
 <!-- START doctoc generated TOC please keep comment here to allow auto update -->
 <!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
 
 - [Installation](#installation)
-- [Documentation](#documentation)
-  - [Usage](#usage)
+- [Usage](#usage)
+  - [Stable features](#stable-features)
+  - [Experimental features (version `2.2+`)](#experimental-features-version-22)
 
 <!-- END doctoc generated TOC please keep comment here to allow auto update -->
 
 # Installation
 
-To install the stable version:
+To install the stable version
 
 ```sh
 npm i io-ts fp-ts
 ```
 
-Note: [`fp-ts`](https://github.com/gcanti/fp-ts) is a peer dependency for `io-ts`
+**Note**. [`fp-ts`](https://github.com/gcanti/fp-ts) is a peer dependency for `io-ts`
 
-# Documentation
+# Usage
 
-- [API Reference](https://gcanti.github.io/io-ts/docs/modules)
+## Stable features
 
-## Usage
+- [`index.ts` module](index.md)
 
-**Stable features**
-
-- [`Type`](Type.md)
-
-**Experimental features** (version `2.2+`)
+## Experimental features (version `2.2+`)
 
 Experimental features are published in order to get early feedback from the community, see these tracking [issues](https://github.com/gcanti/io-ts/issues?q=label%3Av2.2+) for further discussions and enhancements.
 
-- [`Decoder`](Decoder.md)
-- [`Encoder`](Encoder.md)
-- [`Codec`](Codec.md)
-- [`Eq`](Eq.md)
-- [`Schema` (advanced feature)](Schema.md)
+- [`Decoder.ts` module](Decoder.md)
+- [`Encoder.ts` module](Encoder.md)
+- [`Codec.ts` module](Codec.md)
+- [`Eq.ts` module](Eq.md)
+- [`Schema.ts` module (advanced feature)](Schema.md)
diff --git a/Schema.md b/Schema.md
index d2f1d0b..879e80f 100644
--- a/Schema.md
+++ b/Schema.md
@@ -21,8 +21,8 @@ export interface Schema<A> {
 ```ts
 import * as S from 'io-ts/lib/Schema'
 import * as D from 'io-ts/lib/Decoder'
-import * as E from 'io-ts/lib/Encoder'
-import * as C from 'io-ts/lib/Codec'
+import * as JE from 'io-ts/lib/JsonEncoder'
+import * as JC from 'io-ts/lib/JsonCodec'
 import * as G from 'io-ts/lib/Guard'
 import * as Eq from 'io-ts/lib/Eq'
 
@@ -33,11 +33,11 @@ export const Person = S.make((S) =>
   })
 )
 
-export const decoderPerson = Person(D.decoder)
-export const encoderPerson = Person(E.encoder)
-export const codecPerson = Person(C.codec)
-export const guardPerson = Person(G.guard)
-export const eqPerson = Person(Eq.eq)
+export const decoderPerson = S.interpreter(D.schemableDecoder)(Person)
+export const encoderPerson = S.interpreter(JE.schemableJsonEncoder)(Person)
+export const codecPerson = S.interpreter(JC.schemableJsonCodec)(Person)
+export const guardPerson = S.interpreter(G.schemableGuard)(Person)
+export const eqPerson = S.interpreter(Eq.schemableEq)(Person)
 ```
 
 # How to extend the built-in `Schema`
@@ -57,10 +57,13 @@ export type Int = number & IntBrand
 Now we must define a custom `MySchemable` type class containing a new member `Int`...
 
 ```ts
-import { Kind, URIS } from 'fp-ts/lib/HKT'
-import * as S from 'io-ts/lib/Schemable'
+import { Kind, URIS, HKT } from 'fp-ts/lib/HKT'
 
-export interface MySchemable<S extends URIS> extends S.Schemable<S> {
+export interface MySchemable<S> extends S.Schemable<S> {
+  readonly Int: HKT<S, Int>
+}
+
+export interface MySchemable1<S extends URIS> extends S.Schemable1<S> {
   readonly Int: Kind<S, Int>
 }
 ```
@@ -69,7 +72,7 @@ export interface MySchemable<S extends URIS> extends S.Schemable<S> {
 
 ```ts
 export interface MySchema<A> {
-  <S extends URIS>(S: MySchemable<S>): Kind<S, A>
+  <S>(S: MySchemable<S>): HKT<S, A>
 }
 
 export function make<A>(f: MySchema<A>): MySchema<A> {
@@ -77,21 +80,26 @@ export function make<A>(f: MySchema<A>): MySchema<A> {
 }
 ```
 
-Finally we must define an instance of `MySchemable` for `Decoder`
+Finally we must define an instance of `MySchemable1` for `Decoder` and an interpreter
 
 ```ts
 import * as D from 'io-ts/lib/Decoder'
 
-export const mydecoder: MySchemable<D.URI> = {
-  ...D.decoder,
+export const mySchemable: MySchemable1<D.URI> = {
+  ...D.schemableDecoder,
   Int: D.refinement(D.number, (n): n is Int => Number.isInteger(n), 'Int')
 }
+
+export function interpreter<S extends URIS>(S: MySchemable1<S>): <A>(schema: MySchema<A>) => Kind<S, A>
+export function interpreter<S>(S: MySchemable<S>): <A>(schema: MySchema<A>) => HKT<S, A> {
+  return (schema) => schema(S)
+}
 ```
 
 Now we can define a schema leveraging the new `Int` capability
 
 ```ts
-const Person = make((S) =>
+export const Person = make((S) =>
   S.type({
     name: S.string,
     age: S.Int
@@ -104,7 +112,7 @@ const Person: MySchema<{
 }>
 */
 
-const decoderPerson = Person(mydecoder)
+export const decoderPerson = interpreter(mySchemable)(Person)
 /*
 const decoderPerson: D.Decoder<{
     name: string;
diff --git a/docs/index.md b/docs/index.md
index 62a28f1..c24f805 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -3,3 +3,143 @@ title: Home
 ---
 
 # Runtime type system for IO decoding/encoding
+
+## The idea
+
+A value of type `Type<A, O, I>` (called "codec") is the runtime representation of the static type `A`.
+
+A codec can:
+
+- decode inputs of type `I` (through `decode`)
+- encode outputs of type `O` (through `encode`)
+- be used as a custom [type guard](https://basarat.gitbooks.io/typescript/content/docs/types/typeGuard.html) (through `is`)
+
+```ts
+class Type<A, O, I> {
+  constructor(
+    /** a unique name for this codec */
+    readonly name: string,
+
+    /** a custom type guard */
+    readonly is: (u: unknown) => u is A,
+
+    /** succeeds if a value of type I can be decoded to a value of type A */
+    readonly validate: (input: I, context: Context) => Either<Errors, A>,
+
+    /** converts a value of type A to a value of type O */
+    readonly encode: (a: A) => O
+  ) {}
+
+  /** a version of `validate` with a default context */
+  decode(i: I): Either<Errors, A>
+}
+```
+
+The [`Either`](https://gcanti.github.io/fp-ts/modules/Either.ts.html) type returned by `decode` is defined in [fp-ts](https://github.com/gcanti/fp-ts), a library containing implementations of common algebraic types in TypeScript.
+
+The `Either` type represents a value of one of two possible types (a disjoint union). An instance of `Either` is either an instance of `Left` or `Right`:
+
+```ts
+type Either<E, A> =
+  | {
+      readonly _tag: 'Left'
+      readonly left: E
+    }
+  | {
+      readonly _tag: 'Right'
+      readonly right: A
+    }
+```
+
+Convention dictates that `Left` is used for **failure** and `Right` is used for **success**.
+
+**Example**
+
+A codec representing `string` can be defined as:
+
+```ts
+import * as t from 'io-ts'
+
+const string = new t.Type<string, string, unknown>(
+  'string',
+  (input: unknown): input is string => typeof input === 'string',
+  // `t.success` and `t.failure` are helpers used to build `Either` instances
+  (input, context) => (typeof input === 'string' ? t.success(input) : t.failure(input, context)),
+  // `A` and `O` are the same, so `encode` is just the identity function
+  t.identity
+)
+```
+
+and we can use it as follows:
+
+```ts
+import { isRight } from 'fp-ts/lib/Either'
+
+isRight(string.decode('a string')) // true
+isRight(string.decode(null)) // false
+```
+
+More generally the result of calling `decode` can be handled using [`fold`](https://gcanti.github.io/fp-ts/modules/Either.ts.html#fold-function) along with `pipe` (which is similar to the pipeline operator)
+
+```ts
+import * as t from 'io-ts'
+import { pipe } from 'fp-ts/lib/pipeable'
+import { fold } from 'fp-ts/lib/Either'
+
+// failure handler
+const onLeft = (errors: t.Errors): string => `${errors.length} error(s) found`
+
+// success handler
+const onRight = (s: string) => `No errors: ${s}`
+
+pipe(t.string.decode('a string'), fold(onLeft, onRight))
+// => "No errors: a string"
+
+pipe(t.string.decode(null), fold(onLeft, onRight))
+// => "1 error(s) found"
+```
+
+We can combine these codecs through [combinators](#implemented-types--combinators) to build composite types which represent entities like domain models, request payloads etc. in our applications:
+
+```ts
+import * as t from 'io-ts'
+
+const User = t.type({
+  userId: t.number,
+  name: t.string
+})
+```
+
+So this is equivalent to defining something like:
+
+```ts
+type User = {
+  userId: number
+  name: string
+}
+```
+
+The advantage of using `io-ts` to define the runtime type is that we can validate the type at runtime, and we can also extract the corresponding static type, so we don’t have to define it twice.
+
+## Error reporters
+
+A reporter implements the following interface
+
+```ts
+interface Reporter<A> {
+  report: (validation: Validation<any>) => A
+}
+```
+
+This package exports a default `PathReporter` reporter
+
+Example
+
+```ts
+import { PathReporter } from 'io-ts/lib/PathReporter'
+
+const result = User.decode({ name: 'Giulio' })
+
+console.log(PathReporter.report(result))
+// => [ 'Invalid value undefined supplied to : { userId: number, name: string }/userId: number' ]
+```
diff --git a/docs/modules/Codec.ts.md b/docs/modules/Codec.ts.md
index 18cde81..eccdb94 100644
--- a/docs/modules/Codec.ts.md
+++ b/docs/modules/Codec.ts.md
@@ -6,13 +6,14 @@ parent: Modules
 
 # Codec overview
 
-Added in v2.2.0
+Added in v2.2.3
 
 ---
 
 <h2 class="text-delta">Table of contents</h2>
 
 - [Codec (interface)](#codec-interface)
+- [OutputOf (type alias)](#outputof-type-alias)
 - [TypeOf (type alias)](#typeof-type-alias)
 - [URI (type alias)](#uri-type-alias)
 - [URI](#uri)
@@ -20,8 +21,10 @@ Added in v2.2.0
 - [UnknownRecord](#unknownrecord)
 - [array](#array)
 - [boolean](#boolean)
-- [codec](#codec)
+- [fromDecoder](#fromdecoder)
+- [imap](#imap)
 - [intersection](#intersection)
+- [invariantCodec](#invariantcodec)
 - [lazy](#lazy)
 - [literal](#literal)
 - [make](#make)
@@ -48,20 +51,30 @@ Laws:
 **Signature**
 
 ```ts
-export interface Codec<A> extends D.Decoder<A>, E.Encoder<A> {}
+export interface Codec<O, A> extends D.Decoder<A>, E.Encoder<O, A> {}
 ```
 
-Added in v2.2.0
+Added in v2.2.3
+
+# OutputOf (type alias)
+
+**Signature**
+
+```ts
+export type OutputOf<C> = E.OutputOf<C>
+```
+
+Added in v2.2.3
 
 # TypeOf (type alias)
 
 **Signature**
 
 ```ts
-export type TypeOf<C> = C extends Codec<infer A> ? A : never
+export type TypeOf<C> = E.TypeOf<C>
 ```
 
-Added in v2.2.2
+Added in v2.2.3
 
 # URI (type alias)
 
@@ -71,171 +84,193 @@ Added in v2.2.2
 export type URI = typeof URI
 ```
 
-Added in v2.2.0
+Added in v2.2.3
 
 # URI
 
 **Signature**
 
 ```ts
-export declare const URI: 'Codec'
+export declare const URI: 'io-ts/Codec'
 ```
 
-Added in v2.2.0
+Added in v2.2.3
 
 # UnknownArray
 
 **Signature**
 
 ```ts
-export declare const UnknownArray: Codec<unknown[]>
+export declare const UnknownArray: Codec<unknown[], unknown[]>
 ```
 
-Added in v2.2.0
+Added in v2.2.3
 
 # UnknownRecord
 
 **Signature**
 
 ```ts
-export declare const UnknownRecord: Codec<Record<string, unknown>>
+export declare const UnknownRecord: Codec<Record<string, unknown>, Record<string, unknown>>
 ```
 
-Added in v2.2.0
+Added in v2.2.3
 
 # array
 
 **Signature**
 
 ```ts
-export declare function array<A>(items: Codec<A>): Codec<Array<A>>
+export declare function array<O, A>(items: Codec<O, A>): Codec<Array<O>, Array<A>>
 ```
 
-Added in v2.2.0
+Added in v2.2.3
 
 # boolean
 
 **Signature**
 
 ```ts
-export declare const boolean: Codec<boolean>
+export declare const boolean: Codec<boolean, boolean>
 ```
 
-Added in v2.2.0
+Added in v2.2.3
 
-# codec
+# fromDecoder
 
 **Signature**
 
 ```ts
-export declare const codec: Invariant1<'Codec'> & Schemable<'Codec'>
+export declare function fromDecoder<A>(decoder: D.Decoder<A>): Codec<A, A>
 ```
 
-Added in v2.2.0
+Added in v2.2.3
+
+# imap
+
+**Signature**
+
+```ts
+export declare const imap: <E, A, B>(f: (a: A) => B, g: (b: B) => A) => (fa: Codec<E, A>) => Codec<E, B>
+```
+
+Added in v2.2.3
 
 # intersection
 
 **Signature**
 
 ```ts
-export declare function intersection<A, B>(left: Codec<A>, right: Codec<B>): Codec<A & B>
+export declare function intersection<O, A, P, B>(left: Codec<O, A>, right: Codec<P, B>): Codec<O & P, A & B>
+```
+
+Added in v2.2.3
+
+# invariantCodec
+
+**Signature**
+
+```ts
+export declare const invariantCodec: Invariant2<'io-ts/Codec'>
 ```
 
-Added in v2.2.0
+Added in v2.2.3
 
 # lazy
 
 **Signature**
 
 ```ts
-export declare function lazy<A>(id: string, f: () => Codec<A>): Codec<A>
+export declare function lazy<O, A>(id: string, f: () => Codec<O, A>): Codec<O, A>
 ```
 
-Added in v2.2.0
+Added in v2.2.3
 
 # literal
 
 **Signature**
 
 ```ts
-export declare function literal<A extends ReadonlyArray<Literal>>(...values: A): Codec<A[number]>
+export declare function literal<A extends ReadonlyArray<Literal>>(...values: A): Codec<A[number], A[number]>
 ```
 
-Added in v2.2.0
+Added in v2.2.3
 
 # make
 
 **Signature**
 
 ```ts
-export declare function make<A>(decoder: D.Decoder<A>, encoder: E.Encoder<A>): Codec<A>
+export declare function make<O, A>(decoder: D.Decoder<A>, encoder: E.Encoder<O, A>): Codec<O, A>
 ```
 
-Added in v2.2.0
+Added in v2.2.3
 
 # nullable
 
 **Signature**
 
 ```ts
-export declare function nullable<A>(or: Codec<A>): Codec<null | A>
+export declare function nullable<O, A>(or: Codec<O, A>): Codec<null | O, null | A>
 ```
 
-Added in v2.2.0
+Added in v2.2.3
 
 # number
 
 **Signature**
 
 ```ts
-export declare const number: Codec<number>
+export declare const number: Codec<number, number>
 ```
 
-Added in v2.2.0
+Added in v2.2.3
 
 # partial
 
 **Signature**
 
 ```ts
-export declare function partial<A>(properties: { [K in keyof A]: Codec<A[K]> }): Codec<Partial<A>>
+export declare function partial<P extends Record<string, Codec<any, any>>>(
+  properties: P
+): Codec<Partial<{ [K in keyof P]: OutputOf<P[K]> }>, Partial<{ [K in keyof P]: TypeOf<P[K]> }>>
 ```
 
-Added in v2.2.0
+Added in v2.2.3
 
 # record
 
 **Signature**
 
 ```ts
-export declare function record<A>(codomain: Codec<A>): Codec<Record<string, A>>
+export declare function record<O, A>(codomain: Codec<O, A>): Codec<Record<string, O>, Record<string, A>>
 ```
 
-Added in v2.2.0
+Added in v2.2.3
 
 # refinement
 
 **Signature**
 
 ```ts
-export declare function refinement<A, B extends A>(
-  from: Codec<A>,
+export declare function refinement<O, A, B extends A>(
+  from: Codec<O, A>,
   refinement: (a: A) => a is B,
   expected: string
-): Codec<B>
+): Codec<O, B>
 ```
 
-Added in v2.2.0
+Added in v2.2.3
 
 # string
 
 **Signature**
 
 ```ts
-export declare const string: Codec<string>
+export declare const string: Codec<string, string>
 ```
 
-Added in v2.2.0
+Added in v2.2.3
 
 # sum
 
@@ -244,42 +279,44 @@ Added in v2.2.0
 ```ts
 export declare function sum<T extends string>(
   tag: T
-): <A>(members: { [K in keyof A]: Codec<A[K] & Record<T, K>> }) => Codec<A[keyof A]>
+): <M extends Record<string, Codec<any, any>>>(members: M) => Codec<OutputOf<M[keyof M]>, TypeOf<M[keyof M]>>
 ```
 
-Added in v2.2.0
+Added in v2.2.3
 
 # tuple
 
 **Signature**
 
 ```ts
-export declare function tuple<A extends ReadonlyArray<unknown>>(
-  ...components: { [K in keyof A]: Codec<A[K]> }
-): Codec<A>
+export declare function tuple<C extends ReadonlyArray<Codec<any, any>>>(
+  ...components: C
+): Codec<{ [K in keyof C]: OutputOf<C[K]> }, { [K in keyof C]: TypeOf<C[K]> }>
 ```
 
-Added in v2.2.0
+Added in v2.2.3
 
 # type
 
 **Signature**
 
 ```ts
-export declare function type<A>(properties: { [K in keyof A]: Codec<A[K]> }): Codec<A>
+export declare function type<P extends Record<string, Codec<any, any>>>(
+  properties: P
+): Codec<{ [K in keyof P]: OutputOf<P[K]> }, { [K in keyof P]: TypeOf<P[K]> }>
 ```
 
-Added in v2.2.0
+Added in v2.2.3
 
 # withExpected
 
 **Signature**
 
 ```ts
-export declare function withExpected<A>(
-  codec: Codec<A>,
+export declare function withExpected<O, A>(
+  codec: Codec<O, A>,
   expected: (actual: unknown, e: D.DecodeError) => D.DecodeError
-): Codec<A>
+): Codec<O, A>
 ```
 
-Added in v2.2.0
+Added in v2.2.3
diff --git a/docs/modules/Decoder.ts.md b/docs/modules/Decoder.ts.md
index 7b3aa55..cd173c5 100644
--- a/docs/modules/Decoder.ts.md
+++ b/docs/modules/Decoder.ts.md
@@ -20,14 +20,12 @@ Added in v2.2.0
 - [UnknownArray](#unknownarray)
 - [UnknownRecord](#unknownrecord)
 - [alt](#alt)
-- [ap](#ap)
-- [apFirst](#apfirst)
-- [apSecond](#apsecond)
+- [altDecoder](#altdecoder)
 - [array](#array)
 - [boolean](#boolean)
-- [decoder](#decoder)
 - [failure](#failure)
 - [fromGuard](#fromguard)
+- [functorDecoder](#functordecoder)
 - [intersection](#intersection)
 - [isNotEmpty](#isnotempty)
 - [lazy](#lazy)
@@ -36,10 +34,12 @@ Added in v2.2.0
 - [never](#never)
 - [nullable](#nullable)
 - [number](#number)
+- [of](#of)
 - [parse](#parse)
 - [partial](#partial)
 - [record](#record)
 - [refinement](#refinement)
+- [schemableDecoder](#schemabledecoder)
 - [string](#string)
 - [success](#success)
 - [sum](#sum)
@@ -98,7 +98,7 @@ Added in v2.2.0
 **Signature**
 
 ```ts
-export declare const URI: 'Decoder'
+export declare const URI: 'io-ts/Decoder'
 ```
 
 Added in v2.2.0
@@ -133,35 +133,15 @@ export declare const alt: <A>(that: () => Decoder<A>) => (fa: Decoder<A>) => Dec
 
 Added in v2.2.0
 
-# ap
+# altDecoder
 
 **Signature**
 
 ```ts
-export declare const ap: <A>(fa: Decoder<A>) => <B>(fab: Decoder<(a: A) => B>) => Decoder<B>
+export declare const altDecoder: Alt1<'io-ts/Decoder'>
 ```
 
-Added in v2.2.0
-
-# apFirst
-
-**Signature**
-
-```ts
-export declare const apFirst: <B>(fb: Decoder<B>) => <A>(fa: Decoder<A>) => Decoder<A>
-```
-
-Added in v2.2.0
-
-# apSecond
-
-**Signature**
-
-```ts
-export declare const apSecond: <B>(fb: Decoder<B>) => <A>(fa: Decoder<A>) => Decoder<B>
-```
-
-Added in v2.2.0
+Added in v2.2.3
 
 # array
 
@@ -183,38 +163,35 @@ export declare const boolean: Decoder<boolean>
 
 Added in v2.2.0
 
-# decoder
+# failure
 
 **Signature**
 
 ```ts
-export declare const decoder: Applicative1<'Decoder'> &
-  Alternative1<'Decoder'> &
-  Schemable<'Decoder'> &
-  WithUnion<'Decoder'>
+export declare function failure<A = never>(message: string): Either<DecodeError, A>
 ```
 
 Added in v2.2.0
 
-# failure
+# fromGuard
 
 **Signature**
 
 ```ts
-export declare function failure<A = never>(message: string): Either<DecodeError, A>
+export declare function fromGuard<A>(guard: G.Guard<A>, expected: string): Decoder<A>
 ```
 
 Added in v2.2.0
 
-# fromGuard
+# functorDecoder
 
 **Signature**
 
 ```ts
-export declare function fromGuard<A>(guard: G.Guard<A>, expected: string): Decoder<A>
+export declare const functorDecoder: Functor1<'io-ts/Decoder'>
 ```
 
-Added in v2.2.0
+Added in v2.2.3
 
 # intersection
 
@@ -296,6 +273,16 @@ export declare const number: Decoder<number>
 
 Added in v2.2.0
 
+# of
+
+**Signature**
+
+```ts
+export declare function of<A>(a: A): Decoder<A>
+```
+
+Added in v2.2.3
+
 # parse
 
 **Signature**
@@ -340,6 +327,19 @@ export declare function refinement<A, B extends A>(
 
 Added in v2.2.0
 
+# schemableDecoder
+
+**Signature**
+
+```ts
+export declare const schemableDecoder: Schemable1<'io-ts/Decoder'> &
+  WithUnknownContainers1<'io-ts/Decoder'> &
+  WithUnion1<'io-ts/Decoder'> &
+  WithRefinement1<'io-ts/Decoder'>
+```
+
+Added in v2.2.3
+
 # string
 
 **Signature**
@@ -367,7 +367,7 @@ Added in v2.2.0
 ```ts
 export declare function sum<T extends string>(
   tag: T
-): <A>(members: { [K in keyof A]: Decoder<A[K] & Record<T, K>> }) => Decoder<A[keyof A]>
+): <A>(members: { [K in keyof A]: Decoder<A[K]> }) => Decoder<A[keyof A]>
 ```
 
 Added in v2.2.0
diff --git a/docs/modules/Encoder.ts.md b/docs/modules/Encoder.ts.md
index efeb677..0c79914 100644
--- a/docs/modules/Encoder.ts.md
+++ b/docs/modules/Encoder.ts.md
@@ -6,19 +6,22 @@ parent: Modules
 
 # Encoder overview
 
-Added in v2.2.0
+Added in v2.2.3
 
 ---
 
 <h2 class="text-delta">Table of contents</h2>
 
 - [Encoder (interface)](#encoder-interface)
+- [OutputOf (type alias)](#outputof-type-alias)
 - [TypeOf (type alias)](#typeof-type-alias)
 - [URI (type alias)](#uri-type-alias)
 - [URI](#uri)
 - [array](#array)
+- [categoryEncoder](#categoryencoder)
+- [compose](#compose)
 - [contramap](#contramap)
-- [encoder](#encoder)
+- [contravariantEncoder](#contravariantencoder)
 - [id](#id)
 - [intersection](#intersection)
 - [lazy](#lazy)
@@ -36,22 +39,32 @@ Added in v2.2.0
 **Signature**
 
 ```ts
-export interface Encoder<A> {
-  readonly encode: (a: A) => unknown
+export interface Encoder<O, A> {
+  readonly encode: (a: A) => O
 }
 ```
 
-Added in v2.2.0
+Added in v2.2.3
+
+# OutputOf (type alias)
+
+**Signature**
+
+```ts
+export type OutputOf<E> = E extends Encoder<infer O, any> ? O : never
+```
+
+Added in v2.2.3
 
 # TypeOf (type alias)
 
 **Signature**
 
 ```ts
-export type TypeOf<E> = E extends Encoder<infer A> ? A : never
+export type TypeOf<E> = E extends Encoder<any, infer A> ? A : never
 ```
 
-Added in v2.2.2
+Added in v2.2.3
 
 # URI (type alias)
 
@@ -61,107 +74,129 @@ Added in v2.2.2
 export type URI = typeof URI
 ```
 
-Added in v2.2.0
+Added in v2.2.3
 
 # URI
 
 **Signature**
 
 ```ts
-export declare const URI: 'Encoder'
+export declare const URI: 'io-ts/Encoder'
 ```
 
-Added in v2.2.0
+Added in v2.2.3
 
 # array
 
 **Signature**
 
 ```ts
-export declare function array<A>(items: Encoder<A>): Encoder<Array<A>>
+export declare function array<O, A>(items: Encoder<O, A>): Encoder<Array<O>, Array<A>>
+```
+
+Added in v2.2.3
+
+# categoryEncoder
+
+**Signature**
+
+```ts
+export declare const categoryEncoder: Category2<'io-ts/Encoder'>
+```
+
+Added in v2.2.3
+
+# compose
+
+**Signature**
+
+```ts
+export declare const compose: <E, A>(ea: Encoder<E, A>) => <B>(ab: Encoder<A, B>) => Encoder<E, B>
 ```
 
-Added in v2.2.0
+Added in v2.2.3
 
 # contramap
 
 **Signature**
 
 ```ts
-export declare const contramap: <A, B>(f: (b: B) => A) => (fa: Encoder<A>) => Encoder<B>
+export declare const contramap: <A, B>(f: (b: B) => A) => <E>(fa: Encoder<E, A>) => Encoder<E, B>
 ```
 
-Added in v2.2.0
+Added in v2.2.3
 
-# encoder
+# contravariantEncoder
 
 **Signature**
 
 ```ts
-export declare const encoder: Contravariant1<'Encoder'> & Schemable<'Encoder'>
+export declare const contravariantEncoder: Contravariant2<'io-ts/Encoder'>
 ```
 
-Added in v2.2.0
+Added in v2.2.3
 
 # id
 
 **Signature**
 
 ```ts
-export declare const id: Encoder<unknown>
+export declare function id<A>(): Encoder<A, A>
 ```
 
-Added in v2.2.0
+Added in v2.2.3
 
 # intersection
 
 **Signature**
 
 ```ts
-export declare function intersection<A, B>(left: Encoder<A>, right: Encoder<B>): Encoder<A & B>
+export declare function intersection<O, A, P, B>(left: Encoder<O, A>, right: Encoder<P, B>): Encoder<O & P, A & B>
 ```
 
-Added in v2.2.0
+Added in v2.2.3
 
 # lazy
 
 **Signature**
 
 ```ts
-export declare function lazy<A>(f: () => Encoder<A>): Encoder<A>
+export declare function lazy<O, A>(f: () => Encoder<O, A>): Encoder<O, A>
 ```
 
-Added in v2.2.0
+Added in v2.2.3
 
 # nullable
 
 **Signature**
 
 ```ts
-export declare function nullable<A>(or: Encoder<A>): Encoder<null | A>
+export declare function nullable<O, A>(or: Encoder<O, A>): Encoder<null | O, null | A>
 ```
 
-Added in v2.2.0
+Added in v2.2.3
 
 # partial
 
 **Signature**
 
 ```ts
-export declare function partial<A>(properties: { [K in keyof A]: Encoder<A[K]> }): Encoder<Partial<A>>
+export declare function partial<P extends Record<string, Encoder<any, any>>>(
+  properties: P
+): Encoder<Partial<{ [K in keyof P]: OutputOf<P[K]> }>, Partial<{ [K in keyof P]: TypeOf<P[K]> }>>
 ```
 
-Added in v2.2.0
+Added in v2.2.3
 
 # record
 
 **Signature**
 
 ```ts
-export declare function record<A>(codomain: Encoder<A>): Encoder<Record<string, A>>
+export declare function record<O, A>(codomain: Encoder<O, A>): Encoder<Record<string, O>, Record<string, A>>
 ```
 
-Added in v2.2.0
+Added in v2.2.3
 
 # sum
 
@@ -170,29 +205,31 @@ Added in v2.2.0
 ```ts
 export declare function sum<T extends string>(
   tag: T
-): <A>(members: { [K in keyof A]: Encoder<A[K] & Record<T, K>> }) => Encoder<A[keyof A]>
+): <M extends Record<string, Encoder<any, any>>>(members: M) => Encoder<OutputOf<M[keyof M]>, TypeOf<M[keyof M]>>
 ```
 
-Added in v2.2.0
+Added in v2.2.3
 
 # tuple
 
 **Signature**
 
 ```ts
-export declare function tuple<A extends ReadonlyArray<unknown>>(
-  ...components: { [K in keyof A]: Encoder<A[K]> }
-): Encoder<A>
+export declare function tuple<C extends ReadonlyArray<Encoder<any, any>>>(
+  ...components: C
+): Encoder<{ [K in keyof C]: OutputOf<C[K]> }, { [K in keyof C]: TypeOf<C[K]> }>
 ```
 
-Added in v2.2.0
+Added in v2.2.3
 
 # type
 
 **Signature**
 
 ```ts
-export declare function type<A>(properties: { [K in keyof A]: Encoder<A[K]> }): Encoder<A>
+export declare function type<P extends Record<string, Encoder<any, any>>>(
+  properties: P
+): Encoder<{ [K in keyof P]: OutputOf<P[K]> }, { [K in keyof P]: TypeOf<P[K]> }>
 ```
 
-Added in v2.2.0
+Added in v2.2.3
diff --git a/docs/modules/Eq.ts.md b/docs/modules/Eq.ts.md
index a55a06c..ee6152d 100644
--- a/docs/modules/Eq.ts.md
+++ b/docs/modules/Eq.ts.md
@@ -13,17 +13,18 @@ Added in v2.2.2
 <h2 class="text-delta">Table of contents</h2>
 
 - [TypeOf (type alias)](#typeof-type-alias)
+- [URI (type alias)](#uri-type-alias)
 - [UnknownArray](#unknownarray)
 - [UnknownRecord](#unknownrecord)
 - [array](#array)
 - [boolean](#boolean)
-- [eq](#eq)
 - [intersection](#intersection)
 - [lazy](#lazy)
 - [nullable](#nullable)
 - [number](#number)
 - [partial](#partial)
 - [record](#record)
+- [schemableEq](#schemableeq)
 - [string](#string)
 - [sum](#sum)
 - [tuple](#tuple)
@@ -41,6 +42,16 @@ export type TypeOf<E> = E extends Eq<infer A> ? A : never
 
 Added in v2.2.2
 
+# URI (type alias)
+
+**Signature**
+
+```ts
+export type URI = E.URI
+```
+
+Added in v2.2.3
+
 # UnknownArray
 
 **Signature**
@@ -81,16 +92,6 @@ export declare const boolean: E.Eq<boolean>
 
 Added in v2.2.2
 
-# eq
-
-**Signature**
-
-```ts
-export declare const eq: Contravariant1<'Eq'> & S.Schemable<'Eq'>
-```
-
-Added in v2.2.2
-
 # intersection
 
 **Signature**
@@ -151,6 +152,16 @@ export declare const record: <A>(codomain: E.Eq<A>) => E.Eq<Record<string, A>>
 
 Added in v2.2.2
 
+# schemableEq
+
+**Signature**
+
+```ts
+export declare const schemableEq: Schemable1<'Eq'> & WithUnknownContainers1<'Eq'> & WithRefinement1<'Eq'>
+```
+
+Added in v2.2.3
+
 # string
 
 **Signature**
@@ -166,9 +177,7 @@ Added in v2.2.2
 **Signature**
 
 ```ts
-export declare function sum<T extends string>(
-  tag: T
-): <A>(members: { [K in keyof A]: Eq<A[K] & Record<T, K>> }) => Eq<A[keyof A]>
+export declare function sum<T extends string>(tag: T): <A>(members: { [K in keyof A]: Eq<A[K]> }) => Eq<A[keyof A]>
 ```
 
 Added in v2.2.2
diff --git a/docs/modules/Guard.ts.md b/docs/modules/Guard.ts.md
index ebbd591..bc11d25 100644
--- a/docs/modules/Guard.ts.md
+++ b/docs/modules/Guard.ts.md
@@ -20,7 +20,6 @@ Added in v2.2.0
 - [UnknownRecord](#unknownrecord)
 - [array](#array)
 - [boolean](#boolean)
-- [guard](#guard)
 - [intersection](#intersection)
 - [lazy](#lazy)
 - [literal](#literal)
@@ -30,6 +29,7 @@ Added in v2.2.0
 - [partial](#partial)
 - [record](#record)
 - [refinement](#refinement)
+- [schemableGuard](#schemableguard)
 - [string](#string)
 - [sum](#sum)
 - [tuple](#tuple)
@@ -75,7 +75,7 @@ Added in v2.2.0
 **Signature**
 
 ```ts
-export declare const URI: 'Guard'
+export declare const URI: 'io-ts/Guard'
 ```
 
 Added in v2.2.0
@@ -120,16 +120,6 @@ export declare const boolean: Guard<boolean>
 
 Added in v2.2.0
 
-# guard
-
-**Signature**
-
-```ts
-export declare const guard: Schemable<'Guard'> & WithUnion<'Guard'>
-```
-
-Added in v2.2.0
-
 # intersection
 
 **Signature**
@@ -220,6 +210,19 @@ export declare function refinement<A, B extends A>(from: Guard<A>, refinement: (
 
 Added in v2.2.0
 
+# schemableGuard
+
+**Signature**
+
+```ts
+export declare const schemableGuard: Schemable1<'io-ts/Guard'> &
+  WithUnknownContainers1<'io-ts/Guard'> &
+  WithUnion1<'io-ts/Guard'> &
+  WithRefinement1<'io-ts/Guard'>
+```
+
+Added in v2.2.3
+
 # string
 
 **Signature**
@@ -237,7 +240,7 @@ Added in v2.2.0
 ```ts
 export declare function sum<T extends string>(
   tag: T
-): <A>(members: { [K in keyof A]: Guard<A[K] & Record<T, K>> }) => Guard<A[keyof A]>
+): <A>(members: { [K in keyof A]: Guard<A[K]> }) => Guard<A[keyof A]>
 ```
 
 Added in v2.2.0
diff --git a/docs/modules/JsonCodec.ts.md b/docs/modules/JsonCodec.ts.md
new file mode 100644
index 0000000..843f758
--- /dev/null
+++ b/docs/modules/JsonCodec.ts.md
@@ -0,0 +1,285 @@
+---
+title: JsonCodec.ts
+nav_order: 7
+parent: Modules
+---
+
+# JsonCodec overview
+
+Added in v2.2.3
+
+---
+
+<h2 class="text-delta">Table of contents</h2>
+
+- [JsonCodec (interface)](#jsoncodec-interface)
+- [TypeOf (type alias)](#typeof-type-alias)
+- [URI (type alias)](#uri-type-alias)
+- [URI](#uri)
+- [array](#array)
+- [boolean](#boolean)
+- [imap](#imap)
+- [intersection](#intersection)
+- [invariantJsonCodec](#invariantjsoncodec)
+- [lazy](#lazy)
+- [literal](#literal)
+- [make](#make)
+- [nullable](#nullable)
+- [number](#number)
+- [partial](#partial)
+- [record](#record)
+- [refinement](#refinement)
+- [schemableJsonCodec](#schemablejsoncodec)
+- [string](#string)
+- [sum](#sum)
+- [tuple](#tuple)
+- [type](#type)
+- [withExpected](#withexpected)
+
+---
+
+# JsonCodec (interface)
+
+Laws:
+
+1. `pipe(codec.decode(u), E.fold(() => u, codec.encode)) = u` for all `u` in `unknown`
+2. `codec.decode(codec.encode(a)) = E.right(a)` for all `a` in `A`
+
+**Signature**
+
+```ts
+export interface JsonCodec<A> extends C.Codec<JE.Json, A> {}
+```
+
+Added in v2.2.3
+
+# TypeOf (type alias)
+
+**Signature**
+
+```ts
+export type TypeOf<C> = JE.TypeOf<C>
+```
+
+Added in v2.2.2
+
+# URI (type alias)
+
+**Signature**
+
+```ts
+export type URI = typeof URI
+```
+
+Added in v2.2.3
+
+# URI
+
+**Signature**
+
+```ts
+export declare const URI: 'io-ts/JsonCodec'
+```
+
+Added in v2.2.3
+
+# array
+
+**Signature**
+
+```ts
+export declare const array: <A>(items: JsonCodec<A>) => JsonCodec<A[]>
+```
+
+Added in v2.2.3
+
+# boolean
+
+**Signature**
+
+```ts
+export declare const boolean: JsonCodec<boolean>
+```
+
+Added in v2.2.3
+
+# imap
+
+**Signature**
+
+```ts
+export declare const imap: <A, B>(f: (a: A) => B, g: (b: B) => A) => (fa: JsonCodec<A>) => JsonCodec<B>
+```
+
+Added in v2.2.3
+
+# intersection
+
+**Signature**
+
+```ts
+export declare const intersection: <A, B>(left: JsonCodec<A>, right: JsonCodec<B>) => JsonCodec<A & B>
+```
+
+Added in v2.2.3
+
+# invariantJsonCodec
+
+**Signature**
+
+```ts
+export declare const invariantJsonCodec: Invariant1<'io-ts/JsonCodec'>
+```
+
+Added in v2.2.3
+
+# lazy
+
+**Signature**
+
+```ts
+export declare const lazy: <A>(id: string, f: () => JsonCodec<A>) => JsonCodec<A>
+```
+
+Added in v2.2.3
+
+# literal
+
+**Signature**
+
+```ts
+export declare const literal: <A extends readonly (string | number | boolean)[]>(...values: A) => JsonCodec<A[number]>
+```
+
+Added in v2.2.3
+
+# make
+
+**Signature**
+
+```ts
+export declare const make: <A>(decoder: D.Decoder<A>, encoder: JE.JsonEncoder<A>) => JsonCodec<A>
+```
+
+Added in v2.2.3
+
+# nullable
+
+**Signature**
+
+```ts
+export declare const nullable: <A>(or: JsonCodec<A>) => JsonCodec<A>
+```
+
+Added in v2.2.3
+
+# number
+
+**Signature**
+
+```ts
+export declare const number: JsonCodec<number>
+```
+
+Added in v2.2.3
+
+# partial
+
+**Signature**
+
+```ts
+export declare const partial: <A>(properties: { [K in keyof A]: JsonCodec<A[K]> }) => JsonCodec<Partial<A>>
+```
+
+Added in v2.2.3
+
+# record
+
+**Signature**
+
+```ts
+export declare const record: <A>(codomain: JsonCodec<A>) => JsonCodec<Record<string, A>>
+```
+
+Added in v2.2.3
+
+# refinement
+
+**Signature**
+
+```ts
+export declare const refinement: <A, B extends A>(
+  from: JsonCodec<A>,
+  refinement: (a: A) => a is B,
+  expected: string
+) => JsonCodec<B>
+```
+
+Added in v2.2.3
+
+# schemableJsonCodec
+
+**Signature**
+
+```ts
+export declare const schemableJsonCodec: Schemable1<'io-ts/JsonCodec'> & WithRefinement1<'io-ts/JsonCodec'>
+```
+
+Added in v2.2.3
+
+# string
+
+**Signature**
+
+```ts
+export declare const string: JsonCodec<string>
+```
+
+Added in v2.2.3
+
+# sum
+
+**Signature**
+
+```ts
+export declare const sum: <T extends string>(
+  tag: T
+) => <A>(members: { [K in keyof A]: JsonCodec<A[K]> }) => JsonCodec<A[keyof A]>
+```
+
+Added in v2.2.3
+
+# tuple
+
+**Signature**
+
+```ts
+export declare const tuple: <A extends readonly unknown[]>(
+  ...components: { [K in keyof A]: JsonCodec<A[K]> }
+) => JsonCodec<A>
+```
+
+Added in v2.2.3
+
+# type
+
+**Signature**
+
+```ts
+export declare const type: <A>(properties: { [K in keyof A]: JsonCodec<A[K]> }) => JsonCodec<A>
+```
+
+Added in v2.2.3
+
+# withExpected
+
+**Signature**
+
+```ts
+export declare const withExpected: <A>(
+  codec: JsonCodec<A>,
+  expected: (actual: unknown, e: D.DecodeError) => D.DecodeError
+) => JsonCodec<A>
+```
+
+Added in v2.2.3
diff --git a/docs/modules/JsonEncoder.ts.md b/docs/modules/JsonEncoder.ts.md
new file mode 100644
index 0000000..8bb3063
--- /dev/null
+++ b/docs/modules/JsonEncoder.ts.md
@@ -0,0 +1,242 @@
+---
+title: JsonEncoder.ts
+nav_order: 8
+parent: Modules
+---
+
+# JsonEncoder overview
+
+Added in v2.2.3
+
+---
+
+<h2 class="text-delta">Table of contents</h2>
+
+- [JsonArray (interface)](#jsonarray-interface)
+- [JsonEncoder (interface)](#jsonencoder-interface)
+- [Json (type alias)](#json-type-alias)
+- [JsonObject (type alias)](#jsonobject-type-alias)
+- [TypeOf (type alias)](#typeof-type-alias)
+- [URI (type alias)](#uri-type-alias)
+- [URI](#uri)
+- [array](#array)
+- [contramap](#contramap)
+- [contravariantJsonEncoder](#contravariantjsonencoder)
+- [id](#id)
+- [intersection](#intersection)
+- [lazy](#lazy)
+- [nullable](#nullable)
+- [partial](#partial)
+- [record](#record)
+- [schemableJsonEncoder](#schemablejsonencoder)
+- [sum](#sum)
+- [tuple](#tuple)
+- [type](#type)
+
+---
+
+# JsonArray (interface)
+
+**Signature**
+
+```ts
+export interface JsonArray extends Array<Json> {}
+```
+
+Added in v2.2.3
+
+# JsonEncoder (interface)
+
+**Signature**
+
+```ts
+export interface JsonEncoder<A> {
+  readonly encode: (a: A) => Json
+}
+```
+
+Added in v2.2.3
+
+# Json (type alias)
+
+**Signature**
+
+```ts
+export type Json = null | string | number | boolean | JsonObject | JsonArray
+```
+
+Added in v2.2.3
+
+# JsonObject (type alias)
+
+**Signature**
+
+```ts
+export type JsonObject = { [key: string]: Json }
+```
+
+Added in v2.2.3
+
+# TypeOf (type alias)
+
+**Signature**
+
+```ts
+export type TypeOf<E> = E.TypeOf<E>
+```
+
+Added in v2.2.3
+
+# URI (type alias)
+
+**Signature**
+
+```ts
+export type URI = typeof URI
+```
+
+Added in v2.2.3
+
+# URI
+
+**Signature**
+
+```ts
+export declare const URI: 'io-ts/JsonEncoder'
+```
+
+Added in v2.2.3
+
+# array
+
+**Signature**
+
+```ts
+export declare const array: <A>(items: JsonEncoder<A>) => JsonEncoder<A[]>
+```
+
+Added in v2.2.3
+
+# contramap
+
+**Signature**
+
+```ts
+export declare const contramap: <A, B>(f: (b: B) => A) => (fa: JsonEncoder<A>) => JsonEncoder<B>
+```
+
+Added in v2.2.3
+
+# contravariantJsonEncoder
+
+**Signature**
+
+```ts
+export declare const contravariantJsonEncoder: Contravariant1<'io-ts/JsonEncoder'>
+```
+
+Added in v2.2.3
+
+# id
+
+**Signature**
+
+```ts
+export declare const id: JsonEncoder<Json>
+```
+
+Added in v2.2.3
+
+# intersection
+
+**Signature**
+
+```ts
+export declare const intersection: <A, B>(left: JsonEncoder<A>, right: JsonEncoder<B>) => JsonEncoder<A & B>
+```
+
+Added in v2.2.3
+
+# lazy
+
+**Signature**
+
+```ts
+export declare const lazy: <A>(f: () => JsonEncoder<A>) => JsonEncoder<A>
+```
+
+Added in v2.2.3
+
+# nullable
+
+**Signature**
+
+```ts
+export declare const nullable: <A>(or: JsonEncoder<A>) => JsonEncoder<A>
+```
+
+Added in v2.2.3
+
+# partial
+
+**Signature**
+
+```ts
+export declare const partial: <A>(properties: { [K in keyof A]: JsonEncoder<A[K]> }) => JsonEncoder<Partial<A>>
+```
+
+Added in v2.2.3
+
+# record
+
+**Signature**
+
+```ts
+export declare const record: <A>(codomain: JsonEncoder<A>) => JsonEncoder<Record<string, A>>
+```
+
+Added in v2.2.3
+
+# schemableJsonEncoder
+
+**Signature**
+
+```ts
+export declare const schemableJsonEncoder: Schemable1<'io-ts/JsonEncoder'>
+```
+
+Added in v2.2.3
+
+# sum
+
+**Signature**
+
+```ts
+export declare const sum: <T extends string>(
+  tag: T
+) => <A>(members: { [K in keyof A]: JsonEncoder<A[K]> }) => JsonEncoder<A[keyof A]>
+```
+
+Added in v2.2.3
+
+# tuple
+
+**Signature**
+
+```ts
+export declare const tuple: <A extends readonly unknown[]>(
+  ...components: { [K in keyof A]: JsonEncoder<A[K]> }
+) => JsonEncoder<A>
+```
+
+Added in v2.2.3
+
+# type
+
+**Signature**
+
+```ts
+export declare const type: <A>(properties: { [K in keyof A]: JsonEncoder<A[K]> }) => JsonEncoder<A>
+```
+
+Added in v2.2.3
diff --git a/docs/modules/PathReporter.ts.md b/docs/modules/PathReporter.ts.md
index e46f461..80e6dfa 100644
--- a/docs/modules/PathReporter.ts.md
+++ b/docs/modules/PathReporter.ts.md
@@ -1,6 +1,6 @@
 ---
 title: PathReporter.ts
-nav_order: 7
+nav_order: 9
 parent: Modules
 ---
 
diff --git a/docs/modules/Reporter.ts.md b/docs/modules/Reporter.ts.md
index 29f039b..a6d9e4f 100644
--- a/docs/modules/Reporter.ts.md
+++ b/docs/modules/Reporter.ts.md
@@ -1,6 +1,6 @@
 ---
 title: Reporter.ts
-nav_order: 8
+nav_order: 10
 parent: Modules
 ---
 
diff --git a/docs/modules/Schema.ts.md b/docs/modules/Schema.ts.md
index 634ad87..ecec5cb 100644
--- a/docs/modules/Schema.ts.md
+++ b/docs/modules/Schema.ts.md
@@ -1,6 +1,6 @@
 ---
 title: Schema.ts
-nav_order: 9
+nav_order: 11
 parent: Modules
 ---
 
@@ -14,6 +14,7 @@ Added in v2.2.0
 
 - [Schema (interface)](#schema-interface)
 - [TypeOf (type alias)](#typeof-type-alias)
+- [interpreter](#interpreter)
 - [make](#make)
 
 ---
@@ -24,7 +25,7 @@ Added in v2.2.0
 
 ```ts
 export interface Schema<A> {
-  <S extends URIS>(S: Schemable<S>): Kind<S, A>
+  <S>(S: Schemable<S>): HKT<S, A>
 }
 ```
 
@@ -40,12 +41,22 @@ export type TypeOf<S> = S extends Schema<infer A> ? A : never
 
 Added in v2.2.0
 
+# interpreter
+
+**Signature**
+
+```ts
+export declare function interpreter<S extends URIS>(S: Schemable1<S>): <A>(schema: Schema<A>) => Kind<S, A>
+```
+
+Added in v2.2.3
+
 # make
 
 **Signature**
 
 ```ts
-export declare function make<A>(f: Schema<A>): Schema<A>
+export declare function make<A>(schema: Schema<A>): Schema<A>
 ```
 
 Added in v2.2.0
diff --git a/docs/modules/Schemable.ts.md b/docs/modules/Schemable.ts.md
index afd53b2..160adb0 100644
--- a/docs/modules/Schemable.ts.md
+++ b/docs/modules/Schemable.ts.md
@@ -1,6 +1,6 @@
 ---
 title: Schemable.ts
-nav_order: 10
+nav_order: 12
 parent: Modules
 ---
 
@@ -13,7 +13,13 @@ Added in v2.2.0
 <h2 class="text-delta">Table of contents</h2>
 
 - [Schemable (interface)](#schemable-interface)
+- [Schemable1 (interface)](#schemable1-interface)
+- [WithRefinement (interface)](#withrefinement-interface)
+- [WithRefinement1 (interface)](#withrefinement1-interface)
 - [WithUnion (interface)](#withunion-interface)
+- [WithUnion1 (interface)](#withunion1-interface)
+- [WithUnknownContainers (interface)](#withunknowncontainers-interface)
+- [WithUnknownContainers1 (interface)](#withunknowncontainers1-interface)
 - [Literal (type alias)](#literal-type-alias)
 - [memoize](#memoize)
 
@@ -24,14 +30,37 @@ Added in v2.2.0
 **Signature**
 
 ```ts
-export interface Schemable<S extends URIS> {
+export interface Schemable<S> {
+  readonly URI: S
+  readonly literal: <A extends ReadonlyArray<Literal>>(...values: A) => HKT<S, A[number]>
+  readonly string: HKT<S, string>
+  readonly number: HKT<S, number>
+  readonly boolean: HKT<S, boolean>
+  readonly nullable: <A>(or: HKT<S, A>) => HKT<S, null | A>
+  readonly type: <A>(properties: { [K in keyof A]: HKT<S, A[K]> }) => HKT<S, A>
+  readonly partial: <A>(properties: { [K in keyof A]: HKT<S, A[K]> }) => HKT<S, Partial<A>>
+  readonly record: <A>(codomain: HKT<S, A>) => HKT<S, Record<string, A>>
+  readonly array: <A>(items: HKT<S, A>) => HKT<S, Array<A>>
+  readonly tuple: <A extends ReadonlyArray<unknown>>(...components: { [K in keyof A]: HKT<S, A[K]> }) => HKT<S, A>
+  readonly intersection: <A, B>(left: HKT<S, A>, right: HKT<S, B>) => HKT<S, A & B>
+  readonly sum: <T extends string>(tag: T) => <A>(members: { [K in keyof A]: HKT<S, A[K]> }) => HKT<S, A[keyof A]>
+  readonly lazy: <A>(id: string, f: () => HKT<S, A>) => HKT<S, A>
+}
+```
+
+Added in v2.2.3
+
+# Schemable1 (interface)
+
+**Signature**
+
+```ts
+export interface Schemable1<S extends URIS> {
   readonly URI: S
   readonly literal: <A extends ReadonlyArray<Literal>>(...values: A) => Kind<S, A[number]>
   readonly string: Kind<S, string>
   readonly number: Kind<S, number>
   readonly boolean: Kind<S, boolean>
-  readonly UnknownArray: Kind<S, Array<unknown>>
-  readonly UnknownRecord: Kind<S, Record<string, unknown>>
   readonly nullable: <A>(or: Kind<S, A>) => Kind<S, null | A>
   readonly type: <A>(properties: { [K in keyof A]: Kind<S, A[K]> }) => Kind<S, A>
   readonly partial: <A>(properties: { [K in keyof A]: Kind<S, A[K]> }) => Kind<S, Partial<A>>
@@ -39,28 +68,88 @@ export interface Schemable<S extends URIS> {
   readonly array: <A>(items: Kind<S, A>) => Kind<S, Array<A>>
   readonly tuple: <A extends ReadonlyArray<unknown>>(...components: { [K in keyof A]: Kind<S, A[K]> }) => Kind<S, A>
   readonly intersection: <A, B>(left: Kind<S, A>, right: Kind<S, B>) => Kind<S, A & B>
-  readonly sum: <T extends string>(
-    tag: T
-  ) => <A>(members: { [K in keyof A]: Kind<S, A[K] & Record<T, K>> }) => Kind<S, A[keyof A]>
+  readonly sum: <T extends string>(tag: T) => <A>(members: { [K in keyof A]: Kind<S, A[K]> }) => Kind<S, A[keyof A]>
   readonly lazy: <A>(id: string, f: () => Kind<S, A>) => Kind<S, A>
 }
 ```
 
-Added in v2.2.0
+Added in v2.2.3
+
+# WithRefinement (interface)
+
+**Signature**
+
+```ts
+export interface WithRefinement<S> {
+  readonly refinement: <A, B extends A>(from: HKT<S, A>, refinement: (a: A) => a is B, expected: string) => HKT<S, B>
+}
+```
+
+Added in v2.2.3
+
+# WithRefinement1 (interface)
+
+**Signature**
+
+```ts
+export interface WithRefinement1<S extends URIS> {
+  readonly refinement: <A, B extends A>(from: Kind<S, A>, refinement: (a: A) => a is B, expected: string) => Kind<S, B>
+}
+```
+
+Added in v2.2.3
 
 # WithUnion (interface)
 
 **Signature**
 
 ```ts
-export interface WithUnion<S extends URIS> {
+export interface WithUnion<S> {
+  readonly union: <A extends ReadonlyArray<unknown>>(...members: { [K in keyof A]: HKT<S, A[K]> }) => HKT<S, A[number]>
+}
+```
+
+Added in v2.2.3
+
+# WithUnion1 (interface)
+
+**Signature**
+
+```ts
+export interface WithUnion1<S extends URIS> {
   readonly union: <A extends ReadonlyArray<unknown>>(
     ...members: { [K in keyof A]: Kind<S, A[K]> }
   ) => Kind<S, A[number]>
 }
 ```
 
-Added in v2.2.0
+Added in v2.2.3
+
+# WithUnknownContainers (interface)
+
+**Signature**
+
+```ts
+export interface WithUnknownContainers<S> {
+  readonly UnknownArray: HKT<S, Array<unknown>>
+  readonly UnknownRecord: HKT<S, Record<string, unknown>>
+}
+```
+
+Added in v2.2.3
+
+# WithUnknownContainers1 (interface)
+
+**Signature**
+
+```ts
+export interface WithUnknownContainers1<S extends URIS> {
+  readonly UnknownArray: Kind<S, Array<unknown>>
+  readonly UnknownRecord: Kind<S, Record<string, unknown>>
+}
+```
+
+Added in v2.2.3
 
 # Literal (type alias)
 
diff --git a/docs/modules/Tree.ts.md b/docs/modules/Tree.ts.md
index 4b880a0..bc531c7 100644
--- a/docs/modules/Tree.ts.md
+++ b/docs/modules/Tree.ts.md
@@ -1,6 +1,6 @@
 ---
 title: Tree.ts
-nav_order: 11
+nav_order: 13
 parent: Modules
 ---
 
diff --git a/docs/modules/Type.ts.md b/docs/modules/Type.ts.md
new file mode 100644
index 0000000..c6ce566
--- /dev/null
+++ b/docs/modules/Type.ts.md
@@ -0,0 +1,256 @@
+---
+title: Type.ts
+nav_order: 14
+parent: Modules
+---
+
+# Type overview
+
+Added in v2.2.3
+
+---
+
+<h2 class="text-delta">Table of contents</h2>
+
+- [Type (interface)](#type-interface)
+- [URI (type alias)](#uri-type-alias)
+- [URI](#uri)
+- [UnknownArray](#unknownarray)
+- [UnknownRecord](#unknownrecord)
+- [array](#array)
+- [boolean](#boolean)
+- [intersection](#intersection)
+- [lazy](#lazy)
+- [literal](#literal)
+- [nullable](#nullable)
+- [number](#number)
+- [partial](#partial)
+- [record](#record)
+- [refinement](#refinement)
+- [schemableType](#schemabletype)
+- [string](#string)
+- [sum](#sum)
+- [tuple](#tuple)
+- [type](#type)
+- [union](#union)
+
+---
+
+# Type (interface)
+
+**Signature**
+
+```ts
+export interface Type<A> extends t.Type<A, unknown, unknown> {}
+```
+
+Added in v2.2.3
+
+# URI (type alias)
+
+**Signature**
+
+```ts
+export type URI = typeof URI
+```
+
+Added in v2.2.3
+
+# URI
+
+**Signature**
+
+```ts
+export declare const URI: 'io-ts/Type'
+```
+
+Added in v2.2.3
+
+# UnknownArray
+
+**Signature**
+
+```ts
+export declare const UnknownArray: Type<unknown[]>
+```
+
+Added in v2.2.3
+
+# UnknownRecord
+
+**Signature**
+
+```ts
+export declare const UnknownRecord: Type<Record<string, unknown>>
+```
+
+Added in v2.2.3
+
+# array
+
+**Signature**
+
+```ts
+export declare function array<A>(items: Type<A>): Type<Array<A>>
+```
+
+Added in v2.2.3
+
+# boolean
+
+**Signature**
+
+```ts
+export declare const boolean: Type<boolean>
+```
+
+Added in v2.2.3
+
+# intersection
+
+**Signature**
+
+```ts
+export declare function intersection<A, B>(left: Type<A>, right: Type<B>): Type<A & B>
+```
+
+Added in v2.2.3
+
+# lazy
+
+**Signature**
+
+```ts
+export declare function lazy<A>(id: string, f: () => Type<A>): Type<A>
+```
+
+Added in v2.2.3
+
+# literal
+
+**Signature**
+
+```ts
+export declare function literal<A extends ReadonlyArray<Literal>>(...values: A): Type<A[number]>
+```
+
+Added in v2.2.3
+
+# nullable
+
+**Signature**
+
+```ts
+export declare function nullable<A>(or: Type<A>): Type<null | A>
+```
+
+Added in v2.2.3
+
+# number
+
+**Signature**
+
+```ts
+export declare const number: Type<number>
+```
+
+Added in v2.2.3
+
+# partial
+
+**Signature**
+
+```ts
+export declare function partial<A>(properties: { [K in keyof A]: Type<A[K]> }): Type<Partial<A>>
+```
+
+Added in v2.2.3
+
+# record
+
+**Signature**
+
+```ts
+export declare function record<A>(codomain: Type<A>): Type<Record<string, A>>
+```
+
+Added in v2.2.3
+
+# refinement
+
+**Signature**
+
+```ts
+export declare function refinement<A, B extends A>(
+  from: Type<A>,
+  refinement: (a: A) => a is B,
+  expected: string
+): Type<B>
+```
+
+Added in v2.2.3
+
+# schemableType
+
+**Signature**
+
+```ts
+export declare const schemableType: Schemable1<'io-ts/Type'> &
+  WithUnknownContainers1<'io-ts/Type'> &
+  WithUnion1<'io-ts/Type'> &
+  WithRefinement1<'io-ts/Type'>
+```
+
+Added in v2.2.3
+
+# string
+
+**Signature**
+
+```ts
+export declare const string: Type<string>
+```
+
+Added in v2.2.3
+
+# sum
+
+**Signature**
+
+```ts
+export declare function sum<T extends string>(_tag: T): <A>(members: { [K in keyof A]: Type<A[K]> }) => Type<A[keyof A]>
+```
+
+Added in v2.2.3
+
+# tuple
+
+**Signature**
+
+```ts
+export declare function tuple<A extends ReadonlyArray<unknown>>(...components: { [K in keyof A]: Type<A[K]> }): Type<A>
+```
+
+Added in v2.2.3
+
+# type
+
+**Signature**
+
+```ts
+export declare function type<A>(properties: { [K in keyof A]: Type<A[K]> }): Type<A>
+```
+
+Added in v2.2.3
+
+# union
+
+**Signature**
+
+```ts
+export declare function union<A extends ReadonlyArray<unknown>>(
+  ...members: { [K in keyof A]: Type<A[K]> }
+): Type<A[number]>
+```
+
+Added in v2.2.3
diff --git a/docs/modules/index.ts.md b/docs/modules/index.ts.md
index f3f3a66..696af9a 100644
--- a/docs/modules/index.ts.md
+++ b/docs/modules/index.ts.md
@@ -555,12 +555,7 @@ Added in v1.5.3
 
 ```ts
 export interface ReadonlyC<C extends Mixed>
-  extends ReadonlyType<
-    C,
-    { readonly [K in keyof TypeOf<C>]: TypeOf<C>[K] },
-    { readonly [K in keyof OutputOf<C>]: OutputOf<C>[K] },
-    unknown
-  > {}
+  extends ReadonlyType<C, Readonly<TypeOf<C>>, Readonly<OutputOf<C>>, unknown> {}
 ```
 
 Added in v1.5.3
diff --git a/dtslint/ts3.5/Econder.ts b/dtslint/ts3.5/Econder.ts
new file mode 100644
index 0000000..58fc903
--- /dev/null
+++ b/dtslint/ts3.5/Econder.ts
@@ -0,0 +1,104 @@
+import * as E from '../../src/Encoder'
+import { identity } from 'fp-ts/lib/function'
+
+const NumberToString: E.Encoder<string, number> = {
+  encode: String
+}
+
+const BooleanToNumber: E.Encoder<number, boolean> = {
+  encode: (b) => (b ? 1 : 0)
+}
+
+export const Person = E.type({ name: E.categoryEncoder.id<string>(), age: NumberToString })
+
+//
+// TypeOf
+//
+export type Person = E.TypeOf<typeof Person> // $ExpectType { name: string; age: number; }
+
+//
+// OutputOf
+//
+export type PersonOut = E.OutputOf<typeof Person> // $ExpectType { name: string; age: string; }
+
+//
+// nullable
+//
+E.nullable(NumberToString) // $ExpectType Encoder<string | null, number | null>
+
+//
+// type
+//
+E.type({ a: NumberToString }) // $ExpectType Encoder<{ a: string; }, { a: number; }>
+
+//
+// partial
+//
+E.partial({ a: NumberToString }) // $ExpectType Encoder<Partial<{ a: string; }>, Partial<{ a: number; }>>
+
+//
+// record
+//
+E.record(NumberToString) // $ExpectType Encoder<Record<string, string>, Record<string, number>>
+
+//
+// array
+//
+E.array(NumberToString) // $ExpectType Encoder<string[], number[]>
+
+//
+// tuple
+//
+E.tuple() // $ExpectType Encoder<[], []>
+E.tuple(NumberToString) // $ExpectType Encoder<[string], [number]>
+E.tuple(NumberToString, BooleanToNumber) // $ExpectType Encoder<[string, number], [number, boolean]>
+
+//
+// intersection
+//
+E.intersection(E.type({ a: NumberToString }), E.type({ b: BooleanToNumber })) // $ExpectType Encoder<{ a: string; } & { b: number; }, { a: number; } & { b: boolean; }>
+
+//
+// sum
+//
+const S1 = E.type({ _tag: E.id<'A'>(), a: NumberToString })
+const S2 = E.type({ _tag: E.id<'B'>(), b: BooleanToNumber })
+const sum = E.sum('_tag')
+
+// $ExpectType Encoder<{ _tag: "A"; a: string; } | { _tag: "B"; b: number; }, { _tag: "A"; a: number; } | { _tag: "B"; b: boolean; }>
+sum({ A: S1, B: S2 })
+
+const S3 = E.type({ _tag: E.id<'C'>(), c: E.id<string>() })
+
+//
+// lazy
+//
+interface A {
+  a: number
+  bs: Array<B>
+}
+interface AOut {
+  a: string
+  bs: Array<BOut>
+}
+interface B {
+  b: boolean
+  as: Array<A>
+}
+interface BOut {
+  b: number
+  as: Array<AOut>
+}
+const A: E.Encoder<AOut, A> = E.lazy(() =>
+  E.type({
+    a: NumberToString,
+    bs: E.array(B)
+  })
+)
+
+const B: E.Encoder<BOut, B> = E.lazy(() =>
+  E.type({
+    b: BooleanToNumber,
+    as: E.array(A)
+  })
+)
diff --git a/dtslint/ts3.5/Schemable.ts b/dtslint/ts3.5/Schema.ts
similarity index 69%
rename from dtslint/ts3.5/Schemable.ts
rename to dtslint/ts3.5/Schema.ts
index d7578e4..cc61193 100644
--- a/dtslint/ts3.5/Schemable.ts
+++ b/dtslint/ts3.5/Schema.ts
@@ -1,5 +1,22 @@
 import * as D from '../../src/Decoder'
-import { make, Schema } from '../../src/Schema'
+import { Schemable, WithUnknownContainers, memoize, WithRefinement, WithUnion } from '../../src/Schemable'
+import { HKT } from 'fp-ts/lib/HKT'
+
+interface Schema<A> {
+  <S>(S: Schemable<S> & WithUnknownContainers<S> & WithRefinement<S> & WithUnion<S>): HKT<S, A>
+}
+
+export type TypeOf<S> = S extends Schema<infer A> ? A : never
+
+function make<A>(f: Schema<A>): Schema<A> {
+  return memoize(f)
+}
+
+//
+// TypeOf
+//
+export const Person = make((S) => S.type({ name: S.string, age: S.number }))
+export type Person = TypeOf<typeof Person> // $ExpectType { name: string; age: number; }
 
 //
 // literal
@@ -21,16 +38,6 @@ make((S) => S.number) // $ExpectType Schema<number>
 //
 make((S) => S.boolean) // $ExpectType Schema<boolean>
 
-//
-// UnknownArray
-//
-make((S) => S.UnknownArray) // $ExpectType Schema<unknown[]>
-
-//
-// UnknownRecord
-//
-make((S) => S.UnknownRecord) // $ExpectType Schema<Record<string, unknown>>
-
 //
 // nullable
 //
@@ -106,8 +113,27 @@ const B: Schema<B> = make((S) =>
   )
 )
 
+//
+// UnknownArray
+//
+make((S) => S.UnknownArray) // $ExpectType Schema<unknown[]>
+
+//
+// UnknownRecord
+//
+make((S) => S.UnknownRecord) // $ExpectType Schema<Record<string, unknown>>
+
+//
+// refinement
+//
+interface PositiveBrand {
+  readonly Positive: unique symbol
+}
+type Positive = number & PositiveBrand
+make((S) => S.refinement(S.number, (n): n is Positive => n > 0, 'Positive'))
+
 //
 // union
 //
-D.union(D.string) // $ExpectType Decoder<string>
-D.union(D.string, D.number) // $ExpectType Decoder<string | number>
+make((S) => S.union(S.string)) // $ExpectType Schema<string>
+make((S) => S.union(S.string, S.number)) // $ExpectType Schema<string | number>
diff --git a/dtslint/ts3.5/index.ts b/dtslint/ts3.5/index.ts
index 69c1cbc..8faac7e 100644
--- a/dtslint/ts3.5/index.ts
+++ b/dtslint/ts3.5/index.ts
@@ -18,7 +18,7 @@ export const NumberFromString = new t.Type<number, string, unknown>(
   'NumberFromString',
   t.number.is,
   (u, c) =>
-    either.chain(t.string.validate(u, c), s => {
+    either.chain(t.string.validate(u, c), (s) => {
       const n = parseFloat(s)
       return isNaN(n) ? t.failure(s, c) : t.success(n)
     }),
@@ -63,11 +63,11 @@ type Keyof1OutputTest = t.OutputOf<typeof Keyof1> // $ExpectType "a" | "b"
 // refinement
 //
 
-const Refinement1 = t.refinement(t.number, n => n % 2 === 0) // $ExpectType RefinementC<NumberC>
+const Refinement1 = t.refinement(t.number, (n) => n % 2 === 0) // $ExpectType RefinementC<NumberC>
 type Refinement1TypeTest = t.TypeOf<typeof Refinement1> // $ExpectType number
 type Refinement1OutputTest = t.OutputOf<typeof Refinement1> // $ExpectType number
 
-const Refinement2 = t.refinement(NumberFromString, n => n % 2 === 0) // $ExpectType RefinementC<Type<number, string, unknown>>
+const Refinement2 = t.refinement(NumberFromString, (n) => n % 2 === 0) // $ExpectType RefinementC<Type<number, string, unknown>>
 type Refinement2TypeTest = t.TypeOf<typeof Refinement2> // $ExpectType number
 type Refinement2OutputTest = t.OutputOf<typeof Refinement2> // $ExpectType string
 
@@ -266,6 +266,10 @@ const Readonly2 = t.readonly(t.type({ a: NumberFromString })) // $ExpectType Rea
 type Readonly2TypeTest = Equals<t.TypeOf<typeof Readonly2>, { readonly a: number }> // $ExpectType "T"
 type Readonly2OutputTest = Equals<t.OutputOf<typeof Readonly2>, { readonly a: string }> // $ExpectType "T"
 
+const Readonly3 = t.readonly(t.tuple([t.string, NumberFromString]))
+type Readonly3TypeTest = Equals<t.TypeOf<typeof Readonly3>, Readonly<[string, number]>> // $ExpectType "T"
+type Readonly3OutputTest = Equals<t.OutputOf<typeof Readonly3>, Readonly<[string, string]>> // $ExpectType "T"
+
 //
 // readonlyArray
 //
@@ -316,14 +320,14 @@ interface TaggedUnion2_B {
   a: TaggedUnion2_A | undefined
 }
 
-const TaggedUnion2_A: t.RecursiveType<any, TaggedUnion2_A> = t.recursion<TaggedUnion2_A>('TaggedUnion2_A', _ =>
+const TaggedUnion2_A: t.RecursiveType<any, TaggedUnion2_A> = t.recursion<TaggedUnion2_A>('TaggedUnion2_A', (_) =>
   t.type({
     type: t.literal('a'),
     b: t.union([TaggedUnion2_B, t.undefined])
   })
 )
 
-const TaggedUnion2_B: t.RecursiveType<any, TaggedUnion2_B> = t.recursion<TaggedUnion2_B>('TaggedUnion2_B', _ =>
+const TaggedUnion2_B: t.RecursiveType<any, TaggedUnion2_B> = t.recursion<TaggedUnion2_B>('TaggedUnion2_B', (_) =>
   t.type({
     type: t.literal('b'),
     a: t.union([TaggedUnion2_A, t.undefined])
@@ -432,7 +436,7 @@ function f(generable: Generable): string {
   switch (generable._tag) {
     case 'InterfaceType':
       return Object.keys(generable.props)
-        .map(k => f(generable.props[k]))
+        .map((k) => f(generable.props[k]))
         .join('/')
     case 'StringType':
       return 'StringC'
@@ -497,7 +501,7 @@ interface Rec {
   b: Rec | undefined
 }
 
-const Rec = t.recursion<Rec, Rec, t.mixed, GenerableInterface>('T', self =>
+const Rec = t.recursion<Rec, Rec, t.mixed, GenerableInterface>('T', (self) =>
   t.type({
     a: t.number,
     b: t.union([self, t.undefined])
diff --git a/Type.md b/index.md
similarity index 100%
rename from Type.md
rename to index.md
diff --git a/package-lock.json b/package-lock.json
index 30d56f4..7e24977 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,6 +1,6 @@
 {
   "name": "io-ts",
-  "version": "2.2.2",
+  "version": "2.2.3",
   "lockfileVersion": 1,
   "requires": true,
   "dependencies": {
@@ -2147,7 +2147,7 @@
       }
     },
     "dtslint": {
-      "version": "github:gcanti/dtslint#e2f11f84997dbbd4aff3f867cda0a5a400931c87",
+      "version": "github:gcanti/dtslint#de9181e0f1069b16ea15da87f74d88ed9bc221b7",
       "from": "github:gcanti/dtslint",
       "dev": true,
       "requires": {
@@ -2155,7 +2155,7 @@
         "parsimmon": "^1.12.0",
         "strip-json-comments": "^2.0.1",
         "tslint": "^5.12.0",
-        "typescript": "^3.9.0-dev.20200406"
+        "typescript": "^4.0.0-dev.20200514"
       },
       "dependencies": {
         "fs-extra": {
@@ -2191,9 +2191,9 @@
           }
         },
         "typescript": {
-          "version": "3.9.0-dev.20200406",
-          "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.9.0-dev.20200406.tgz",
-          "integrity": "sha512-oDw6MZnp1bMrifgsisvC0Ea3Op8Uyajq88/1EBCi5J8lxuPLDgVTKm3uqArGpcwiX6QphMIWA124ZH2OQx+dzw==",
+          "version": "4.0.0-dev.20200514",
+          "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.0.0-dev.20200514.tgz",
+          "integrity": "sha512-o++Z0PwCL2iqEwTnoUVfDIAMo9xS+dvxm/6sl6n2VfxGGmVyaC9F6Naaylh+VZ5qG6Actdso0kJnzDxXVwY5fw==",
           "dev": true
         }
       }
@@ -7269,9 +7269,9 @@
       }
     },
     "typescript": {
-      "version": "3.8.3",
-      "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.8.3.tgz",
-      "integrity": "sha512-MYlEfn5VrLNsgudQTVJeNaQFUAI7DkhnOjdpAp4T+ku1TfQClewlbSuTVHiA+8skNBgaf02TL/kLOvig4y3G8w==",
+      "version": "3.9.3",
+      "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.9.3.tgz",
+      "integrity": "sha512-D/wqnB2xzNFIcoBG9FG8cXRDjiqSTbG2wd8DMZeQyJlP1vfTkIxH4GKveWaEBYySKIg+USu+E+EDIR47SqnaMQ==",
       "dev": true
     },
     "unc-path-regex": {
diff --git a/package.json b/package.json
index d8cc245..040aec1 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "io-ts",
-  "version": "2.2.2",
+  "version": "2.2.3",
   "description": "TypeScript runtime type system for IO decoding/encoding",
   "files": [
     "lib",
@@ -59,7 +59,7 @@
     "ts-node": "8.8.2",
     "tslint": "6.1.1",
     "tslint-config-standard": "9.0.0",
-    "typescript": "3.8.3"
+    "typescript": "^3.9.3"
   },
   "tags": [
     "typescript",
diff --git a/src/Codec.ts b/src/Codec.ts
index 9e52a0d..330dfe9 100644
--- a/src/Codec.ts
+++ b/src/Codec.ts
@@ -1,10 +1,11 @@
 /**
- * @since 2.2.0
+ * @since 2.2.3
  */
-import { Invariant1 } from 'fp-ts/lib/Invariant'
+import { Invariant2 } from 'fp-ts/lib/Invariant'
 import * as D from './Decoder'
 import * as E from './Encoder'
-import { Schemable, Literal } from './Schemable'
+import { Literal } from './Schemable'
+import { identity } from 'fp-ts/lib/function'
 
 // -------------------------------------------------------------------------------------
 // model
@@ -16,23 +17,28 @@ import { Schemable, Literal } from './Schemable'
  * 1. `pipe(codec.decode(u), E.fold(() => u, codec.encode)) = u` for all `u` in `unknown`
  * 2. `codec.decode(codec.encode(a)) = E.right(a)` for all `a` in `A`
  *
- * @since 2.2.0
+ * @since 2.2.3
  */
-export interface Codec<A> extends D.Decoder<A>, E.Encoder<A> {}
+export interface Codec<O, A> extends D.Decoder<A>, E.Encoder<O, A> {}
 
 /**
- * @since 2.2.2
+ * @since 2.2.3
  */
-export type TypeOf<C> = C extends Codec<infer A> ? A : never
+export type TypeOf<C> = E.TypeOf<C>
+
+/**
+ * @since 2.2.3
+ */
+export type OutputOf<C> = E.OutputOf<C>
 
 // -------------------------------------------------------------------------------------
 // constructors
 // -------------------------------------------------------------------------------------
 
 /**
- * @since 2.2.0
+ * @since 2.2.3
  */
-export function make<A>(decoder: D.Decoder<A>, encoder: E.Encoder<A>): Codec<A> {
+export function make<O, A>(decoder: D.Decoder<A>, encoder: E.Encoder<O, A>): Codec<O, A> {
   return {
     decode: decoder.decode,
     encode: encoder.encode
@@ -40,10 +46,20 @@ export function make<A>(decoder: D.Decoder<A>, encoder: E.Encoder<A>): Codec<A>
 }
 
 /**
- * @since 2.2.0
+ * @since 2.2.3
  */
-export function literal<A extends ReadonlyArray<Literal>>(...values: A): Codec<A[number]> {
-  return make(D.decoder.literal(...values), E.encoder.literal(...values))
+export function fromDecoder<A>(decoder: D.Decoder<A>): Codec<A, A> {
+  return {
+    decode: decoder.decode,
+    encode: identity
+  }
+}
+
+/**
+ * @since 2.2.3
+ */
+export function literal<A extends ReadonlyArray<Literal>>(...values: A): Codec<A[number], A[number]> {
+  return fromDecoder(D.literal(...values))
 }
 
 // -------------------------------------------------------------------------------------
@@ -51,157 +67,168 @@ export function literal<A extends ReadonlyArray<Literal>>(...values: A): Codec<A
 // -------------------------------------------------------------------------------------
 
 /**
- * @since 2.2.0
+ * @since 2.2.3
  */
-export const string: Codec<string> = make(D.decoder.string, E.encoder.string)
+export const string: Codec<string, string> = fromDecoder(D.string)
 
 /**
- * @since 2.2.0
+ * @since 2.2.3
  */
-export const number: Codec<number> = make(D.decoder.number, E.encoder.number)
+export const number: Codec<number, number> = fromDecoder(D.number)
 
 /**
- * @since 2.2.0
+ * @since 2.2.3
  */
-export const boolean: Codec<boolean> = make(D.decoder.boolean, E.encoder.boolean)
+export const boolean: Codec<boolean, boolean> = fromDecoder(D.boolean)
 
 /**
- * @since 2.2.0
+ * @since 2.2.3
  */
-export const UnknownArray: Codec<Array<unknown>> = make(D.decoder.UnknownArray, E.encoder.UnknownArray)
+export const UnknownArray: Codec<Array<unknown>, Array<unknown>> = fromDecoder(D.UnknownArray)
 
 /**
- * @since 2.2.0
+ * @since 2.2.3
  */
-export const UnknownRecord: Codec<Record<string, unknown>> = make(D.decoder.UnknownRecord, E.encoder.UnknownRecord)
+export const UnknownRecord: Codec<Record<string, unknown>, Record<string, unknown>> = fromDecoder(D.UnknownRecord)
 
 // -------------------------------------------------------------------------------------
 // combinators
 // -------------------------------------------------------------------------------------
 
 /**
- * @since 2.2.0
+ * @since 2.2.3
  */
-export function withExpected<A>(
-  codec: Codec<A>,
+export function withExpected<O, A>(
+  codec: Codec<O, A>,
   expected: (actual: unknown, e: D.DecodeError) => D.DecodeError
-): Codec<A> {
+): Codec<O, A> {
   return make(D.withExpected(codec, expected), codec)
 }
 
 /**
- * @since 2.2.0
+ * @since 2.2.3
  */
-export function refinement<A, B extends A>(from: Codec<A>, refinement: (a: A) => a is B, expected: string): Codec<B> {
+export function refinement<O, A, B extends A>(
+  from: Codec<O, A>,
+  refinement: (a: A) => a is B,
+  expected: string
+): Codec<O, B> {
   return make(D.refinement(from, refinement, expected), from)
 }
 
 /**
- * @since 2.2.0
+ * @since 2.2.3
  */
-export function nullable<A>(or: Codec<A>): Codec<null | A> {
-  return make(D.decoder.nullable(or), E.encoder.nullable(or))
+export function nullable<O, A>(or: Codec<O, A>): Codec<null | O, null | A> {
+  return make(D.nullable(or), E.nullable(or))
 }
 
 /**
- * @since 2.2.0
+ * @since 2.2.3
  */
-export function type<A>(properties: { [K in keyof A]: Codec<A[K]> }): Codec<A> {
-  return make(D.decoder.type(properties), E.encoder.type(properties))
+export function type<P extends Record<string, Codec<any, any>>>(
+  properties: P
+): Codec<{ [K in keyof P]: OutputOf<P[K]> }, { [K in keyof P]: TypeOf<P[K]> }> {
+  const decoder: D.Decoder<{ [K in keyof P]: TypeOf<P[K]> }> = D.type(properties) as any
+  return make(decoder, E.type(properties))
 }
 
 /**
- * @since 2.2.0
+ * @since 2.2.3
  */
-export function partial<A>(properties: { [K in keyof A]: Codec<A[K]> }): Codec<Partial<A>> {
-  return make(D.decoder.partial(properties), E.encoder.partial(properties))
+export function partial<P extends Record<string, Codec<any, any>>>(
+  properties: P
+): Codec<Partial<{ [K in keyof P]: OutputOf<P[K]> }>, Partial<{ [K in keyof P]: TypeOf<P[K]> }>> {
+  return make(D.partial(properties), E.partial(properties))
 }
 
 /**
- * @since 2.2.0
+ * @since 2.2.3
  */
-export function record<A>(codomain: Codec<A>): Codec<Record<string, A>> {
-  return make(D.decoder.record(codomain), E.encoder.record(codomain))
+export function record<O, A>(codomain: Codec<O, A>): Codec<Record<string, O>, Record<string, A>> {
+  return make(D.record(codomain), E.record(codomain))
 }
 
 /**
- * @since 2.2.0
+ * @since 2.2.3
  */
-export function array<A>(items: Codec<A>): Codec<Array<A>> {
-  return make(D.decoder.array(items), E.encoder.array(items))
+export function array<O, A>(items: Codec<O, A>): Codec<Array<O>, Array<A>> {
+  return make(D.array(items), E.array(items))
 }
 
 /**
- * @since 2.2.0
+ * @since 2.2.3
  */
-export function tuple<A extends ReadonlyArray<unknown>>(...components: { [K in keyof A]: Codec<A[K]> }): Codec<A> {
-  return make(D.decoder.tuple<A>(...(components as any)), E.encoder.tuple<A>(...(components as any)))
+export function tuple<C extends ReadonlyArray<Codec<any, any>>>(
+  ...components: C
+): Codec<{ [K in keyof C]: OutputOf<C[K]> }, { [K in keyof C]: TypeOf<C[K]> }> {
+  const decoder: D.Decoder<{ [K in keyof C]: TypeOf<C[K]> }> = D.tuple(...components) as any
+  const encoder = E.tuple(...components)
+  return make(decoder, encoder)
 }
 
 /**
- * @since 2.2.0
+ * @since 2.2.3
  */
-export function intersection<A, B>(left: Codec<A>, right: Codec<B>): Codec<A & B> {
-  return make(D.decoder.intersection(left, right), E.encoder.intersection(left, right))
+export function intersection<O, A, P, B>(left: Codec<O, A>, right: Codec<P, B>): Codec<O & P, A & B> {
+  return make(D.intersection(left, right), E.intersection(left, right))
 }
 
 /**
- * @since 2.2.0
+ * @since 2.2.3
  */
 export function sum<T extends string>(
   tag: T
-): <A>(members: { [K in keyof A]: Codec<A[K] & Record<T, K>> }) => Codec<A[keyof A]> {
-  const sumD = D.decoder.sum(tag)
-  const sumE = E.encoder.sum(tag)
+): <M extends Record<string, Codec<any, any>>>(members: M) => Codec<OutputOf<M[keyof M]>, TypeOf<M[keyof M]>> {
+  const sumD = D.sum(tag)
+  const sumE = E.sum(tag)
   return (members) => make(sumD(members), sumE(members))
 }
 
 /**
- * @since 2.2.0
+ * @since 2.2.3
  */
-export function lazy<A>(id: string, f: () => Codec<A>): Codec<A> {
-  return make(D.decoder.lazy(id, f), E.encoder.lazy(id, f))
+export function lazy<O, A>(id: string, f: () => Codec<O, A>): Codec<O, A> {
+  return make(D.lazy(id, f), E.lazy(f))
 }
 
+// -------------------------------------------------------------------------------------
+// pipeables
+// -------------------------------------------------------------------------------------
+
+/**
+ * @since 2.2.3
+ */
+export const imap: <E, A, B>(f: (a: A) => B, g: (b: B) => A) => (fa: Codec<E, A>) => Codec<E, B> = (f, g) => (fa) =>
+  imap_(fa, f, g)
+
+const imap_: <E, A, B>(fa: Codec<E, A>, f: (a: A) => B, g: (b: B) => A) => Codec<E, B> = (fa, f, g) =>
+  make(D.functorDecoder.map(fa, f), E.contravariantEncoder.contramap(fa, g))
+
 // -------------------------------------------------------------------------------------
 // instances
 // -------------------------------------------------------------------------------------
 
 /**
- * @since 2.2.0
+ * @since 2.2.3
  */
-export const URI = 'Codec'
+export const URI = 'io-ts/Codec'
 
 /**
- * @since 2.2.0
+ * @since 2.2.3
  */
 export type URI = typeof URI
 
 declare module 'fp-ts/lib/HKT' {
-  interface URItoKind<A> {
-    readonly Codec: Codec<A>
+  interface URItoKind2<E, A> {
+    readonly [URI]: Codec<E, A>
   }
 }
 
 /**
- * @since 2.2.0
+ * @since 2.2.3
  */
-export const codec: Invariant1<URI> & Schemable<URI> = {
+export const invariantCodec: Invariant2<URI> = {
   URI,
-  imap: (fa, f, g) => make(D.decoder.map(fa, f), E.encoder.contramap(fa, g)),
-  literal,
-  string,
-  number,
-  boolean,
-  UnknownArray,
-  UnknownRecord,
-  nullable,
-  type,
-  partial,
-  record,
-  array,
-  tuple: tuple as Schemable<URI>['tuple'],
-  intersection,
-  sum,
-  lazy
+  imap: imap_
 }
diff --git a/src/Decoder.ts b/src/Decoder.ts
index 10858ea..a2da872 100644
--- a/src/Decoder.ts
+++ b/src/Decoder.ts
@@ -1,14 +1,14 @@
 /**
  * @since 2.2.0
  */
-import { Alternative1 } from 'fp-ts/lib/Alternative'
-import { Applicative1 } from 'fp-ts/lib/Applicative'
-import { Either, either, isLeft, isRight, left, mapLeft, right } from 'fp-ts/lib/Either'
+import { Either, isLeft, isRight, left, mapLeft, right } from 'fp-ts/lib/Either'
 import { NonEmptyArray } from 'fp-ts/lib/NonEmptyArray'
-import { pipe, pipeable } from 'fp-ts/lib/pipeable'
-import { Tree, Forest } from 'fp-ts/lib/Tree'
+import { pipe } from 'fp-ts/lib/pipeable'
+import { Forest, Tree } from 'fp-ts/lib/Tree'
 import * as G from './Guard'
-import { Schemable, memoize, WithUnion, Literal } from './Schemable'
+import { Literal, memoize, Schemable1, WithRefinement1, WithUnion1, WithUnknownContainers1 } from './Schemable'
+import { Functor1 } from 'fp-ts/lib/Functor'
+import { Alt1 } from 'fp-ts/lib/Alt'
 
 // -------------------------------------------------------------------------------------
 // model
@@ -32,7 +32,7 @@ export interface Decoder<A> {
 export type TypeOf<D> = D extends Decoder<infer A> ? A : never
 
 // -------------------------------------------------------------------------------------
-// constructors
+// DecodeError
 // -------------------------------------------------------------------------------------
 
 const empty: Array<never> = []
@@ -68,6 +68,19 @@ export function isNotEmpty<A>(as: ReadonlyArray<A>): as is NonEmptyArray<A> {
   return as.length > 0
 }
 
+// -------------------------------------------------------------------------------------
+// constructors
+// -------------------------------------------------------------------------------------
+
+/**
+ * @since 2.2.3
+ */
+export function of<A>(a: A): Decoder<A> {
+  return {
+    decode: () => success(a)
+  }
+}
+
 /**
  * @since 2.2.0
  */
@@ -85,7 +98,7 @@ export function literal<A extends ReadonlyArray<Literal>>(...values: A): Decoder
     return never
   }
   const expected = values.map((value) => JSON.stringify(value)).join(' | ')
-  return fromGuard(G.guard.literal(...values), expected)
+  return fromGuard(G.schemableGuard.literal(...values), expected)
 }
 
 // -------------------------------------------------------------------------------------
@@ -386,9 +399,7 @@ export function lazy<A>(id: string, f: () => Decoder<A>): Decoder<A> {
 /**
  * @since 2.2.0
  */
-export function sum<T extends string>(
-  tag: T
-): <A>(members: { [K in keyof A]: Decoder<A[K] & Record<T, K>> }) => Decoder<A[keyof A]> {
+export function sum<T extends string>(tag: T): <A>(members: { [K in keyof A]: Decoder<A[K]> }) => Decoder<A[keyof A]> {
   return (members) => {
     const keys = Object.keys(members)
     if (keys.length === 0) {
@@ -446,6 +457,34 @@ export function union<A extends ReadonlyArray<unknown>>(
   }
 }
 
+// -------------------------------------------------------------------------------------
+// pipeables
+// -------------------------------------------------------------------------------------
+
+/**
+ * @since 2.2.0
+ */
+export const map: <A, B>(f: (a: A) => B) => (fa: Decoder<A>) => Decoder<B> = (f) => (fa) => map_(fa, f)
+
+const map_: <A, B>(fa: Decoder<A>, f: (a: A) => B) => Decoder<B> = (fa, f) => ({
+  decode: (u) => {
+    const e = fa.decode(u)
+    return isLeft(e) ? e : right(f(e.right))
+  }
+})
+
+/**
+ * @since 2.2.0
+ */
+export const alt: <A>(that: () => Decoder<A>) => (fa: Decoder<A>) => Decoder<A> = (that) => (fa) => alt_(fa, that)
+
+const alt_: <A>(fx: Decoder<A>, fy: () => Decoder<A>) => Decoder<A> = (fx, fy) => ({
+  decode: (u) => {
+    const e = fx.decode(u)
+    return isLeft(e) ? fy().decode(u) : e
+  }
+})
+
 // -------------------------------------------------------------------------------------
 // instances
 // -------------------------------------------------------------------------------------
@@ -453,7 +492,7 @@ export function union<A extends ReadonlyArray<unknown>>(
 /**
  * @since 2.2.0
  */
-export const URI = 'Decoder'
+export const URI = 'io-ts/Decoder'
 
 /**
  * @since 2.2.0
@@ -462,67 +501,50 @@ export type URI = typeof URI
 
 declare module 'fp-ts/lib/HKT' {
   interface URItoKind<A> {
-    readonly Decoder: Decoder<A>
+    readonly [URI]: Decoder<A>
   }
 }
 
 /**
- * @since 2.2.0
+ * @since 2.2.3
  */
-export const decoder: Applicative1<URI> & Alternative1<URI> & Schemable<URI> & WithUnion<URI> = {
+export const functorDecoder: Functor1<URI> = {
+  URI,
+  map: map_
+}
+
+/**
+ * @since 2.2.3
+ */
+export const altDecoder: Alt1<URI> = {
+  URI,
+  map: map_,
+  alt: alt_
+}
+
+/**
+ * @since 2.2.3
+ */
+export const schemableDecoder: Schemable1<URI> &
+  WithUnknownContainers1<URI> &
+  WithUnion1<URI> &
+  WithRefinement1<URI> = {
   URI,
-  map: (fa, f) => ({
-    decode: (u) => either.map(fa.decode(u), f)
-  }),
-  of: (a) => ({
-    decode: () => success(a)
-  }),
-  ap: (fab, fa) => ({
-    decode: (u) => either.ap(fab.decode(u), fa.decode(u))
-  }),
-  alt: (fx, fy) => ({
-    decode: (u) => either.alt(fx.decode(u), () => fy().decode(u))
-  }),
-  zero: () => never,
   literal,
   string,
   number,
   boolean,
-  UnknownArray,
-  UnknownRecord,
   nullable,
   type,
   partial,
   record,
   array,
-  tuple: tuple as Schemable<URI>['tuple'],
+  tuple: tuple as Schemable1<URI>['tuple'],
   intersection,
   sum,
   lazy,
-  union
-}
-
-const { alt, ap, apFirst, apSecond, map } = pipeable(decoder)
-
-export {
-  /**
-   * @since 2.2.0
-   */
-  alt,
-  /**
-   * @since 2.2.0
-   */
-  ap,
-  /**
-   * @since 2.2.0
-   */
-  apFirst,
-  /**
-   * @since 2.2.0
-   */
-  apSecond,
-  /**
-   * @since 2.2.0
-   */
-  map
+  UnknownArray,
+  UnknownRecord,
+  union,
+  refinement: refinement as WithRefinement1<URI>['refinement']
 }
diff --git a/src/Encoder.ts b/src/Encoder.ts
index 33581b4..794b18b 100644
--- a/src/Encoder.ts
+++ b/src/Encoder.ts
@@ -1,37 +1,44 @@
 /**
- * @since 2.2.0
+ * @since 2.2.3
  */
-import { Contravariant1 } from 'fp-ts/lib/Contravariant'
-import { identity } from 'fp-ts/lib/function'
-import { pipeable } from 'fp-ts/lib/pipeable'
-import { Schemable, memoize } from './Schemable'
+import { Contravariant2 } from 'fp-ts/lib/Contravariant'
+import { Category2 } from 'fp-ts/lib/Category'
 import { intersect } from './Decoder'
+import { memoize } from './Schemable'
+import { identity } from 'fp-ts/lib/function'
 
 // -------------------------------------------------------------------------------------
 // model
 // -------------------------------------------------------------------------------------
 
 /**
- * @since 2.2.0
+ * @since 2.2.3
  */
-export interface Encoder<A> {
-  readonly encode: (a: A) => unknown
+export interface Encoder<O, A> {
+  readonly encode: (a: A) => O
 }
 
 /**
- * @since 2.2.2
+ * @since 2.2.3
+ */
+export type TypeOf<E> = E extends Encoder<any, infer A> ? A : never
+
+/**
+ * @since 2.2.3
  */
-export type TypeOf<E> = E extends Encoder<infer A> ? A : never
+export type OutputOf<E> = E extends Encoder<infer O, any> ? O : never
 
 // -------------------------------------------------------------------------------------
-// primitives
+// constructors
 // -------------------------------------------------------------------------------------
 
 /**
- * @since 2.2.0
+ * @since 2.2.3
  */
-export const id: Encoder<unknown> = {
-  encode: identity
+export function id<A>(): Encoder<A, A> {
+  return {
+    encode: identity
+  }
 }
 
 // -------------------------------------------------------------------------------------
@@ -39,21 +46,23 @@ export const id: Encoder<unknown> = {
 // -------------------------------------------------------------------------------------
 
 /**
- * @since 2.2.0
+ * @since 2.2.3
  */
-export function nullable<A>(or: Encoder<A>): Encoder<null | A> {
+export function nullable<O, A>(or: Encoder<O, A>): Encoder<null | O, null | A> {
   return {
-    encode: (a) => (a === null ? a : or.encode(a))
+    encode: (a) => (a === null ? null : or.encode(a))
   }
 }
 
 /**
- * @since 2.2.0
+ * @since 2.2.3
  */
-export function type<A>(properties: { [K in keyof A]: Encoder<A[K]> }): Encoder<A> {
+export function type<P extends Record<string, Encoder<any, any>>>(
+  properties: P
+): Encoder<{ [K in keyof P]: OutputOf<P[K]> }, { [K in keyof P]: TypeOf<P[K]> }> {
   return {
     encode: (a) => {
-      const o: Record<string, unknown> = {}
+      const o: Record<keyof P, any> = {} as any
       for (const k in properties) {
         o[k] = properties[k].encode(a[k])
       }
@@ -63,18 +72,20 @@ export function type<A>(properties: { [K in keyof A]: Encoder<A[K]> }): Encoder<
 }
 
 /**
- * @since 2.2.0
+ * @since 2.2.3
  */
-export function partial<A>(properties: { [K in keyof A]: Encoder<A[K]> }): Encoder<Partial<A>> {
+export function partial<P extends Record<string, Encoder<any, any>>>(
+  properties: P
+): Encoder<Partial<{ [K in keyof P]: OutputOf<P[K]> }>, Partial<{ [K in keyof P]: TypeOf<P[K]> }>> {
   return {
     encode: (a) => {
-      const o: Record<string, unknown> = {}
+      const o: Record<keyof P, any> = {} as any
       for (const k in properties) {
-        const v: A[Extract<keyof A, string>] | undefined = a[k]
+        const v = a[k]
         // don't add missing properties
         if (k in a) {
           // don't strip undefined properties
-          o[k] = v === undefined ? v : properties[k].encode(v)
+          o[k] = v === undefined ? undefined : properties[k].encode(v)
         }
       }
       return o
@@ -83,12 +94,12 @@ export function partial<A>(properties: { [K in keyof A]: Encoder<A[K]> }): Encod
 }
 
 /**
- * @since 2.2.0
+ * @since 2.2.3
  */
-export function record<A>(codomain: Encoder<A>): Encoder<Record<string, A>> {
+export function record<O, A>(codomain: Encoder<O, A>): Encoder<Record<string, O>, Record<string, A>> {
   return {
     encode: (r) => {
-      const o: Record<string, unknown> = {}
+      const o: Record<string, O> = {}
       for (const k in r) {
         o[k] = codomain.encode(r[k])
       }
@@ -98,105 +109,114 @@ export function record<A>(codomain: Encoder<A>): Encoder<Record<string, A>> {
 }
 
 /**
- * @since 2.2.0
+ * @since 2.2.3
  */
-export function array<A>(items: Encoder<A>): Encoder<Array<A>> {
+export function array<O, A>(items: Encoder<O, A>): Encoder<Array<O>, Array<A>> {
   return {
     encode: (as) => as.map(items.encode)
   }
 }
 
 /**
- * @since 2.2.0
+ * @since 2.2.3
  */
-export function tuple<A extends ReadonlyArray<unknown>>(...components: { [K in keyof A]: Encoder<A[K]> }): Encoder<A> {
+export function tuple<C extends ReadonlyArray<Encoder<any, any>>>(
+  ...components: C
+): Encoder<{ [K in keyof C]: OutputOf<C[K]> }, { [K in keyof C]: TypeOf<C[K]> }> {
   return {
-    encode: (as) => components.map((c, i) => c.encode(as[i]))
+    encode: (as) => components.map((c, i) => c.encode(as[i])) as any
   }
 }
 
 /**
- * @since 2.2.0
+ * @since 2.2.3
  */
-export function intersection<A, B>(left: Encoder<A>, right: Encoder<B>): Encoder<A & B> {
+export function intersection<O, A, P, B>(left: Encoder<O, A>, right: Encoder<P, B>): Encoder<O & P, A & B> {
   return {
     encode: (ab) => intersect(left.encode(ab), right.encode(ab))
   }
 }
 
 /**
- * @since 2.2.0
+ * @since 2.2.3
  */
 export function sum<T extends string>(
   tag: T
-): <A>(members: { [K in keyof A]: Encoder<A[K] & Record<T, K>> }) => Encoder<A[keyof A]> {
-  return (members: Record<string, Encoder<any>>) => {
+): <M extends Record<string, Encoder<any, any>>>(members: M) => Encoder<OutputOf<M[keyof M]>, TypeOf<M[keyof M]>> {
+  return (members) => {
     return {
-      encode: (a: Record<string, any>) => members[a[tag]].encode(a)
+      encode: (a) => members[a[tag]].encode(a)
     }
   }
 }
 
 /**
- * @since 2.2.0
+ * @since 2.2.3
  */
-export function lazy<A>(f: () => Encoder<A>): Encoder<A> {
-  const get = memoize<void, Encoder<A>>(f)
+export function lazy<O, A>(f: () => Encoder<O, A>): Encoder<O, A> {
+  const get = memoize<void, Encoder<O, A>>(f)
   return {
     encode: (a) => get().encode(a)
   }
 }
 
+// -------------------------------------------------------------------------------------
+// pipeables
+// -------------------------------------------------------------------------------------
+
+/**
+ * @since 2.2.3
+ */
+export const contramap: <A, B>(f: (b: B) => A) => <E>(fa: Encoder<E, A>) => Encoder<E, B> = (f) => (fa) =>
+  contramap_(fa, f)
+
+const contramap_: <E, A, B>(fa: Encoder<E, A>, f: (b: B) => A) => Encoder<E, B> = (fa, f) => ({
+  encode: (b) => fa.encode(f(b))
+})
+
+/**
+ * @since 2.2.3
+ */
+export const compose: <E, A>(ea: Encoder<E, A>) => <B>(ab: Encoder<A, B>) => Encoder<E, B> = (ea) => (ab) =>
+  compose_(ab, ea)
+
+const compose_: <E, A, B>(ab: Encoder<A, B>, la: Encoder<E, A>) => Encoder<E, B> = (ab, ea) => ({
+  encode: (b) => ea.encode(ab.encode(b))
+})
+
 // -------------------------------------------------------------------------------------
 // instances
 // -------------------------------------------------------------------------------------
 
 /**
- * @since 2.2.0
+ * @since 2.2.3
  */
-export const URI = 'Encoder'
+export const URI = 'io-ts/Encoder'
 
 /**
- * @since 2.2.0
+ * @since 2.2.3
  */
 export type URI = typeof URI
 
 declare module 'fp-ts/lib/HKT' {
-  interface URItoKind<A> {
-    readonly Encoder: Encoder<A>
+  interface URItoKind2<E, A> {
+    readonly [URI]: Encoder<E, A>
   }
 }
 
 /**
- * @since 2.2.0
+ * @since 2.2.3
  */
-export const encoder: Contravariant1<URI> & Schemable<URI> = {
+export const contravariantEncoder: Contravariant2<URI> = {
   URI,
-  contramap: (fa, f) => ({
-    encode: (b) => fa.encode(f(b))
-  }),
-  literal: () => id,
-  string: id,
-  number: id,
-  boolean: id,
-  UnknownArray: id,
-  UnknownRecord: id,
-  nullable,
-  type,
-  partial,
-  record,
-  array,
-  tuple,
-  intersection,
-  sum,
-  lazy: (_, f) => lazy(f)
+  contramap: contramap_
 }
 
-const { contramap } = pipeable(encoder)
-
-export {
-  /**
-   * @since 2.2.0
-   */
-  contramap
+/**
+ * @since 2.2.3
+ */
+export const categoryEncoder: Category2<URI> = {
+  URI,
+  compose: compose_,
+  id
 }
diff --git a/src/Eq.ts b/src/Eq.ts
index 78ba937..05fa45a 100644
--- a/src/Eq.ts
+++ b/src/Eq.ts
@@ -4,9 +4,14 @@
 import * as A from 'fp-ts/lib/Array'
 import * as E from 'fp-ts/lib/Eq'
 import * as R from 'fp-ts/lib/Record'
-import * as S from './Schemable'
+import { memoize, Schemable1, WithRefinement1, WithUnknownContainers1 } from './Schemable'
 import Eq = E.Eq
 
+/**
+ * @since 2.2.3
+ */
+export type URI = E.URI
+
 /**
  * @since 2.2.2
  */
@@ -118,9 +123,7 @@ export function intersection<A, B>(left: Eq<A>, right: Eq<B>): Eq<A & B> {
 /**
  * @since 2.2.2
  */
-export function sum<T extends string>(
-  tag: T
-): <A>(members: { [K in keyof A]: Eq<A[K] & Record<T, K>> }) => Eq<A[keyof A]> {
+export function sum<T extends string>(tag: T): <A>(members: { [K in keyof A]: Eq<A[K]> }) => Eq<A[keyof A]> {
   return (members: Record<string, Eq<any>>) => {
     return {
       equals: (x: Record<string, any>, y: Record<string, any>) => {
@@ -139,23 +142,25 @@ export function sum<T extends string>(
  * @since 2.2.2
  */
 export function lazy<A>(f: () => Eq<A>): Eq<A> {
-  const get = S.memoize<void, Eq<A>>(f)
+  const get = memoize<void, Eq<A>>(f)
   return {
     equals: (x, y) => get().equals(x, y)
   }
 }
 
+// -------------------------------------------------------------------------------------
+// instances
+// -------------------------------------------------------------------------------------
+
 /**
- * @since 2.2.2
+ * @since 2.2.3
  */
-export const eq: typeof E.eq & S.Schemable<E.URI> = {
-  ...E.eq,
+export const schemableEq: Schemable1<E.URI> & WithUnknownContainers1<E.URI> & WithRefinement1<E.URI> = {
+  URI: E.URI,
   literal: () => E.eqStrict,
   string,
   number,
   boolean,
-  UnknownArray,
-  UnknownRecord,
   nullable,
   type,
   partial,
@@ -164,5 +169,8 @@ export const eq: typeof E.eq & S.Schemable<E.URI> = {
   tuple,
   intersection,
   sum,
-  lazy: (_, f) => lazy(f)
+  lazy: (_, f) => lazy(f),
+  UnknownArray,
+  UnknownRecord,
+  refinement: (from) => from
 }
diff --git a/src/Guard.ts b/src/Guard.ts
index 7a5feae..8c5fd7d 100644
--- a/src/Guard.ts
+++ b/src/Guard.ts
@@ -1,7 +1,7 @@
 /**
  * @since 2.2.0
  */
-import { Schemable, memoize, WithUnion, Literal } from './Schemable'
+import { Literal, memoize, Schemable1, WithRefinement1, WithUnion1, WithUnknownContainers1 } from './Schemable'
 
 // -------------------------------------------------------------------------------------
 // model
@@ -182,14 +182,12 @@ export function union<A extends ReadonlyArray<unknown>>(...members: { [K in keyo
 /**
  * @since 2.2.0
  */
-export function sum<T extends string>(
-  tag: T
-): <A>(members: { [K in keyof A]: Guard<A[K] & Record<T, K>> }) => Guard<A[keyof A]> {
-  return <A>(members: { [K in keyof A]: Guard<A[K] & Record<T, K>> }) =>
-    refinement(UnknownRecord, (r): r is { [K in keyof A]: A[K] & Record<T, K> }[keyof A] => {
+export function sum<T extends string>(tag: T): <A>(members: { [K in keyof A]: Guard<A[K]> }) => Guard<A[keyof A]> {
+  return (members: Record<string, Guard<unknown>>) =>
+    refinement(UnknownRecord, (r): r is any => {
       const v = r[tag]
       if (string.is(v) && v in members) {
-        return (members as any)[v].is(r)
+        return members[v].is(r)
       }
       return false
     })
@@ -212,7 +210,7 @@ export function lazy<A>(f: () => Guard<A>): Guard<A> {
 /**
  * @since 2.2.0
  */
-export const URI = 'Guard'
+export const URI = 'io-ts/Guard'
 
 /**
  * @since 2.2.0
@@ -221,29 +219,30 @@ export type URI = typeof URI
 
 declare module 'fp-ts/lib/HKT' {
   interface URItoKind<A> {
-    readonly Guard: Guard<A>
+    readonly [URI]: Guard<A>
   }
 }
 
 /**
- * @since 2.2.0
+ * @since 2.2.3
  */
-export const guard: Schemable<URI> & WithUnion<URI> = {
+export const schemableGuard: Schemable1<URI> & WithUnknownContainers1<URI> & WithUnion1<URI> & WithRefinement1<URI> = {
   URI,
   literal,
   string,
   number,
   boolean,
-  UnknownArray,
-  UnknownRecord,
   nullable,
   type,
   partial,
   record,
   array,
-  tuple: tuple as Schemable<URI>['tuple'],
+  tuple: tuple as Schemable1<URI>['tuple'],
   intersection,
   sum,
   lazy: (_, f) => lazy(f),
-  union
+  UnknownArray,
+  UnknownRecord,
+  union,
+  refinement: refinement as WithRefinement1<URI>['refinement']
 }
diff --git a/src/JsonCodec.ts b/src/JsonCodec.ts
new file mode 100644
index 0000000..8f94fd6
--- /dev/null
+++ b/src/JsonCodec.ts
@@ -0,0 +1,188 @@
+/**
+ * @since 2.2.3
+ */
+import { Invariant1 } from 'fp-ts/lib/Invariant'
+import * as D from './Decoder'
+import * as JE from './JsonEncoder'
+import * as C from './Codec'
+import { Literal, Schemable1, WithRefinement1 } from './Schemable'
+
+// -------------------------------------------------------------------------------------
+// model
+// -------------------------------------------------------------------------------------
+
+/**
+ * Laws:
+ *
+ * 1. `pipe(codec.decode(u), E.fold(() => u, codec.encode)) = u` for all `u` in `unknown`
+ * 2. `codec.decode(codec.encode(a)) = E.right(a)` for all `a` in `A`
+ *
+ * @since 2.2.3
+ */
+export interface JsonCodec<A> extends C.Codec<JE.Json, A> {}
+
+/**
+ * @since 2.2.2
+ */
+export type TypeOf<C> = JE.TypeOf<C>
+
+// -------------------------------------------------------------------------------------
+// constructors
+// -------------------------------------------------------------------------------------
+
+/**
+ * @since 2.2.3
+ */
+export const make: <A>(decoder: D.Decoder<A>, encoder: JE.JsonEncoder<A>) => JsonCodec<A> = C.make
+
+/**
+ * @since 2.2.3
+ */
+export const literal: <A extends ReadonlyArray<Literal>>(...values: A) => JsonCodec<A[number]> = C.literal
+
+// -------------------------------------------------------------------------------------
+// primitives
+// -------------------------------------------------------------------------------------
+
+/**
+ * @since 2.2.3
+ */
+export const string: JsonCodec<string> = C.string
+
+/**
+ * @since 2.2.3
+ */
+export const number: JsonCodec<number> = C.number
+
+/**
+ * @since 2.2.3
+ */
+export const boolean: JsonCodec<boolean> = C.boolean
+
+// -------------------------------------------------------------------------------------
+// combinators
+// -------------------------------------------------------------------------------------
+
+/**
+ * @since 2.2.3
+ */
+export const withExpected: <A>(
+  codec: JsonCodec<A>,
+  expected: (actual: unknown, e: D.DecodeError) => D.DecodeError
+) => JsonCodec<A> = C.withExpected
+
+/**
+ * @since 2.2.3
+ */
+export const refinement: <A, B extends A>(
+  from: JsonCodec<A>,
+  refinement: (a: A) => a is B,
+  expected: string
+) => JsonCodec<B> = C.refinement
+
+/**
+ * @since 2.2.3
+ */
+export const nullable: <A>(or: JsonCodec<A>) => JsonCodec<null | A> = C.nullable
+
+/**
+ * @since 2.2.3
+ */
+export const type: <A>(properties: { [K in keyof A]: JsonCodec<A[K]> }) => JsonCodec<A> = C.type as any
+
+/**
+ * @since 2.2.3
+ */
+export const partial: <A>(properties: { [K in keyof A]: JsonCodec<A[K]> }) => JsonCodec<Partial<A>> = C.partial as any
+
+/**
+ * @since 2.2.3
+ */
+export const record: <A>(codomain: JsonCodec<A>) => JsonCodec<Record<string, A>> = C.record
+
+/**
+ * @since 2.2.3
+ */
+export const array: <A>(items: JsonCodec<A>) => JsonCodec<Array<A>> = C.array
+
+/**
+ * @since 2.2.3
+ */
+export const tuple: <A extends ReadonlyArray<unknown>>(
+  ...components: { [K in keyof A]: JsonCodec<A[K]> }
+) => JsonCodec<A> = C.tuple as any
+
+/**
+ * @since 2.2.3
+ */
+export const intersection: <A, B>(left: JsonCodec<A>, right: JsonCodec<B>) => JsonCodec<A & B> = C.intersection
+
+/**
+ * @since 2.2.3
+ */
+export const sum: <T extends string>(
+  tag: T
+) => <A>(members: { [K in keyof A]: JsonCodec<A[K]> }) => JsonCodec<A[keyof A]> = C.sum as any
+
+/**
+ * @since 2.2.3
+ */
+export const lazy: <A>(id: string, f: () => JsonCodec<A>) => JsonCodec<A> = C.lazy
+
+// -------------------------------------------------------------------------------------
+// pipeables
+// -------------------------------------------------------------------------------------
+
+/**
+ * @since 2.2.3
+ */
+export const imap: <A, B>(f: (a: A) => B, g: (b: B) => A) => (fa: JsonCodec<A>) => JsonCodec<B> = C.imap
+
+// -------------------------------------------------------------------------------------
+// instances
+// -------------------------------------------------------------------------------------
+
+/**
+ * @since 2.2.3
+ */
+export const URI = 'io-ts/JsonCodec'
+
+/**
+ * @since 2.2.3
+ */
+export type URI = typeof URI
+
+declare module 'fp-ts/lib/HKT' {
+  interface URItoKind<A> {
+    readonly [URI]: JsonCodec<A>
+  }
+}
+
+/**
+ * @since 2.2.3
+ */
+export const invariantJsonCodec: Invariant1<URI> = {
+  URI,
+  imap: C.invariantCodec.imap
+}
+
+/**
+ * @since 2.2.3
+ */
+export const schemableJsonCodec: Schemable1<URI> & WithRefinement1<URI> = {
+  URI,
+  literal,
+  string,
+  number,
+  boolean,
+  nullable,
+  type,
+  partial,
+  record,
+  array,
+  tuple: tuple as Schemable1<URI>['tuple'],
+  intersection,
+  sum,
+  lazy,
+  refinement: refinement as WithRefinement1<URI>['refinement']
+}
diff --git a/src/JsonEncoder.ts b/src/JsonEncoder.ts
new file mode 100644
index 0000000..c0041d1
--- /dev/null
+++ b/src/JsonEncoder.ts
@@ -0,0 +1,161 @@
+/**
+ * @since 2.2.3
+ */
+import { Contravariant1 } from 'fp-ts/lib/Contravariant'
+import { identity } from 'fp-ts/lib/function'
+import * as E from './Encoder'
+import { Schemable1 } from './Schemable'
+
+// -------------------------------------------------------------------------------------
+// model
+// -------------------------------------------------------------------------------------
+
+/**
+ * @since 2.2.3
+ */
+export interface JsonArray extends Array<Json> {}
+
+/**
+ * @since 2.2.3
+ */
+export type JsonObject = { [key: string]: Json }
+
+/**
+ * @since 2.2.3
+ */
+export type Json = null | string | number | boolean | JsonObject | JsonArray
+
+/**
+ * @since 2.2.3
+ */
+export interface JsonEncoder<A> {
+  readonly encode: (a: A) => Json
+}
+
+/**
+ * @since 2.2.3
+ */
+export type TypeOf<E> = E.TypeOf<E>
+
+// -------------------------------------------------------------------------------------
+// primitives
+// -------------------------------------------------------------------------------------
+
+/**
+ * @since 2.2.3
+ */
+export const id: JsonEncoder<Json> = {
+  encode: identity
+}
+
+// -------------------------------------------------------------------------------------
+// combinators
+// -------------------------------------------------------------------------------------
+
+/**
+ * @since 2.2.3
+ */
+export const nullable: <A>(or: JsonEncoder<A>) => JsonEncoder<null | A> = E.nullable
+
+/**
+ * @since 2.2.3
+ */
+export const type: <A>(properties: { [K in keyof A]: JsonEncoder<A[K]> }) => JsonEncoder<A> = E.type as any
+
+/**
+ * @since 2.2.3
+ */
+export const partial: <A>(
+  properties: { [K in keyof A]: JsonEncoder<A[K]> }
+) => JsonEncoder<Partial<A>> = E.partial as any
+
+/**
+ * @since 2.2.3
+ */
+export const record: <A>(codomain: JsonEncoder<A>) => JsonEncoder<Record<string, A>> = E.record
+
+/**
+ * @since 2.2.3
+ */
+export const array: <A>(items: JsonEncoder<A>) => JsonEncoder<Array<A>> = E.array
+
+/**
+ * @since 2.2.3
+ */
+export const tuple: <A extends ReadonlyArray<unknown>>(
+  ...components: { [K in keyof A]: JsonEncoder<A[K]> }
+) => JsonEncoder<A> = E.tuple as any
+
+/**
+ * @since 2.2.3
+ */
+export const intersection: <A, B>(left: JsonEncoder<A>, right: JsonEncoder<B>) => JsonEncoder<A & B> = E.intersection
+
+/**
+ * @since 2.2.3
+ */
+export const sum: <T extends string>(
+  tag: T
+) => <A>(members: { [K in keyof A]: JsonEncoder<A[K]> }) => JsonEncoder<A[keyof A]> = E.sum as any
+
+/**
+ * @since 2.2.3
+ */
+export const lazy: <A>(f: () => JsonEncoder<A>) => JsonEncoder<A> = E.lazy
+
+// -------------------------------------------------------------------------------------
+// pipeables
+// -------------------------------------------------------------------------------------
+
+/**
+ * @since 2.2.3
+ */
+export const contramap: <A, B>(f: (b: B) => A) => (fa: JsonEncoder<A>) => JsonEncoder<B> = E.contramap
+
+// -------------------------------------------------------------------------------------
+// instances
+// -------------------------------------------------------------------------------------
+
+/**
+ * @since 2.2.3
+ */
+export const URI = 'io-ts/JsonEncoder'
+
+/**
+ * @since 2.2.3
+ */
+export type URI = typeof URI
+
+declare module 'fp-ts/lib/HKT' {
+  interface URItoKind<A> {
+    readonly [URI]: JsonEncoder<A>
+  }
+}
+
+/**
+ * @since 2.2.3
+ */
+export const contravariantJsonEncoder: Contravariant1<URI> = {
+  URI,
+  contramap: E.contravariantEncoder.contramap
+}
+
+/**
+ * @since 2.2.3
+ */
+export const schemableJsonEncoder: Schemable1<URI> = {
+  URI,
+  literal: () => id,
+  string: id,
+  number: id,
+  boolean: id,
+  nullable,
+  type,
+  partial,
+  record,
+  array,
+  tuple,
+  intersection,
+  sum,
+  lazy: (_, f) => lazy(f)
+}
diff --git a/src/Schema.ts b/src/Schema.ts
index 30fd480..8e5b424 100644
--- a/src/Schema.ts
+++ b/src/Schema.ts
@@ -1,14 +1,14 @@
 /**
  * @since 2.2.0
  */
-import { Kind, URIS } from 'fp-ts/lib/HKT'
-import { Schemable, memoize } from './Schemable'
+import { HKT, URIS, Kind } from 'fp-ts/lib/HKT'
+import { memoize, Schemable, Schemable1 } from './Schemable'
 
 /**
  * @since 2.2.0
  */
 export interface Schema<A> {
-  <S extends URIS>(S: Schemable<S>): Kind<S, A>
+  <S>(S: Schemable<S>): HKT<S, A>
 }
 
 /**
@@ -19,6 +19,13 @@ export type TypeOf<S> = S extends Schema<infer A> ? A : never
 /**
  * @since 2.2.0
  */
-export function make<A>(f: Schema<A>): Schema<A> {
-  return memoize(f)
+export function make<A>(schema: Schema<A>): Schema<A> {
+  return memoize(schema)
+}
+
+/**
+ * @since 2.2.3
+ */
+export function interpreter<S extends URIS>(S: Schemable1<S>): <A>(schema: Schema<A>) => Kind<S, A> {
+  return (schema: any) => schema(S)
 }
diff --git a/src/Schemable.ts b/src/Schemable.ts
index a4d615c..2f374e7 100644
--- a/src/Schemable.ts
+++ b/src/Schemable.ts
@@ -1,7 +1,7 @@
 /**
  * @since 2.2.0
  */
-import { Kind, URIS } from 'fp-ts/lib/HKT'
+import { Kind, URIS, HKT } from 'fp-ts/lib/HKT'
 
 /**
  * @since 2.2.0
@@ -9,16 +9,34 @@ import { Kind, URIS } from 'fp-ts/lib/HKT'
 export type Literal = string | number | boolean | null
 
 /**
- * @since 2.2.0
+ * @since 2.2.3
+ */
+export interface Schemable<S> {
+  readonly URI: S
+  readonly literal: <A extends ReadonlyArray<Literal>>(...values: A) => HKT<S, A[number]>
+  readonly string: HKT<S, string>
+  readonly number: HKT<S, number>
+  readonly boolean: HKT<S, boolean>
+  readonly nullable: <A>(or: HKT<S, A>) => HKT<S, null | A>
+  readonly type: <A>(properties: { [K in keyof A]: HKT<S, A[K]> }) => HKT<S, A>
+  readonly partial: <A>(properties: { [K in keyof A]: HKT<S, A[K]> }) => HKT<S, Partial<A>>
+  readonly record: <A>(codomain: HKT<S, A>) => HKT<S, Record<string, A>>
+  readonly array: <A>(items: HKT<S, A>) => HKT<S, Array<A>>
+  readonly tuple: <A extends ReadonlyArray<unknown>>(...components: { [K in keyof A]: HKT<S, A[K]> }) => HKT<S, A>
+  readonly intersection: <A, B>(left: HKT<S, A>, right: HKT<S, B>) => HKT<S, A & B>
+  readonly sum: <T extends string>(tag: T) => <A>(members: { [K in keyof A]: HKT<S, A[K]> }) => HKT<S, A[keyof A]>
+  readonly lazy: <A>(id: string, f: () => HKT<S, A>) => HKT<S, A>
+}
+
+/**
+ * @since 2.2.3
  */
-export interface Schemable<S extends URIS> {
+export interface Schemable1<S extends URIS> {
   readonly URI: S
   readonly literal: <A extends ReadonlyArray<Literal>>(...values: A) => Kind<S, A[number]>
   readonly string: Kind<S, string>
   readonly number: Kind<S, number>
   readonly boolean: Kind<S, boolean>
-  readonly UnknownArray: Kind<S, Array<unknown>>
-  readonly UnknownRecord: Kind<S, Record<string, unknown>>
   readonly nullable: <A>(or: Kind<S, A>) => Kind<S, null | A>
   readonly type: <A>(properties: { [K in keyof A]: Kind<S, A[K]> }) => Kind<S, A>
   readonly partial: <A>(properties: { [K in keyof A]: Kind<S, A[K]> }) => Kind<S, Partial<A>>
@@ -26,21 +44,56 @@ export interface Schemable<S extends URIS> {
   readonly array: <A>(items: Kind<S, A>) => Kind<S, Array<A>>
   readonly tuple: <A extends ReadonlyArray<unknown>>(...components: { [K in keyof A]: Kind<S, A[K]> }) => Kind<S, A>
   readonly intersection: <A, B>(left: Kind<S, A>, right: Kind<S, B>) => Kind<S, A & B>
-  readonly sum: <T extends string>(
-    tag: T
-  ) => <A>(members: { [K in keyof A]: Kind<S, A[K] & Record<T, K>> }) => Kind<S, A[keyof A]>
+  readonly sum: <T extends string>(tag: T) => <A>(members: { [K in keyof A]: Kind<S, A[K]> }) => Kind<S, A[keyof A]>
   readonly lazy: <A>(id: string, f: () => Kind<S, A>) => Kind<S, A>
 }
 
 /**
- * @since 2.2.0
+ * @since 2.2.3
+ */
+export interface WithUnknownContainers<S> {
+  readonly UnknownArray: HKT<S, Array<unknown>>
+  readonly UnknownRecord: HKT<S, Record<string, unknown>>
+}
+
+/**
+ * @since 2.2.3
  */
-export interface WithUnion<S extends URIS> {
+export interface WithUnknownContainers1<S extends URIS> {
+  readonly UnknownArray: Kind<S, Array<unknown>>
+  readonly UnknownRecord: Kind<S, Record<string, unknown>>
+}
+
+/**
+ * @since 2.2.3
+ */
+export interface WithUnion<S> {
+  readonly union: <A extends ReadonlyArray<unknown>>(...members: { [K in keyof A]: HKT<S, A[K]> }) => HKT<S, A[number]>
+}
+
+/**
+ * @since 2.2.3
+ */
+export interface WithUnion1<S extends URIS> {
   readonly union: <A extends ReadonlyArray<unknown>>(
     ...members: { [K in keyof A]: Kind<S, A[K]> }
   ) => Kind<S, A[number]>
 }
 
+/**
+ * @since 2.2.3
+ */
+export interface WithRefinement<S> {
+  readonly refinement: <A, B extends A>(from: HKT<S, A>, refinement: (a: A) => a is B, expected: string) => HKT<S, B>
+}
+
+/**
+ * @since 2.2.3
+ */
+export interface WithRefinement1<S extends URIS> {
+  readonly refinement: <A, B extends A>(from: Kind<S, A>, refinement: (a: A) => a is B, expected: string) => Kind<S, B>
+}
+
 /**
  * @since 2.2.0
  */
diff --git a/src/Type.ts b/src/Type.ts
new file mode 100644
index 0000000..24e7208
--- /dev/null
+++ b/src/Type.ts
@@ -0,0 +1,180 @@
+/**
+ * @since 2.2.3
+ */
+import * as t from './index'
+import { Literal, Schemable1, WithUnion1, WithRefinement1, WithUnknownContainers1 } from './Schemable'
+
+// -------------------------------------------------------------------------------------
+// model
+// -------------------------------------------------------------------------------------
+
+/**
+ * @since 2.2.3
+ */
+export interface Type<A> extends t.Type<A, unknown, unknown> {}
+
+// -------------------------------------------------------------------------------------
+// constructors
+// -------------------------------------------------------------------------------------
+
+/**
+ * @since 2.2.3
+ */
+export function literal<A extends ReadonlyArray<Literal>>(...values: A): Type<A[number]> {
+  return t.union(values.map((v) => t.literal(v as any)) as any)
+}
+
+// -------------------------------------------------------------------------------------
+// primitives
+// -------------------------------------------------------------------------------------
+
+/**
+ * @since 2.2.3
+ */
+export const string: Type<string> = t.string
+
+/**
+ * @since 2.2.3
+ */
+export const number: Type<number> = t.number
+
+/**
+ * @since 2.2.3
+ */
+export const boolean: Type<boolean> = t.boolean
+
+/**
+ * @since 2.2.3
+ */
+export const UnknownArray: Type<Array<unknown>> = t.UnknownArray
+
+/**
+ * @since 2.2.3
+ */
+export const UnknownRecord: Type<Record<string, unknown>> = t.UnknownRecord
+
+// -------------------------------------------------------------------------------------
+// combinators
+// -------------------------------------------------------------------------------------
+
+/**
+ * @since 2.2.3
+ */
+export function refinement<A, B extends A>(from: Type<A>, refinement: (a: A) => a is B, expected: string): Type<B> {
+  // tslint:disable-next-line: deprecation
+  return t.refinement(from, refinement, expected) as any
+}
+
+/**
+ * @since 2.2.3
+ */
+export function nullable<A>(or: Type<A>): Type<null | A> {
+  return t.union([t.null, or])
+}
+
+/**
+ * @since 2.2.3
+ */
+export function type<A>(properties: { [K in keyof A]: Type<A[K]> }): Type<A> {
+  return t.type(properties) as any
+}
+
+/**
+ * @since 2.2.3
+ */
+export function partial<A>(properties: { [K in keyof A]: Type<A[K]> }): Type<Partial<A>> {
+  return t.partial(properties)
+}
+
+/**
+ * @since 2.2.3
+ */
+export function record<A>(codomain: Type<A>): Type<Record<string, A>> {
+  return t.record(t.string, codomain)
+}
+
+/**
+ * @since 2.2.3
+ */
+export function array<A>(items: Type<A>): Type<Array<A>> {
+  return t.array(items)
+}
+
+/**
+ * @since 2.2.3
+ */
+export function tuple<A extends ReadonlyArray<unknown>>(...components: { [K in keyof A]: Type<A[K]> }): Type<A> {
+  return t.tuple(components as any) as any
+}
+
+/**
+ * @since 2.2.3
+ */
+export function intersection<A, B>(left: Type<A>, right: Type<B>): Type<A & B> {
+  return t.intersection([left, right])
+}
+
+/**
+ * @since 2.2.3
+ */
+export function lazy<A>(id: string, f: () => Type<A>): Type<A> {
+  return t.recursion(id, f)
+}
+
+/**
+ * @since 2.2.3
+ */
+export function sum<T extends string>(_tag: T): <A>(members: { [K in keyof A]: Type<A[K]> }) => Type<A[keyof A]> {
+  return (members) => t.union(Object.values(members) as any)
+}
+
+/**
+ * @since 2.2.3
+ */
+export function union<A extends ReadonlyArray<unknown>>(...members: { [K in keyof A]: Type<A[K]> }): Type<A[number]> {
+  return t.union(members as any)
+}
+
+// -------------------------------------------------------------------------------------
+// instances
+// -------------------------------------------------------------------------------------
+
+/**
+ * @since 2.2.3
+ */
+export const URI = 'io-ts/Type'
+
+/**
+ * @since 2.2.3
+ */
+export type URI = typeof URI
+
+declare module 'fp-ts/lib/HKT' {
+  interface URItoKind<A> {
+    readonly [URI]: Type<A>
+  }
+}
+
+/**
+ * @since 2.2.3
+ */
+export const schemableType: Schemable1<URI> & WithUnknownContainers1<URI> & WithUnion1<URI> & WithRefinement1<URI> = {
+  URI,
+  literal,
+  string,
+  number,
+  boolean,
+  nullable,
+  type,
+  partial,
+  record,
+  array,
+  tuple: tuple as Schemable1<URI>['tuple'],
+  intersection,
+  sum,
+  lazy,
+  UnknownArray,
+  UnknownRecord,
+  union,
+  refinement: refinement as WithRefinement1<URI>['refinement']
+}
diff --git a/src/index.ts b/src/index.ts
index 0af6214..8bafbfb 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -1554,12 +1554,7 @@ export class ReadonlyType<C extends Any, A = any, O = A, I = unknown> extends Ty
  * @since 1.5.3
  */
 export interface ReadonlyC<C extends Mixed>
-  extends ReadonlyType<
-    C,
-    { readonly [K in keyof TypeOf<C>]: TypeOf<C>[K] },
-    { readonly [K in keyof OutputOf<C>]: OutputOf<C>[K] },
-    unknown
-  > {}
+  extends ReadonlyType<C, Readonly<TypeOf<C>>, Readonly<OutputOf<C>>, unknown> {}
 
 /**
  * @since 1.0.0


Test patch:
diff --git a/test/2.1.x/union.ts b/test/2.1.x/union.ts
index 1410dfa..d340cba 100644
--- a/test/2.1.x/union.ts
+++ b/test/2.1.x/union.ts
@@ -199,6 +199,7 @@ describe('union', () => {
         }
       )
       assert.strictEqual(
+        // @ts-expect-error
         t.getTags(t.intersection([t.type({ a: t.literal('a') }), t.type({ a: t.literal('b') })])),
         t.emptyTags
       )
diff --git a/test/Arbitrary.ts b/test/Arbitrary.ts
index bc2bf6c..60e218f 100644
--- a/test/Arbitrary.ts
+++ b/test/Arbitrary.ts
@@ -43,6 +43,10 @@ export const UnknownRecord: Arbitrary<Record<string, unknown>> = fc.dictionary(s
 // combinators
 // -------------------------------------------------------------------------------------
 
+export function refinement<A, B extends A>(from: Arbitrary<A>, refinement: (a: A) => a is B): Arbitrary<B> {
+  return from.filter(refinement)
+}
+
 export function nullable<A>(or: Arbitrary<A>): Arbitrary<null | A> {
   return fc.oneof(fc.constant(null), or)
 }
@@ -83,7 +87,7 @@ export function intersection<A, B>(left: Arbitrary<A>, right: Arbitrary<B>): Arb
 
 export function sum<T extends string>(
   _tag: T
-): <A>(members: { [K in keyof A]: Arbitrary<A[K] & Record<T, K>> }) => Arbitrary<A[keyof A]> {
+): <A>(members: { [K in keyof A]: Arbitrary<A[K]> }) => Arbitrary<A[keyof A]> {
   return (members: Record<string, Arbitrary<any>>) => fc.oneof(...Object.keys(members).map((k) => members[k]))
 }
 
@@ -112,22 +116,26 @@ declare module 'fp-ts/lib/HKT' {
   }
 }
 
-export const arbitrary: S.Schemable<URI> & S.WithUnion<URI> = {
+export const schemableArbitrary: S.Schemable1<URI> &
+  S.WithUnknownContainers1<URI> &
+  S.WithUnion1<URI> &
+  S.WithRefinement1<URI> = {
   URI,
   literal,
   string,
   number,
   boolean,
-  UnknownArray,
-  UnknownRecord,
   nullable,
   type,
   partial,
   record,
   array,
-  tuple: tuple as S.Schemable<URI>['tuple'],
+  tuple: tuple as S.Schemable1<URI>['tuple'],
   intersection,
   sum,
   lazy: (_, f) => lazy(f),
-  union
+  UnknownArray,
+  UnknownRecord,
+  union,
+  refinement: refinement as S.WithRefinement1<URI>['refinement']
 }
diff --git a/test/Codec.ts b/test/Codec.ts
index 20f1ec4..c9c30e1 100644
--- a/test/Codec.ts
+++ b/test/Codec.ts
@@ -3,9 +3,9 @@ import { left, right } from 'fp-ts/lib/Either'
 import * as C from '../src/Codec'
 import * as D from '../src/Decoder'
 import * as G from '../src/Guard'
-import * as E from '../src/Encoder'
+import { pipe } from 'fp-ts/lib/pipeable'
 
-const NumberFromString: C.Codec<number> = C.make(
+const NumberFromString: C.Codec<string, number> = C.make(
   D.parse(D.string, (s) => {
     const n = parseFloat(s)
     return isNaN(n) ? left(`cannot decode ${JSON.stringify(s)}, should be parsable into a number`) : right(n)
@@ -17,23 +17,23 @@ interface PositiveBrand {
   readonly Positive: unique symbol
 }
 type Positive = number & PositiveBrand
-const Positive: C.Codec<Positive> = C.refinement(C.number, (n): n is Positive => n > 0, 'Positive')
+const Positive: C.Codec<number, Positive> = C.refinement(C.number, (n): n is Positive => n > 0, 'Positive')
 
 interface IntBrand {
   readonly Int: unique symbol
 }
 type Int = number & IntBrand
-const Int: C.Codec<Int> = C.refinement(C.number, (n): n is Int => Number.isInteger(n), 'Int')
+const Int: C.Codec<number, Int> = C.refinement(C.number, (n): n is Int => Number.isInteger(n), 'Int')
 
 const undefinedGuard: G.Guard<undefined> = {
   is: (u): u is undefined => u === undefined
 }
-const undef: C.Codec<undefined> = C.make(D.fromGuard(undefinedGuard, 'undefined'), E.id)
+const undef: C.Codec<undefined, undefined> = C.fromDecoder(D.fromGuard(undefinedGuard, 'undefined'))
 
 describe('Codec', () => {
-  describe('codec', () => {
+  describe('invariantCodec', () => {
     it('imap', () => {
-      const codec = C.codec.imap(
+      const codec = C.invariantCodec.imap(
         C.string,
         (s) => ({ value: s }),
         ({ value }) => value
@@ -43,6 +43,18 @@ describe('Codec', () => {
     })
   })
 
+  it('imap', () => {
+    const codec = pipe(
+      C.string,
+      C.imap(
+        (s) => ({ value: s }),
+        ({ value }) => value
+      )
+    )
+    assert.deepStrictEqual(codec.decode('a'), right({ value: 'a' }))
+    assert.deepStrictEqual(codec.encode({ value: 'a' }), 'a')
+  })
+
   describe('withExpected', () => {
     describe('decode', () => {
       it('should, return the provided expected', () => {
@@ -572,8 +584,12 @@ describe('Codec', () => {
       a: number
       b?: A
     }
+    interface AOut {
+      a: string
+      b?: AOut
+    }
 
-    const codec: C.Codec<A> = C.lazy('A', () =>
+    const codec: C.Codec<AOut, A> = C.lazy('A', () =>
       C.intersection(C.type({ a: NumberFromString }), C.partial({ b: codec }))
     )
 
diff --git a/test/Decoder.ts b/test/Decoder.ts
index 9471e79..80ed42c 100644
--- a/test/Decoder.ts
+++ b/test/Decoder.ts
@@ -4,27 +4,23 @@ import { pipe } from 'fp-ts/lib/pipeable'
 import * as D from '../src/Decoder'
 
 describe('Decoder', () => {
-  describe('decoder', () => {
+  it('of', () => {
+    const decoder = D.of(1)
+    assert.deepStrictEqual(decoder.decode('aaa'), E.right(1))
+  })
+
+  describe('functorDecoder', () => {
     it('map', () => {
       const decoder = pipe(
         D.string,
         D.map((s) => s.length)
       )
+      assert.deepStrictEqual(decoder.decode(null), E.left([D.tree('cannot decode null, should be string')]))
       assert.deepStrictEqual(decoder.decode('aaa'), E.right(3))
     })
+  })
 
-    it('of', () => {
-      const decoder = D.decoder.of(1)
-      assert.deepStrictEqual(decoder.decode(1), E.right(1))
-      assert.deepStrictEqual(decoder.decode('a'), E.right(1))
-    })
-
-    it('ap', () => {
-      const fab = D.decoder.of((s: string): number => s.length)
-      const fa = D.string
-      assert.deepStrictEqual(pipe(fab, D.ap(fa)).decode('aaa'), E.right(3))
-    })
-
+  describe('altDecoder', () => {
     it('alt', () => {
       const decoder = pipe(
         D.string,
@@ -33,11 +29,6 @@ describe('Decoder', () => {
       assert.deepStrictEqual(decoder.decode('a'), E.right('a'))
       assert.deepStrictEqual(decoder.decode(1), E.right('1'))
     })
-
-    it('zero', () => {
-      const decoder = D.decoder.zero()
-      assert.deepStrictEqual(decoder.decode(null), E.left([D.tree('cannot decode null, should be never')]))
-    })
   })
 
   describe('union', () => {
diff --git a/test/Encoder.ts b/test/Encoder.ts
index 4d1f066..77643ed 100644
--- a/test/Encoder.ts
+++ b/test/Encoder.ts
@@ -1,11 +1,110 @@
 import * as assert from 'assert'
 import * as E from '../src/Encoder'
+import { pipe } from 'fp-ts/lib/pipeable'
+
+const NumberToString: E.Encoder<string, number> = {
+  encode: String
+}
+
+const BooleanToNumber: E.Encoder<number, boolean> = {
+  encode: (b) => (b ? 1 : 0)
+}
 
 describe('Encoder', () => {
-  describe('encoder', () => {
-    it('contramap', () => {
-      const encoder = E.encoder.contramap(E.encoder.number, (s: string) => s.length)
-      assert.deepStrictEqual(encoder.encode('aaa'), 3)
+  it('contramap', () => {
+    const encoder = E.contramap((s: string) => s.length)(NumberToString)
+    assert.deepStrictEqual(encoder.encode('aaa'), '3')
+  })
+
+  it('compose', () => {
+    const encoder = pipe(BooleanToNumber, E.compose(NumberToString))
+    assert.deepStrictEqual(encoder.encode(true), '1')
+  })
+
+  it('nullable', () => {
+    const encoder = E.nullable(NumberToString)
+    assert.deepStrictEqual(encoder.encode(1), '1')
+    assert.deepStrictEqual(encoder.encode(null), null)
+  })
+
+  it('type', () => {
+    const encoder = E.type({ a: NumberToString, b: BooleanToNumber })
+    assert.deepStrictEqual(encoder.encode({ a: 1, b: true }), { a: '1', b: 1 })
+  })
+
+  it('partial', () => {
+    const encoder = E.partial({ a: NumberToString, b: BooleanToNumber })
+    assert.deepStrictEqual(encoder.encode({ a: 1, b: true }), { a: '1', b: 1 })
+    assert.deepStrictEqual(encoder.encode({ a: 1 }), { a: '1' })
+    assert.deepStrictEqual(encoder.encode({ a: 1, b: undefined }), { a: '1', b: undefined })
+    assert.deepStrictEqual(encoder.encode({ b: true }), { b: 1 })
+    assert.deepStrictEqual(encoder.encode({}), {})
+  })
+
+  it('record', () => {
+    const encoder = E.record(NumberToString)
+    assert.deepStrictEqual(encoder.encode({ a: 1, b: 2 }), { a: '1', b: '2' })
+  })
+
+  it('array', () => {
+    const encoder = E.array(NumberToString)
+    assert.deepStrictEqual(encoder.encode([1, 2]), ['1', '2'])
+  })
+
+  it('tuple', () => {
+    const encoder = E.tuple(NumberToString, BooleanToNumber)
+    assert.deepStrictEqual(encoder.encode([3, true]), ['3', 1])
+  })
+
+  it('intersection', () => {
+    const encoder = E.intersection(E.type({ a: NumberToString }), E.type({ b: BooleanToNumber }))
+    assert.deepStrictEqual(encoder.encode({ a: 1, b: true }), { a: '1', b: 1 })
+  })
+
+  it('sum', () => {
+    const S1 = E.type({ _tag: E.id<'A'>(), a: NumberToString })
+    const S2 = E.type({ _tag: E.id<'B'>(), b: BooleanToNumber })
+    const sum = E.sum('_tag')
+    const encoder = sum({ A: S1, B: S2 })
+    assert.deepStrictEqual(encoder.encode({ _tag: 'A', a: 1 }), { _tag: 'A', a: '1' })
+    assert.deepStrictEqual(encoder.encode({ _tag: 'B', b: true }), { _tag: 'B', b: 1 })
+  })
+
+  it('lazy', () => {
+    interface A {
+      a: number
+      bs: Array<B>
+    }
+    interface AOut {
+      a: string
+      bs: Array<BOut>
+    }
+    interface B {
+      b: boolean
+      as: Array<A>
+    }
+    interface BOut {
+      b: number
+      as: Array<AOut>
+    }
+    const A: E.Encoder<AOut, A> = E.lazy(() =>
+      E.type({
+        a: NumberToString,
+        bs: E.array(B)
+      })
+    )
+
+    const B: E.Encoder<BOut, B> = E.lazy(() =>
+      E.type({
+        b: BooleanToNumber,
+        as: E.array(A)
+      })
+    )
+    assert.deepStrictEqual(A.encode({ a: 1, bs: [] }), { a: '1', bs: [] })
+    assert.deepStrictEqual(A.encode({ a: 1, bs: [{ b: true, as: [] }] }), { a: '1', bs: [{ b: 1, as: [] }] })
+    assert.deepStrictEqual(A.encode({ a: 1, bs: [{ b: true, as: [{ a: 2, bs: [] }] }] }), {
+      a: '1',
+      bs: [{ b: 1, as: [{ a: '2', bs: [] }] }]
     })
   })
 })
diff --git a/test/Eq.ts b/test/Eq.ts
index 525fa33..285a7b5 100644
--- a/test/Eq.ts
+++ b/test/Eq.ts
@@ -4,7 +4,7 @@ import { Eq } from 'fp-ts/lib/Eq'
 
 describe('Eq', () => {
   it('literal', () => {
-    const eq = E.eq.literal('a', null)
+    const eq = E.schemableEq.literal('a', null)
     assert.deepStrictEqual(eq.equals('a', 'a'), true)
     assert.deepStrictEqual(eq.equals(null, null), true)
     assert.deepStrictEqual(eq.equals('a', null), false)
@@ -55,7 +55,7 @@ describe('Eq', () => {
       b: Array<A>
     }
 
-    const eq: Eq<A> = E.eq.lazy('A', () =>
+    const eq: Eq<A> = E.schemableEq.lazy('A', () =>
       E.type({
         a: E.number,
         b: E.array(eq)
@@ -70,8 +70,8 @@ describe('Eq', () => {
   it('sum', () => {
     const sum = E.sum('_tag')
     const eq = sum({
-      A: E.type({ _tag: E.eq.literal('A'), a: E.string }),
-      B: E.type({ _tag: E.eq.literal('B'), b: E.number })
+      A: E.type({ _tag: E.schemableEq.literal('A'), a: E.string }),
+      B: E.type({ _tag: E.schemableEq.literal('B'), b: E.number })
     })
     assert.strictEqual(eq.equals({ _tag: 'A', a: 'a' }, { _tag: 'A', a: 'a' }), true)
     assert.strictEqual(eq.equals({ _tag: 'B', b: 1 }, { _tag: 'B', b: 1 }), true)
@@ -79,4 +79,16 @@ describe('Eq', () => {
     assert.strictEqual(eq.equals({ _tag: 'A', a: 'a' }, { _tag: 'A', a: 'b' }), false)
     assert.strictEqual(eq.equals({ _tag: 'B', b: 1 }, { _tag: 'B', b: 2 }), false)
   })
+
+  it('refinement', () => {
+    interface NonEmptyStringBrand {
+      readonly NonEmptyString: unique symbol
+    }
+    type NonEmptyString = string & NonEmptyStringBrand
+    const eq = E.schemableEq.refinement(E.string, (s): s is NonEmptyString => s.length > 0, 'NonEmptyString')
+    const a: NonEmptyString = 'a' as any
+    const b: NonEmptyString = 'b' as any
+    assert.deepStrictEqual(eq.equals(a, a), true)
+    assert.deepStrictEqual(eq.equals(a, b), false)
+  })
 })
diff --git a/test/Guard.ts b/test/Guard.ts
index 7d89a21..4c910e7 100644
--- a/test/Guard.ts
+++ b/test/Guard.ts
@@ -187,7 +187,7 @@ describe('Guard', () => {
       b: Array<A>
     }
 
-    const guard: G.Guard<A> = G.guard.lazy('A', () =>
+    const guard: G.Guard<A> = G.schemableGuard.lazy('A', () =>
       G.type({
         a: G.number,
         b: G.array(guard)
diff --git a/test/JsonEncoder.ts b/test/JsonEncoder.ts
new file mode 100644
index 0000000..044f1de
--- /dev/null
+++ b/test/JsonEncoder.ts
@@ -0,0 +1,11 @@
+import * as assert from 'assert'
+import * as JE from '../src/JsonEncoder'
+
+describe('Encoder', () => {
+  describe('contravariantJsonEncoder', () => {
+    it('contramap', () => {
+      const encoder = JE.contravariantJsonEncoder.contramap(JE.schemableJsonEncoder.number, (s: string) => s.length)
+      assert.deepStrictEqual(encoder.encode('aaa'), 3)
+    })
+  })
+})
diff --git a/test/Schema.ts b/test/Schema.ts
index 2db29ba..1f95324 100644
--- a/test/Schema.ts
+++ b/test/Schema.ts
@@ -1,13 +1,12 @@
-import { isRight, right } from 'fp-ts/lib/Either'
-import { Schema, make } from '../src/Schema'
-import * as G from '../src/Guard'
-import * as D from '../src/Decoder'
-import * as E from '../src/Encoder'
-import * as C from '../src/Codec'
+import * as assert from 'assert'
+import * as fc from 'fast-check'
+import { isRight } from 'fp-ts/lib/Either'
+import * as JC from '../src/JsonCodec'
 import * as Eq from '../src/Eq'
+import * as G from '../src/Guard'
+import { Schema, interpreter, make } from '../src/Schema'
 import * as A from './Arbitrary'
-import * as fc from 'fast-check'
-import * as assert from 'assert'
+import * as JE from '../src/JsonEncoder'
 
 function isDeepStrictEqual(actual: unknown, expected: unknown): boolean {
   try {
@@ -19,50 +18,29 @@ function isDeepStrictEqual(actual: unknown, expected: unknown): boolean {
 }
 
 function check<A>(schema: Schema<A>): void {
-  const arb = schema(A.arbitrary)
-  const codec = schema(C.codec)
-  const guard = schema(G.guard)
-  const eq = schema(Eq.eq)
+  const arb = interpreter(A.schemableArbitrary)(schema)
+  const codec = interpreter(JC.schemableJsonCodec)(schema)
+  const guard = interpreter(G.schemableGuard)(schema)
+  const eq = interpreter(Eq.schemableEq)(schema)
+  const encoder = interpreter(JE.schemableJsonEncoder)(schema)
   // decoders, guards and eqs should be aligned
   fc.assert(fc.property(arb, (a) => isRight(codec.decode(a)) && guard.is(a) && eq.equals(a, a)))
   // laws
-  fc.assert(fc.property(arb, (a) => isRight(codec.decode(codec.encode(a)))))
+  // 1.
+  fc.assert(fc.property(arb, (a) => isRight(codec.decode(codec.encode(a))) && isRight(codec.decode(encoder.encode(a)))))
+  // 2.
   fc.assert(
     fc.property(arb, (u) => {
       const a = codec.decode(u)
       if (isRight(a)) {
         const o = a.right
-        return isDeepStrictEqual(codec.encode(o), u) && eq.equals(o, u)
+        return isDeepStrictEqual(codec.encode(o), u) && isDeepStrictEqual(encoder.encode(o), u) && eq.equals(o, u)
       }
       return false
     })
   )
 }
-describe('Schemable', () => {
-  const Person = make((S) =>
-    S.type({
-      name: S.string,
-      age: S.number
-    })
-  )
-
-  it('should handle decoders', () => {
-    assert.deepStrictEqual(Person(D.decoder).decode({ name: 'name', age: 46 }), right({ name: 'name', age: 46 }))
-  })
-
-  it('should handle encoders', () => {
-    assert.deepStrictEqual(Person(E.encoder).encode({ name: 'name', age: 46 }), { name: 'name', age: 46 })
-  })
-
-  it('should handle guards', () => {
-    assert.deepStrictEqual(Person(G.guard).is({ name: 'name', age: 46 }), true)
-  })
-
-  it('should handle codecs', () => {
-    assert.deepStrictEqual(Person(C.codec).decode({ name: 'name', age: 46 }), right({ name: 'name', age: 46 }))
-    assert.deepStrictEqual(Person(E.encoder).encode({ name: 'name', age: 46 }), { name: 'name', age: 46 })
-  })
-
+describe('Schema', () => {
   it('string', () => {
     check(make((S) => S.string))
   })
@@ -75,14 +53,6 @@ describe('Schemable', () => {
     check(make((S) => S.boolean))
   })
 
-  it('UnknownArray', () => {
-    check(make((S) => S.UnknownArray))
-  })
-
-  it('UnknownRecord', () => {
-    check(make((S) => S.UnknownRecord))
-  })
-
   it('literal', () => {
     check(make((S) => S.literal('a')))
     check(make((S) => S.literal('a', 1)))
@@ -151,4 +121,12 @@ describe('Schemable', () => {
     )
     check(schema)
   })
+
+  // it('UnknownArray', () => {
+  //   check(make((S) => S.UnknownArray))
+  // })
+
+  // it('UnknownRecord', () => {
+  //   check(make((S) => S.UnknownRecord))
+  // })
 })
diff --git a/test/Type.ts b/test/Type.ts
new file mode 100644
index 0000000..079b133
--- /dev/null
+++ b/test/Type.ts
@@ -0,0 +1,203 @@
+import * as assert from 'assert'
+import * as fc from 'fast-check'
+import { isRight } from 'fp-ts/lib/Either'
+import { Kind, URIS, HKT } from 'fp-ts/lib/HKT'
+import * as t from '../src'
+import * as D from '../src/Decoder'
+import * as G from '../src/Guard'
+import {
+  memoize,
+  Schemable,
+  WithUnion,
+  Schemable1,
+  WithUnion1,
+  WithUnknownContainers,
+  WithUnknownContainers1
+} from '../src/Schemable'
+import * as T from '../src/Type'
+import * as A from './Arbitrary'
+
+interface Schema<A> {
+  <S>(S: Schemable<S> & WithUnknownContainers<S> & WithUnion<S>): HKT<S, A>
+}
+
+function make<A>(f: Schema<A>): Schema<A> {
+  return memoize(f)
+}
+
+function interpreter<S extends URIS>(
+  S: Schemable1<S> & WithUnknownContainers1<S> & WithUnion1<S>
+): <A>(schema: Schema<A>) => Kind<S, A> {
+  return (schema: any) => schema(S)
+}
+
+function check<A>(schema: Schema<A>, type: t.Type<A>): void {
+  const arb = interpreter(A.schemableArbitrary)(schema)
+  const decoder = interpreter(D.schemableDecoder)(schema)
+  const guard = interpreter(G.schemableGuard)(schema)
+  const itype = interpreter(T.schemableType)(schema)
+  // decoder and type should be aligned
+  fc.assert(fc.property(arb, (a) => isRight(decoder.decode(a)) === isRight(type.decode(a))))
+  // interpreted type and type should be aligned
+  fc.assert(fc.property(arb, (a) => isRight(itype.decode(a)) === isRight(type.decode(a))))
+  // guard and `Type`'s `is` should be aligned
+  fc.assert(fc.property(arb, (a) => guard.is(a) === type.is(a)))
+}
+
+describe('Type', () => {
+  it('string', () => {
+    check(
+      make((S) => S.string),
+      t.string
+    )
+  })
+
+  it('number', () => {
+    check(
+      make((S) => S.number),
+      t.number
+    )
+  })
+
+  it('boolean', () => {
+    check(
+      make((S) => S.boolean),
+      t.boolean
+    )
+  })
+
+  it('UnknownArray', () => {
+    check(
+      make((S) => S.UnknownArray),
+      t.UnknownArray
+    )
+  })
+
+  it('UnknownRecord', () => {
+    check(
+      make((S) => S.UnknownRecord),
+      t.UnknownRecord
+    )
+  })
+
+  it('literal', () => {
+    check(
+      make((S) => S.literal('a', 'b')),
+      t.keyof({ a: null, b: null })
+    )
+  })
+
+  it('nullable', () => {
+    check(
+      make((S) => S.nullable(S.string)),
+      t.union([t.null, t.string])
+    )
+  })
+
+  it('type', () => {
+    check(
+      make((S) =>
+        S.type({
+          name: S.string,
+          age: S.number
+        })
+      ),
+      t.type({
+        name: t.string,
+        age: t.number
+      })
+    )
+  })
+
+  it('partial', () => {
+    check(
+      make((S) =>
+        S.partial({
+          name: S.string,
+          age: S.number
+        })
+      ),
+      t.partial({
+        name: t.string,
+        age: t.number
+      })
+    )
+  })
+
+  it('record', () => {
+    check(
+      make((S) => S.record(S.string)),
+      t.record(t.string, t.string)
+    )
+  })
+
+  it('array', () => {
+    check(
+      make((S) => S.array(S.string)),
+      t.array(t.string)
+    )
+  })
+
+  it('tuple', () => {
+    check(
+      make((S) => S.tuple(S.string)),
+      t.tuple([t.string])
+    )
+    check(
+      make((S) => S.tuple(S.string, S.number)),
+      t.tuple([t.string, t.number])
+    )
+  })
+
+  it('intersection', () => {
+    check(
+      make((S) => S.intersection(S.type({ a: S.string }), S.type({ b: S.number }))),
+      t.intersection([t.type({ a: t.string }), t.type({ b: t.number })])
+    )
+  })
+
+  it('sum', () => {
+    check(
+      make((S) =>
+        S.sum('_tag')({
+          A: S.type({ _tag: S.literal('A'), a: S.string }),
+          B: S.type({ _tag: S.literal('B'), b: S.number })
+        })
+      ),
+      t.union([t.type({ _tag: t.literal('A'), a: t.string }), t.type({ _tag: t.literal('B'), b: t.number })])
+    )
+  })
+
+  it('lazy', () => {
+    interface A {
+      a: string
+      b?: A
+      c?: number
+    }
+
+    const schema: Schema<A> = make((S) =>
+      S.lazy('A', () => S.intersection(S.type({ a: S.string }), S.partial({ b: schema(S), c: S.number })))
+    )
+    const type: t.Type<A> = t.recursion('A', () =>
+      t.intersection([t.type({ a: t.string }), t.partial({ b: type, c: t.number })])
+    )
+    check(schema, type)
+  })
+
+  it('union', () => {
+    check(
+      make((S) => S.union(S.string, S.number)),
+      t.union([t.string, t.number])
+    )
+  })
+
+  it('refinement', () => {
+    interface NonEmptyStringBrand {
+      readonly NonEmptyString: unique symbol
+    }
+    type NonEmptyString = string & NonEmptyStringBrand
+    const type = T.refinement(T.string, (s): s is NonEmptyString => s.length > 0, 'NonEmptyString')
+    assert.deepStrictEqual(isRight(type.decode('a')), true)
+    assert.deepStrictEqual(isRight(type.decode('')), false)
+  })
+})