Repository: gcanti/io-ts
Pull request #334 (2019-06-18 16:55:14)

make `io-ts` compatible with both `fp-ts@1.x`, `fp-ts@2.x`


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index d93fac9..7ec4778 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -14,6 +14,11 @@
 **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.
 
+# 1.10.0
+
+- **New Feature**
+  - make `io-ts` compatible with both `fp-ts@1.x`, `fp-ts@2.x` (@gcanti)
+
 # 1.9.0
 
 - **New Feature**
diff --git a/docs/modules/index.ts.md b/docs/modules/index.ts.md
index 02224b5..6d42e20 100644
--- a/docs/modules/index.ts.md
+++ b/docs/modules/index.ts.md
@@ -123,11 +123,13 @@ parent: Modules
 - [~~any~~ (constant)](#any-constant)
 - [boolean (constant)](#boolean-constant)
 - [~~dictionary~~ (constant)](#dictionary-constant)
+- [failures (constant)](#failures-constant)
 - [~~never~~ (constant)](#never-constant)
 - [nullType (constant)](#nulltype-constant)
 - [number (constant)](#number-constant)
 - [~~object~~ (constant)](#object-constant)
 - [string (constant)](#string-constant)
+- [success (constant)](#success-constant)
 - [unknown (constant)](#unknown-constant)
 - [voidType (constant)](#voidtype-constant)
 - [~~alias~~ (function)](#alias-function)
@@ -137,7 +139,6 @@ parent: Modules
 - [~~clean~~ (function)](#clean-function)
 - [exact (function)](#exact-function)
 - [failure (function)](#failure-function)
-- [failures (function)](#failures-function)
 - [getContextEntry (function)](#getcontextentry-function)
 - [~~getDefaultContext~~ (function)](#getdefaultcontext-function)
 - [getFunctionName (function)](#getfunctionname-function)
@@ -153,7 +154,6 @@ parent: Modules
 - [recursion (function)](#recursion-function)
 - [~~refinement~~ (function)](#refinement-function)
 - [strict (function)](#strict-function)
-- [success (function)](#success-function)
 - [~~taggedUnion~~ (function)](#taggedunion-function)
 - [tuple (function)](#tuple-function)
 - [type (function)](#type-function)
@@ -1673,6 +1673,16 @@ export const dictionary: typeof record = ...
 
 Added in v1.0.0
 
+# failures (constant)
+
+**Signature**
+
+```ts
+export const failures: <T>(errors: Errors) => Validation<T> = ...
+```
+
+Added in v1.0.0
+
 # ~~never~~ (constant)
 
 **Signature**
@@ -1723,6 +1733,16 @@ export const string: StringC = ...
 
 Added in v1.0.0
 
+# success (constant)
+
+**Signature**
+
+```ts
+export const success: <T>(value: T) => Validation<T> = ...
+```
+
+Added in v1.0.0
+
 # unknown (constant)
 
 **Signature**
@@ -1806,7 +1826,8 @@ export const array = <C extends Mixed>(codec: C, name: string = `Array<${codec.n
   new ArrayType(
     name,
     (u): u is Array<TypeOf<C>> => UnknownArray.is(u) && u.every(codec.is),
-    (u, c) => ...
+    (u, c) =>
+      chain(UnknownArray.validate(u, c), us => ...
 ```
 
 Added in v1.0.0
@@ -1859,16 +1880,6 @@ export const failure = <T>(value: unknown, context: Context, message?: string):
 
 Added in v1.0.0
 
-# failures (function)
-
-**Signature**
-
-```ts
-export const failures = <T>(errors: Errors): Validation<T> => ...
-```
-
-Added in v1.0.0
-
 # getContextEntry (function)
 
 **Signature**
@@ -1984,12 +1995,7 @@ Added in v1.0.0
 **Signature**
 
 ```ts
-export const readonly = <C extends Mixed>(codec: C, name: string = `Readonly<${codec.name}>`): ReadonlyC<C> =>
-  new ReadonlyType(
-    name,
-    codec.is,
-    (u, c) =>
-      codec.validate(u, c).map(x => ...
+export const readonly = <C extends Mixed>(codec: C, name: string = `Readonly<${codec.name}>`): ReadonlyC<C> => ...
 ```
 
 Added in v1.0.0
@@ -2063,16 +2069,6 @@ export const strict = <P extends Props>(props: P, name?: string): ExactC<TypeC<P
 
 Added in v1.0.0
 
-# success (function)
-
-**Signature**
-
-```ts
-export const success = <T>(value: T): Validation<T> => ...
-```
-
-Added in v1.0.0
-
 # ~~taggedUnion~~ (function)
 
 Use `union` instead
diff --git a/dtslint/ts3.1/index.ts b/dtslint/ts3.1/index.ts
index 4f0fcad..4e6ff57 100644
--- a/dtslint/ts3.1/index.ts
+++ b/dtslint/ts3.1/index.ts
@@ -1,4 +1,5 @@
 import * as t from '../../src'
+import { either } from 'fp-ts/lib/Either'
 
 //
 // helpers
@@ -17,7 +18,7 @@ export const NumberFromString = new t.Type<number, string, unknown>(
   'NumberFromString',
   t.number.is,
   (u, c) =>
-    t.string.validate(u, c).chain(s => {
+    either.chain(t.string.validate(u, c), s => {
       const n = parseFloat(s)
       return isNaN(n) ? t.failure(s, c) : t.success(n)
     }),
diff --git a/package-lock.json b/package-lock.json
index 5323ec3..0fae16d 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,6 +1,6 @@
 {
   "name": "io-ts",
-  "version": "1.8.6",
+  "version": "1.10.0",
   "lockfileVersion": 1,
   "requires": true,
   "dependencies": {
@@ -2419,9 +2419,10 @@
       "dev": true
     },
     "fp-ts": {
-      "version": "1.10.0",
-      "resolved": "https://registry.npmjs.org/fp-ts/-/fp-ts-1.10.0.tgz",
-      "integrity": "sha512-ZOdfxiFgjipHEQKMam06/+BVneM/UKQDhoXAH2I6VvwqY4p0awswQcDgopvfE/jPeR7Aigv1aIv8U5awSXSw8A=="
+      "version": "2.0.0-rc.6",
+      "resolved": "https://registry.npmjs.org/fp-ts/-/fp-ts-2.0.0-rc.6.tgz",
+      "integrity": "sha512-2IQNyze7i7/yazRV8tM/PEvPQsZ7kSoZZLrKvpDxtQ1v+x3FzBfXzPcrTxdHE/wMXrlhQXaHNVmE0a0lxO83gg==",
+      "dev": true
     },
     "fragment-cache": {
       "version": "0.2.1",
diff --git a/package.json b/package.json
index f2db20f..4b4591f 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "io-ts",
-  "version": "1.9.0",
+  "version": "1.10.0",
   "description": "TypeScript compatible runtime type system for IO validation",
   "files": [
     "lib",
@@ -36,8 +36,8 @@
     "url": "https://github.com/gcanti/io-ts/issues"
   },
   "homepage": "https://github.com/gcanti/io-ts",
-  "dependencies": {
-    "fp-ts": "^1.0.0"
+  "peerDependencies": {
+    "fp-ts": "^1.0.0 || ^2.0.0-rc.6"
   },
   "devDependencies": {
     "@types/benchmark": "1.0.31",
@@ -47,6 +47,7 @@
     "docs-ts": "0.0.3",
     "doctoc": "^1.4.0",
     "dtslint": "github:gcanti/dtslint",
+    "fp-ts": "^2.0.0-rc.6",
     "jest": "^23.6.0",
     "mocha": "^5.2.0",
     "prettier": "^1.15.3",
diff --git a/perf/index.js b/perf/index.js
index 40bfda6..4d5495a 100644
--- a/perf/index.js
+++ b/perf/index.js
@@ -1,6 +1,11 @@
 var Benchmark = require('benchmark')
 var t = require('../lib/index')
 
+/*
+space-object (good) x 371,834 ops/sec ±1.06% (80 runs sampled)
+space-object (bad) x 376,063 ops/sec ±0.76% (89 runs sampled)
+*/
+
 const suite = new Benchmark.Suite()
 
 const Vector = t.tuple([t.number, t.number, t.number])
diff --git a/src/PathReporter.ts b/src/PathReporter.ts
index e9d5517..8f74fba 100644
--- a/src/PathReporter.ts
+++ b/src/PathReporter.ts
@@ -1,5 +1,5 @@
 import { Reporter } from './Reporter'
-import { Context, getFunctionName, ValidationError } from './index'
+import { Context, getFunctionName, ValidationError, fold } from '.'
 
 function stringify(v: any): string {
   if (typeof v === 'function') {
@@ -42,5 +42,5 @@ export function success(): Array<string> {
  * @since 1.0.0
  */
 export const PathReporter: Reporter<Array<string>> = {
-  report: validation => validation.fold(failure, success)
+  report: validation => fold(validation, failure, success)
 }
diff --git a/src/ThrowReporter.ts b/src/ThrowReporter.ts
index b4723a0..1d310f2 100644
--- a/src/ThrowReporter.ts
+++ b/src/ThrowReporter.ts
@@ -3,15 +3,21 @@
  */
 import { Reporter } from './Reporter'
 import { PathReporter } from './PathReporter'
+import { fold } from '.'
 
 /**
  * @since 1.0.0
  * @deprecated
  */
 export const ThrowReporter: Reporter<void> = {
-  report: validation => {
-    if (validation.isLeft()) {
-      throw PathReporter.report(validation).join('\n')
-    }
-  }
+  report: validation =>
+    fold(
+      validation,
+      () => {
+        throw PathReporter.report(validation).join('\n')
+      },
+      () => {
+        return
+      }
+    )
 }
diff --git a/src/index.ts b/src/index.ts
index 8f3fa18..59d56aa 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -1,6 +1,31 @@
-import { Either, Left, Right } from 'fp-ts/lib/Either'
+import { Either, either, left, right } from 'fp-ts/lib/Either'
 import { Predicate, Refinement } from 'fp-ts/lib/function'
 
+const map = either.map
+const chain = either.chain
+
+/**
+ * @internal
+ */
+export function fold<L, A, R>(ma: Either<L, A>, onLeft: (l: L) => R, onRight: (a: A) => R): R {
+  const e: any = ma
+  return e._tag === 'Left'
+    ? onLeft(
+        e.hasOwnProperty('left')
+          ? /* istanbul ignore next */
+            e.left
+          : /* istanbul ignore next */
+            e.value
+      )
+    : onRight(
+        e.hasOwnProperty('right')
+          ? /* istanbul ignore next */
+            e.right
+          : /* istanbul ignore next */
+            e.value
+      )
+}
+
 /**
  * @since 1.0.0
  */
@@ -126,13 +151,7 @@ export class Type<A, O = A, I = unknown> implements Decoder<I, A>, Encoder<A, O>
     return new Type(
       name,
       ab.is,
-      (i, c) => {
-        const validation = this.validate(i, c)
-        if (validation.isLeft()) {
-          return validation as any
-        }
-        return ab.validate(validation.value, c)
-      },
+      (i, c) => chain(this.validate(i, c), a => ab.validate(a, c)),
       this.encode === identity && ab.encode === identity ? (identity as any) : b => this.encode(ab.encode(b))
     )
   }
@@ -180,7 +199,7 @@ export const appendContext = (c: Context, key: string, decoder: Decoder<any, any
 /**
  * @since 1.0.0
  */
-export const failures = <T>(errors: Errors): Validation<T> => new Left(errors)
+export const failures: <T>(errors: Errors) => Validation<T> = left
 
 /**
  * @since 1.0.0
@@ -191,7 +210,7 @@ export const failure = <T>(value: unknown, context: Context, message?: string):
 /**
  * @since 1.0.0
  */
-export const success = <T>(value: T): Validation<T> => new Right<Errors, T>(value)
+export const success: <T>(value: T) => Validation<T> = right
 
 const pushAll = <A>(xs: Array<A>, ys: Array<A>): void => {
   const l = ys.length
@@ -655,32 +674,28 @@ export const array = <C extends Mixed>(codec: C, name: string = `Array<${codec.n
   new ArrayType(
     name,
     (u): u is Array<TypeOf<C>> => UnknownArray.is(u) && u.every(codec.is),
-    (u, c) => {
-      const unknownArrayValidation = UnknownArray.validate(u, c)
-      if (unknownArrayValidation.isLeft()) {
-        return unknownArrayValidation
-      }
-      const us = unknownArrayValidation.value
-      const len = us.length
-      let as: Array<TypeOf<C>> = us
-      const errors: Errors = []
-      for (let i = 0; i < len; i++) {
-        const ui = us[i]
-        const validation = codec.validate(ui, appendContext(c, String(i), codec, ui))
-        if (validation.isLeft()) {
-          pushAll(errors, validation.value)
-        } else {
-          const ai = validation.value
-          if (ai !== ui) {
-            if (as === us) {
-              as = us.slice()
+    (u, c) =>
+      chain(UnknownArray.validate(u, c), us => {
+        const len = us.length
+        let as: Array<TypeOf<C>> = us
+        const errors: Errors = []
+        for (let i = 0; i < len; i++) {
+          const ui = us[i]
+          fold(
+            codec.validate(ui, appendContext(c, String(i), codec, ui)),
+            e => pushAll(errors, e),
+            ai => {
+              if (ai !== ui) {
+                if (as === us) {
+                  as = us.slice()
+                }
+                as[i] = ai
+              }
             }
-            as[i] = ai
-          }
+          )
         }
-      }
-      return errors.length > 0 ? failures(errors) : success(as)
-    },
+        return errors.length > 0 ? failures(errors) : success(as)
+      }),
     codec.encode === identity ? identity : a => a.map(codec.encode),
     codec
   )
@@ -771,40 +786,36 @@ export const type = <P extends Props>(props: P, name: string = getInterfaceTypeN
       }
       return true
     },
-    (u, c) => {
-      const unknownRecordValidation = UnknownRecord.validate(u, c)
-      if (unknownRecordValidation.isLeft()) {
-        return unknownRecordValidation
-      }
-      const o = unknownRecordValidation.value
-      let a = o
-      const errors: Errors = []
-      for (let i = 0; i < len; i++) {
-        const k = keys[i]
-        if (!hasOwnProperty.call(a, k)) {
-          if (a === o) {
-            a = { ...o }
-          }
-          a[k] = a[k]
-        }
-        const ak = a[k]
-        const type = types[i]
-        const validation = type.validate(ak, appendContext(c, k, type, ak))
-        if (validation.isLeft()) {
-          pushAll(errors, validation.value)
-        } else {
-          const vak = validation.value
-          if (vak !== ak) {
-            /* istanbul ignore next */
+    (u, c) =>
+      chain(UnknownRecord.validate(u, c), o => {
+        let a = o
+        const errors: Errors = []
+        for (let i = 0; i < len; i++) {
+          const k = keys[i]
+          if (!hasOwnProperty.call(a, k)) {
             if (a === o) {
               a = { ...o }
             }
-            a[k] = vak
+            a[k] = a[k]
           }
+          const ak = a[k]
+          const type = types[i]
+          fold(
+            type.validate(ak, appendContext(c, k, type, ak)),
+            e => pushAll(errors, e),
+            vak => {
+              if (vak !== ak) {
+                /* istanbul ignore next */
+                if (a === o) {
+                  a = { ...o }
+                }
+                a[k] = vak
+              }
+            }
+          )
         }
-      }
-      return errors.length > 0 ? failures(errors) : success(a as any)
-    },
+        return errors.length > 0 ? failures(errors) : success(a as any)
+      }),
     useIdentity(types)
       ? identity
       : a => {
@@ -883,34 +894,34 @@ export const partial = <P extends Props>(
       }
       return true
     },
-    (u, c) => {
-      const unknownRecordValidation = UnknownRecord.validate(u, c)
-      if (unknownRecordValidation.isLeft()) {
-        return unknownRecordValidation
-      }
-      const o = unknownRecordValidation.value
-      let a = o
-      const errors: Errors = []
-      for (let i = 0; i < len; i++) {
-        const k = keys[i]
-        const ak = a[k]
-        const type = props[k]
-        const validation = type.validate(ak, appendContext(c, k, type, ak))
-        if (validation.isLeft() && ak !== undefined) {
-          pushAll(errors, validation.value)
-        } else if (validation.isRight()) {
-          const vak = validation.value
-          if (vak !== ak) {
-            /* istanbul ignore next */
-            if (a === o) {
-              a = { ...o }
+    (u, c) =>
+      chain(UnknownRecord.validate(u, c), o => {
+        let a = o
+        const errors: Errors = []
+        for (let i = 0; i < len; i++) {
+          const k = keys[i]
+          const ak = a[k]
+          const type = props[k]
+          fold(
+            type.validate(ak, appendContext(c, k, type, ak)),
+            e => {
+              if (ak !== undefined) {
+                pushAll(errors, e)
+              }
+            },
+            vak => {
+              if (vak !== ak) {
+                /* istanbul ignore next */
+                if (a === o) {
+                  a = { ...o }
+                }
+                a[k] = vak
+              }
             }
-            a[k] = vak
-          }
+          )
         }
-      }
-      return errors.length > 0 ? failures(errors) : success(a as any)
-    },
+        return errors.length > 0 ? failures(errors) : success(a as any)
+      }),
     useIdentity(types)
       ? identity
       : a => {
@@ -982,42 +993,38 @@ export const record = <D extends Mixed, C extends Mixed>(
       }
       return Object.keys(u).every(k => domain.is(k) && codomain.is(u[k]))
     },
-    (u, c) => {
-      const unknownRecordValidation = UnknownRecord.validate(u, c)
-      if (unknownRecordValidation.isLeft()) {
-        return unknownRecordValidation
-      }
-      const o = unknownRecordValidation.value
-      if (!isUnknownCodec(codomain) && !isAnyCodec(codomain) && !isObject(o)) {
-        return failure(u, c)
-      }
-      const a: { [key: string]: any } = {}
-      const errors: Errors = []
-      const keys = Object.keys(o)
-      const len = keys.length
-      let changed: boolean = false
-      for (let i = 0; i < len; i++) {
-        let k = keys[i]
-        const ok = o[k]
-        const domainValidation = domain.validate(k, appendContext(c, k, domain, k))
-        if (domainValidation.isLeft()) {
-          pushAll(errors, domainValidation.value)
-        } else {
-          const vk = domainValidation.value
-          changed = changed || vk !== k
-          k = vk
-          const codomainValidation = codomain.validate(ok, appendContext(c, k, codomain, ok))
-          if (codomainValidation.isLeft()) {
-            pushAll(errors, codomainValidation.value)
-          } else {
-            const vok = codomainValidation.value
-            changed = changed || vok !== ok
-            a[k] = vok
-          }
+    (u, c) =>
+      chain(UnknownRecord.validate(u, c), o => {
+        if (!isUnknownCodec(codomain) && !isAnyCodec(codomain) && !isObject(o)) {
+          return failure(u, c)
         }
-      }
-      return errors.length > 0 ? failures(errors) : success((changed ? a : o) as any)
-    },
+        const a: { [key: string]: any } = {}
+        const errors: Errors = []
+        const keys = Object.keys(o)
+        const len = keys.length
+        let changed: boolean = false
+        for (let i = 0; i < len; i++) {
+          let k = keys[i]
+          const ok = o[k]
+          fold(
+            domain.validate(k, appendContext(c, k, domain, k)),
+            e => pushAll(errors, e),
+            vk => {
+              changed = changed || vk !== k
+              k = vk
+              fold(
+                codomain.validate(ok, appendContext(c, k, codomain, ok)),
+                e => pushAll(errors, e),
+                vok => {
+                  changed = changed || vok !== ok
+                  a[k] = vok
+                }
+              )
+            }
+          )
+        }
+        return errors.length > 0 ? failures(errors) : success((changed ? a : o) as any)
+      }),
     domain.encode === identity && codomain.encode === identity
       ? identity
       : a => {
@@ -1090,19 +1097,15 @@ export const union = <CS extends [Mixed, Mixed, ...Array<Mixed>]>(
         const i = find(u[tag])
         return i !== undefined ? codecs[i].is(u) : false
       },
-      (u, c) => {
-        const dictionaryResult = UnknownRecord.validate(u, c)
-        if (dictionaryResult.isLeft()) {
-          return dictionaryResult
-        }
-        const r = dictionaryResult.value
-        const i = find(r[tag])
-        if (i === undefined) {
-          return failure(u, c)
-        }
-        const codec = codecs[i]
-        return codec.validate(r, appendContext(c, String(i), codec, r))
-      },
+      (u, c) =>
+        chain(UnknownRecord.validate(u, c), r => {
+          const i = find(r[tag])
+          if (i === undefined) {
+            return failure(u, c)
+          }
+          const codec = codecs[i]
+          return codec.validate(r, appendContext(c, String(i), codec, r))
+        }),
       useIdentity(codecs)
         ? identity
         : a => {
@@ -1125,11 +1128,14 @@ export const union = <CS extends [Mixed, Mixed, ...Array<Mixed>]>(
         const errors: Errors = []
         for (let i = 0; i < codecs.length; i++) {
           const codec = codecs[i]
-          const validation = codec.validate(u, appendContext(c, String(i), codec, u))
-          if (validation.isRight()) {
-            return validation
+          const r = fold<Errors, any, Either<Errors, any> | void>(
+            codec.validate(u, appendContext(c, String(i), codec, u)),
+            e => pushAll(errors, e),
+            success
+          )
+          if (r !== undefined) {
+            return r
           }
-          pushAll(errors, validation.value)
         }
         return failures(errors)
       },
@@ -1243,12 +1249,7 @@ export function intersection<CS extends [Mixed, Mixed, ...Array<Mixed>]>(
           const errors: Errors = []
           for (let i = 0; i < len; i++) {
             const codec = codecs[i]
-            const validation = codec.validate(u, appendContext(c, String(i), codec, u))
-            if (validation.isLeft()) {
-              pushAll(errors, validation.value)
-            } else {
-              us.push(validation.value)
-            }
+            fold(codec.validate(u, appendContext(c, String(i), codec, u)), e => pushAll(errors, e), a => us.push(a))
           }
           return errors.length > 0 ? failures(errors) : success(mergeAll(u, us))
         },
@@ -1329,33 +1330,29 @@ export function tuple<CS extends [Mixed, ...Array<Mixed>]>(
   return new TupleType(
     name,
     (u): u is any => UnknownArray.is(u) && u.length === len && codecs.every((type, i) => type.is(u[i])),
-    (u, c) => {
-      const unknownArrayValidation = UnknownArray.validate(u, c)
-      if (unknownArrayValidation.isLeft()) {
-        return unknownArrayValidation
-      }
-      const us = unknownArrayValidation.value
-      let as: Array<any> = us.length > len ? us.slice(0, len) : us // strip additional components
-      const errors: Errors = []
-      for (let i = 0; i < len; i++) {
-        const a = us[i]
-        const type = codecs[i]
-        const validation = type.validate(a, appendContext(c, String(i), type, a))
-        if (validation.isLeft()) {
-          pushAll(errors, validation.value)
-        } else {
-          const va = validation.value
-          if (va !== a) {
-            /* istanbul ignore next */
-            if (as === us) {
-              as = us.slice()
+    (u, c) =>
+      chain(UnknownArray.validate(u, c), us => {
+        let as: Array<any> = us.length > len ? us.slice(0, len) : us // strip additional components
+        const errors: Errors = []
+        for (let i = 0; i < len; i++) {
+          const a = us[i]
+          const type = codecs[i]
+          fold(
+            type.validate(a, appendContext(c, String(i), type, a)),
+            e => pushAll(errors, e),
+            va => {
+              if (va !== a) {
+                /* istanbul ignore next */
+                if (as === us) {
+                  as = us.slice()
+                }
+                as[i] = va
+              }
             }
-            as[i] = va
-          }
+          )
         }
-      }
-      return errors.length > 0 ? failures(errors) : success(as)
-    },
+        return errors.length > 0 ? failures(errors) : success(as)
+      }),
     useIdentity(codecs) ? identity : a => codecs.map((type, i) => type.encode(a[i])),
     codecs
   )
@@ -1391,12 +1388,12 @@ export interface ReadonlyC<C extends Mixed>
 /**
  * @since 1.0.0
  */
-export const readonly = <C extends Mixed>(codec: C, name: string = `Readonly<${codec.name}>`): ReadonlyC<C> =>
-  new ReadonlyType(
+export const readonly = <C extends Mixed>(codec: C, name: string = `Readonly<${codec.name}>`): ReadonlyC<C> => {
+  return new ReadonlyType(
     name,
     codec.is,
     (u, c) =>
-      codec.validate(u, c).map(x => {
+      map(codec.validate(u, c), x => {
         if (process.env.NODE_ENV !== 'production') {
           return Object.freeze(x)
         }
@@ -1405,6 +1402,7 @@ export const readonly = <C extends Mixed>(codec: C, name: string = `Readonly<${c
     codec.encode === identity ? identity : codec.encode,
     codec
   )
+}
 
 /**
  * @since 1.0.0
@@ -1440,7 +1438,7 @@ export const readonlyArray = <C extends Mixed>(
     name,
     arrayType.is,
     (u, c) =>
-      arrayType.validate(u, c).map(x => {
+      map(arrayType.validate(u, c), x => {
         if (process.env.NODE_ENV !== 'production') {
           return Object.freeze(x)
         }
@@ -1607,17 +1605,8 @@ export const exact = <C extends HasProps>(codec: C, name: string = getExactTypeN
   return new ExactType(
     name,
     codec.is,
-    (u, c) => {
-      const unknownRecordValidation = UnknownRecord.validate(u, c)
-      if (unknownRecordValidation.isLeft()) {
-        return unknownRecordValidation
-      }
-      const validation = codec.validate(u, c)
-      if (validation.isLeft()) {
-        return validation
-      }
-      return success(stripKeys(validation.value, props))
-    },
+    (u, c) =>
+      chain(UnknownRecord.validate(u, c), () => fold(codec.validate(u, c), left, a => success(stripKeys(a, props)))),
     a => codec.encode(stripKeys(a, props)),
     codec
   )
@@ -1778,14 +1767,7 @@ RefinementC<C> {
   return new RefinementType(
     name,
     (u): u is TypeOf<C> => codec.is(u) && predicate(u),
-    (i, c) => {
-      const validation = codec.validate(i, c)
-      if (validation.isLeft()) {
-        return validation
-      }
-      const a = validation.value
-      return predicate(a) ? success(a) : failure(a, c)
-    },
+    (i, c) => chain(codec.validate(i, c), a => (predicate(a) ? success(a) : failure(a, c))),
     codec.encode,
     codec,
     predicate


Test patch:
diff --git a/test/TypeClass.ts b/test/TypeClass.ts
index eb1cfd5..43198ae 100644
--- a/test/TypeClass.ts
+++ b/test/TypeClass.ts
@@ -27,7 +27,7 @@ describe('Type', () => {
     }
 
     const T = t.string
-    const decode = <L, A>(f: (u: unknown) => Either<L, A>, u: unknown): boolean => f(u).isRight()
+    const decode = <L, A>(f: (u: unknown) => Either<L, A>, u: unknown): boolean => t.fold(f(u), () => false, () => true)
     assert.strictEqual(decode(T.decode, 'a'), true)
     assert.strictEqual(decode(clone(T).decode, 'a'), true)
     type A = {
diff --git a/test/helpers.ts b/test/helpers.ts
index 776f372..63182c4 100644
--- a/test/helpers.ts
+++ b/test/helpers.ts
@@ -1,50 +1,66 @@
 import * as assert from 'assert'
-import { right } from 'fp-ts/lib/Either'
+import { right, either } from 'fp-ts/lib/Either'
 import * as t from '../src/index'
 import { PathReporter } from '../src/PathReporter'
 
 export function assertStrictEqual<T>(result: t.Validation<T>, expected: any): void {
-  if (result.isRight()) {
-    assert.deepStrictEqual(result.value, expected)
-  } else {
-    throw new Error(`${result} is not a right`)
-  }
+  t.fold(
+    result,
+    () => {
+      throw new Error(`${result} is not a right`)
+    },
+    a => {
+      assert.deepStrictEqual(a, expected)
+    }
+  )
 }
 
 export function assertSuccess<T>(result: t.Validation<T>, expected?: T): void {
-  if (result.isRight()) {
-    if (expected !== undefined) {
-      assert.deepStrictEqual(result.value, expected)
+  t.fold(
+    result,
+    () => {
+      throw new Error(`${result} is not a right`)
+    },
+    a => {
+      if (expected !== undefined) {
+        assert.deepStrictEqual(a, expected)
+      }
     }
-  } else {
-    throw new Error(`${result} is not a right`)
-  }
+  )
 }
 
 export function assertStrictSuccess<T>(result: t.Validation<T>, expected: T): void {
-  if (result.isRight()) {
-    if (expected !== undefined) {
-      assert.strictEqual(result.value, expected)
+  t.fold(
+    result,
+    () => {
+      throw new Error(`${result} is not a right`)
+    },
+    a => {
+      if (expected !== undefined) {
+        assert.strictEqual(a, expected)
+      }
     }
-  } else {
-    throw new Error(`${result} is not a right`)
-  }
+  )
 }
 
 export function assertFailure(codec: t.Any, value: unknown, errors: Array<string>): void {
   const result = codec.decode(value)
-  if (result.isLeft()) {
-    assert.deepStrictEqual(PathReporter.report(result), errors)
-  } else {
-    throw new Error(`${result} is not a left`)
-  }
+  t.fold(
+    result,
+    () => {
+      assert.deepStrictEqual(PathReporter.report(result), errors)
+    },
+    () => {
+      throw new Error(`${result} is not a left`)
+    }
+  )
 }
 
 export const NumberFromString = new t.Type<number, string, unknown>(
   'NumberFromString',
   t.number.is,
   (u, c) =>
-    t.string.validate(u, c).chain(s => {
+    either.chain(t.string.validate(u, c), s => {
       const n = +s
       return isNaN(n) ? t.failure(u, c, 'cannot parse to a number') : t.success(n)
     }),
@@ -54,19 +70,14 @@ export const NumberFromString = new t.Type<number, string, unknown>(
 export const HyphenatedString = new t.Type<string, string, unknown>(
   'HyphenatedString',
   (v): v is string => t.string.is(v) && v.length === 3 && v[1] === '-',
-  (u, c) => {
-    const stringResult = t.string.validate(u, c)
-    if (stringResult.isLeft()) {
-      return stringResult
-    } else {
-      const s = stringResult.value
+  (u, c) =>
+    either.chain(t.string.validate(u, c), s => {
       if (s.length === 2) {
         return right(s[0] + '-' + s[1])
       } else {
         return t.failure(s, c)
       }
-    }
-  },
+    }),
   a => a[0] + a[2]
 )
 
diff --git a/test/partial.ts b/test/partial.ts
index f8150f6..dbace91 100644
--- a/test/partial.ts
+++ b/test/partial.ts
@@ -70,7 +70,7 @@ describe('partial', () => {
     assertStrictEqual(T.decode(input1), input1)
     const input2 = { a: undefined }
     assertStrictEqual(T.decode(input2), input2)
-    assert.deepStrictEqual(T.decode({ b: 1 }).value, { b: 1 })
+    assertSuccess(T.decode({ b: 1 }), { b: 1 } as any)
     const input3 = { a: undefined, b: 1 }
     assertStrictEqual(T.decode(input3), input3)
   })
diff --git a/test/readonly.ts b/test/readonly.ts
index d90892c..88b45f8 100644
--- a/test/readonly.ts
+++ b/test/readonly.ts
@@ -1,6 +1,7 @@
 import * as assert from 'assert'
 import * as t from '../src/index'
 import { assertSuccess, assertFailure, NumberFromString } from './helpers'
+import { either } from 'fp-ts/lib/Either'
 
 describe('readonly', () => {
   describe('name', () => {
@@ -44,14 +45,14 @@ describe('readonly', () => {
 
     it('should freeze the value', () => {
       const T = t.readonly(t.type({ a: t.number }))
-      T.decode({ a: 1 }).map(x => assert.ok(Object.isFrozen(x)))
+      either.map(T.decode({ a: 1 }), x => assert.ok(Object.isFrozen(x)))
     })
 
     it('should not freeze in production', () => {
       const env = process.env.NODE_ENV
       process.env.NODE_ENV = 'production'
       const T = t.readonly(t.type({ a: t.number }))
-      T.decode({ a: 1 }).map(x => assert.ok(!Object.isFrozen(x)))
+      either.map(T.decode({ a: 1 }), x => assert.ok(!Object.isFrozen(x)))
       process.env.NODE_ENV = env
     })
   })
diff --git a/test/readonlyArray.ts b/test/readonlyArray.ts
index 24c64fa..44ecf68 100644
--- a/test/readonlyArray.ts
+++ b/test/readonlyArray.ts
@@ -1,6 +1,7 @@
 import * as assert from 'assert'
 import * as t from '../src/index'
 import { assertSuccess, assertFailure, NumberFromString } from './helpers'
+import { either } from 'fp-ts/lib/Either'
 
 describe('readonlyArray', () => {
   describe('name', () => {
@@ -46,14 +47,14 @@ describe('readonlyArray', () => {
 
     it('should freeze the value', () => {
       const T = t.readonlyArray(t.number)
-      T.decode([1]).map(x => assert.ok(Object.isFrozen(x)))
+      either.map(T.decode([1]), x => assert.ok(Object.isFrozen(x)))
     })
 
     it('should not freeze in production', () => {
       const env = process.env.NODE_ENV
       process.env.NODE_ENV = 'production'
       const T = t.readonlyArray(t.number)
-      T.decode([1]).map(x => assert.ok(!Object.isFrozen(x)))
+      either.map(T.decode([1]), x => assert.ok(!Object.isFrozen(x)))
       process.env.NODE_ENV = env
     })
   })
diff --git a/test/type.ts b/test/type.ts
index be97b4f..337b882 100644
--- a/test/type.ts
+++ b/test/type.ts
@@ -51,7 +51,7 @@ describe('type', () => {
       assertSuccess(T2.decode({}), { a: undefined })
 
       const T3 = t.type({ a: t.unknown })
-      assert.deepStrictEqual(T3.decode({}).value, { a: undefined })
+      assertSuccess(T3.decode({}), { a: undefined })
     })
 
     it('should fail decoding an invalid value', () => {
@@ -82,11 +82,15 @@ describe('type', () => {
   it('should keep unknown properties', () => {
     const T = t.type({ a: t.string })
     const validation = T.decode({ a: 's', b: 1 })
-    if (validation.isRight()) {
-      assert.deepStrictEqual(validation.value, { a: 's', b: 1 })
-    } else {
-      assert.ok(false)
-    }
+    t.fold(
+      validation,
+      () => {
+        assert.ok(false)
+      },
+      a => {
+        assert.deepStrictEqual(a, { a: 's', b: 1 })
+      }
+    )
   })
 
   it('should return the same reference if validation succeeded and nothing changed', () => {
diff --git a/test/union.ts b/test/union.ts
index e1820f4..6f95e8a 100644
--- a/test/union.ts
+++ b/test/union.ts
@@ -1,6 +1,7 @@
 import * as assert from 'assert'
 import * as t from '../src/index'
 import { assertFailure, assertStrictEqual, assertSuccess, NumberFromString } from './helpers'
+import { either } from 'fp-ts/lib/Either'
 
 describe('union', () => {
   describe('name', () => {
@@ -112,7 +113,7 @@ describe('union', () => {
           super(
             'DateT',
             (u): u is Date => u instanceof Date,
-            (u, c) => t.number.validate(u, c).map(n => new Date(n)),
+            (u, c) => either.map(t.number.validate(u, c), n => new Date(n)),
             a => a.valueOf()
           )
         }