Repository: gcanti/fp-ts
Pull request #430 (2018-05-13 14:39:44)

Internal: add more tests (100% coverage)


Patch:
diff --git a/src/Const.ts b/src/Const.ts
index 11db09ea..f6738a32 100644
--- a/src/Const.ts
+++ b/src/Const.ts
@@ -28,10 +28,10 @@ export class Const<L, A> {
   readonly _URI!: URI
   constructor(readonly value: L) {}
   map<B>(f: (a: A) => B): Const<L, B> {
-    return new Const(this.value)
+    return this as any
   }
   contramap<B>(f: (b: B) => A): Const<L, B> {
-    return new Const(this.value)
+    return this as any
   }
   fold<B>(f: (l: L) => B): B {
     return f(this.value)
diff --git a/src/Validation.ts b/src/Validation.ts
index 664325d3..380077ac 100644
--- a/src/Validation.ts
+++ b/src/Validation.ts
@@ -10,6 +10,7 @@ import { Semigroup } from './Semigroup'
 import { Setoid } from './Setoid'
 import { Traversable2 } from './Traversable'
 import { Predicate, phantom, toString } from './function'
+import { Bifunctor2 } from './Bifunctor'
 
 // Adapted from https://github.com/purescript/purescript-validation
 
@@ -51,7 +52,7 @@ export class Failure<L, A> {
     return b
   }
   fold<B>(failure: (l: L) => B, success: (a: A) => B): B {
-    return this as any
+    return failure(this.value)
   }
   /** Returns the value from this `Success` or the given argument if this is a `Failure` */
   getOrElse(a: A): A {
@@ -222,6 +223,10 @@ const traverse = <F>(F: Applicative<F>) => <L, A, B>(
   return ta.isFailure() ? F.of(failure(ta.value)) : F.map(f(ta.value), of as (a: B) => Validation<L, B>)
 }
 
+const bimap = <L, V, A, B>(fla: Validation<L, A>, f: (u: L) => V, g: (a: A) => B): Validation<V, B> => {
+  return fla.bimap(f, g)
+}
+
 /**
  * @function
  * @since 1.0.0
@@ -317,9 +322,10 @@ export const isSuccess = <L, A>(fa: Validation<L, A>): fa is Success<L, A> => {
  * @instance
  * @since 1.0.0
  */
-export const validation: Functor2<URI> & Foldable2<URI> & Traversable2<URI> = {
+export const validation: Functor2<URI> & Bifunctor2<URI> & Foldable2<URI> & Traversable2<URI> = {
   URI,
   map,
+  bimap,
   reduce,
   traverse
 }
diff --git a/src/function.ts b/src/function.ts
index 9921ee5c..19c6fa08 100644
--- a/src/function.ts
+++ b/src/function.ts
@@ -4,7 +4,15 @@ import { HKT } from './HKT'
  * @function
  * @since 1.0.0
  */
-export const unsafeCoerce = <A, B>(a: A): B => a as any
+export const identity = <A>(a: A): A => {
+  return a
+}
+
+/**
+ * @function
+ * @since 1.0.0
+ */
+export const unsafeCoerce: <A, B>(a: A) => B = identity as any
 
 export type Lazy<A> = () => A
 
@@ -112,14 +120,6 @@ export const constUndefined = (): undefined => {
   return
 }
 
-/**
- * @function
- * @since 1.0.0
- */
-export const identity = <A>(a: A): A => {
-  return a
-}
-
 /**
  * Flips the order of the arguments to a function of two arguments.
  * @function
@@ -308,6 +308,9 @@ export function curry(f: Function) {
   return curried(f, f.length - 1, [])
 }
 
+/* tslint:disable-next-line */
+const getFunctionName = (f: Function): string => (f as any).displayName || f.name || `<function${f.length}>`
+
 /**
  * @function
  * @since 1.0.0
@@ -322,6 +325,9 @@ export const toString = (x: any): string => {
   if (Array.isArray(x)) {
     return `[${x.map(toString).join(', ')}]`
   }
+  if (typeof x === 'function') {
+    return getFunctionName(x)
+  }
   try {
     return JSON.stringify(x, null, 2)
   } catch (e) {


Test patch:
diff --git a/test/Array.ts b/test/Array.ts
index add1c1d7..88d1f700 100644
--- a/test/Array.ts
+++ b/test/Array.ts
@@ -35,6 +35,7 @@ import {
   scanRight,
   snoc,
   sort,
+  sortBy,
   sortBy1,
   span,
   tail,
@@ -125,6 +126,7 @@ describe('Array', () => {
 
   it('index', () => {
     assert.deepEqual(index(1, as), some(2))
+    assert.deepEqual(index(3, as), none)
   })
 
   it('cons', () => {
@@ -139,6 +141,7 @@ describe('Array', () => {
 
   it('head', () => {
     assert.deepEqual(head(as), some(1))
+    assert.deepEqual(head([]), none)
   })
 
   it('last', () => {
@@ -378,6 +381,25 @@ describe('Array', () => {
     assert.deepEqual(uniq(setoidString)(['a', 'b', 'A']), ['a', 'b', 'A'])
   })
 
+  it('sortBy', () => {
+    interface Person {
+      name: string
+      age: number
+    }
+    const byName = contramapOrd((p: Person) => p.name, ordString)
+    const sortByNameByAge = sortBy([byName])
+    assert.ok(sortByNameByAge.isSome())
+    if (sortByNameByAge.isSome()) {
+      const persons = [{ name: 'a', age: 1 }, { name: 'b', age: 3 }, { name: 'c', age: 2 }, { name: 'b', age: 2 }]
+      assert.deepEqual(sortByNameByAge.value(persons), [
+        { name: 'a', age: 1 },
+        { name: 'b', age: 3 },
+        { name: 'b', age: 2 },
+        { name: 'c', age: 2 }
+      ])
+    }
+  })
+
   it('sortBy1', () => {
     interface Person {
       name: string
diff --git a/test/Const.ts b/test/Const.ts
index e5d8ae4a..9e6b4864 100644
--- a/test/Const.ts
+++ b/test/Const.ts
@@ -1,17 +1,49 @@
 import * as assert from 'assert'
-import { Const, getApply, getSetoid } from '../src/Const'
-import { semigroupSum } from '../src/Semigroup'
+import { Const, getApply, getSetoid, const_, getApplicative } from '../src/Const'
+import { semigroupString } from '../src/Semigroup'
 import { setoidNumber } from '../src/Setoid'
+import { monoidString } from '../src/Monoid'
 
 describe('Const', () => {
+  it('map', () => {
+    const fa = new Const<string, number>('foo')
+    const double = (n: number): number => n * 2
+    assert.strictEqual(fa.map(double), fa)
+    assert.strictEqual(const_.map(fa, double), fa)
+  })
+
+  it('contramap', () => {
+    const fa = new Const<string, number>('foo')
+    const double = (n: number): number => n * 2
+    assert.strictEqual(fa.contramap(double), fa)
+    assert.strictEqual(const_.contramap(fa, double), fa)
+  })
+
+  it('fold', () => {
+    const fa = new Const<string, number>('foo')
+    assert.strictEqual(fa.fold(s => s.length), 3)
+  })
+
+  it('getApplicative', () => {
+    const F = getApplicative(monoidString)
+    assert.deepEqual(F.of(1), new Const<string, number>(''))
+  })
+
+  it('toString', () => {
+    const fa = new Const<string, number>('foo')
+    assert.strictEqual(fa.toString(), 'new Const("foo")')
+    assert.strictEqual(fa.inspect(), 'new Const("foo")')
+  })
+
   it('getSetoid', () => {
     const S = getSetoid<number, string>(setoidNumber)
     assert.strictEqual(S.equals(new Const(1), new Const(1)), true)
     assert.strictEqual(S.equals(new Const(1), new Const(2)), false)
   })
 
-  it('getApply', () => {
-    const A = getApply(semigroupSum)
-    assert.deepEqual(A.ap(new Const<number, (s: string) => string>(1), new Const<number, string>(1)), new Const(2))
+  it('getApplicative', () => {
+    const F = getApply(semigroupString)
+    const fa = new Const<string, (n: number) => number>('foo')
+    assert.deepEqual(F.ap(fa, new Const('bar')), new Const('foobar'))
   })
 })
diff --git a/test/Either.ts b/test/Either.ts
index 328d6df0..d28c718f 100644
--- a/test/Either.ts
+++ b/test/Either.ts
@@ -10,7 +10,9 @@ import {
   getSetoid,
   left,
   right,
-  tryCatch
+  tryCatch,
+  isLeft,
+  isRight
 } from '../src/Either'
 import { none, option, some } from '../src/Option'
 import { setoidNumber, setoidString } from '../src/Setoid'
@@ -29,12 +31,16 @@ describe('Either', () => {
     const f = (s: string): number => s.length
     assert.deepEqual(right('abc').map(f), right(3))
     assert.deepEqual(left<string, string>('s').map(f), left('s'))
+    assert.deepEqual(either.map(right('abc'), f), right(3))
+    assert.deepEqual(either.map(left<string, string>('s'), f), left('s'))
   })
 
   it('bimap', () => {
     const f = (s: string): number => s.length
     const g = (n: number): boolean => n > 2
     assert.deepEqual(right<string, number>(1).bimap(f, g), right(false))
+    assert.deepEqual(left<string, number>('foo').bimap(f, g), left(3))
+    assert.deepEqual(either.bimap(right<string, number>(1), f, g), right(false))
   })
 
   it('ap', () => {
@@ -46,12 +52,19 @@ describe('Either', () => {
       left<string, number>('a')
     )
     assert.deepEqual(left<string, string>('b').ap(left<string, (s: string) => number>('a')), left<string, number>('a'))
+
+    assert.deepEqual(right<string, (s: string) => number>(f).ap_(right<string, string>('abc')), right(3))
+    assert.deepEqual(
+      left<string, (s: string) => number>('a').ap_(right<string, string>('abc')),
+      left<string, number>('a')
+    )
   })
 
   it('chain', () => {
     const f = (s: string) => right<string, number>(s.length)
     assert.deepEqual(right<string, string>('abc').chain(f), right(3))
     assert.deepEqual(left<string, string>('a').chain(f), left('a'))
+    assert.deepEqual(either.chain(right<string, string>('abc'), f), right(3))
   })
 
   it('fromPredicate', () => {
@@ -171,4 +184,56 @@ describe('Either', () => {
     assert.deepEqual(right(7).filterOrElseL(n => n > 10, () => -1), left(-1))
     assert.deepEqual(left(12).filterOrElseL(n => n > 10, () => -1), left(12))
   })
+
+  it('isLeft', () => {
+    assert.strictEqual(right(1).isLeft(), false)
+    assert.strictEqual(left(1).isLeft(), true)
+    assert.strictEqual(isLeft(right(1)), false)
+    assert.strictEqual(isLeft(left(1)), true)
+  })
+
+  it('isRight', () => {
+    assert.strictEqual(right(1).isRight(), true)
+    assert.strictEqual(left(1).isRight(), false)
+    assert.strictEqual(isRight(right(1)), true)
+    assert.strictEqual(isRight(left(1)), false)
+  })
+
+  it('alt', () => {
+    assert.deepEqual(right<string, number>(1).alt(right<string, number>(2)), right<string, number>(1))
+    assert.deepEqual(right<string, number>(1).alt(left<string, number>('foo')), right<string, number>(1))
+    assert.deepEqual(left<string, number>('foo').alt(right<string, number>(1)), right<string, number>(1))
+    assert.deepEqual(left<string, number>('foo').alt(left<string, number>('bar')), left<string, number>('bar'))
+    assert.deepEqual(either.alt(right<string, number>(1), right<string, number>(2)), right<string, number>(1))
+  })
+
+  it('extend', () => {
+    assert.deepEqual(right(1).extend(() => 2), right(2))
+    assert.deepEqual(left('foo').extend(() => 2), left('foo'))
+    assert.deepEqual(either.extend(right(1), () => 2), right(2))
+  })
+
+  it('reduce', () => {
+    assert.deepEqual(right('bar').reduce('foo', (b, a) => b + a), 'foobar')
+    assert.deepEqual(left('bar').reduce('foo', (b, a) => b + a), 'foo')
+    assert.deepEqual(either.reduce(right('bar'), 'foo', (b, a) => b + a), 'foobar')
+  })
+
+  it('mapLeft', () => {
+    const double = (n: number): number => n * 2
+    assert.deepEqual(right<number, string>('bar').mapLeft(double), right('bar'))
+    assert.deepEqual(left<number, string>(2).mapLeft(double), left(4))
+  })
+
+  it('toString', () => {
+    assert.strictEqual(right('bar').toString(), 'right("bar")')
+    assert.strictEqual(right('bar').inspect(), 'right("bar")')
+    assert.strictEqual(left('bar').toString(), 'left("bar")')
+    assert.strictEqual(left('bar').inspect(), 'left("bar")')
+  })
+
+  it('swap', () => {
+    assert.deepEqual(right('bar').swap(), left('bar'))
+    assert.deepEqual(left('bar').swap(), right('bar'))
+  })
 })
diff --git a/test/EitherT.ts b/test/EitherT.ts
new file mode 100644
index 00000000..5261b2b0
--- /dev/null
+++ b/test/EitherT.ts
@@ -0,0 +1,18 @@
+import * as assert from 'assert'
+import * as eitherT from '../src/EitherT'
+import * as either from '../src/Either'
+import { task } from '../src/Task'
+
+describe('EitherT', () => {
+  it('chain', () => {
+    const chain = eitherT.chain(task)
+    const of = eitherT.getEitherT(task).of
+    const f = (n: number) => of(n * 2)
+    const x = of(1)
+    const y = eitherT.fromEither(task)<string, number>(either.left('foo'))
+    return Promise.all([chain(f, x).run(), chain(f, y).run()]).then(([e1, e2]) => {
+      assert.deepEqual(e1, either.right(2))
+      assert.deepEqual(e2, either.left('foo'))
+    })
+  })
+})
diff --git a/test/Exception.ts b/test/Exception.ts
index fb6c5493..b9cbd7b8 100644
--- a/test/Exception.ts
+++ b/test/Exception.ts
@@ -1,36 +1,67 @@
 import * as assert from 'assert'
-import * as exception from '../src/Exception'
+import { catchError, error, message, stack, throwError, tryCatch } from '../src/Exception'
 import { IO } from '../src/IO'
 
 describe('Exception', () => {
   it('error', () => {
     const message = 'bum!'
-    const e = exception.error(message)
+    const e = error(message)
     assert.strictEqual(e instanceof Error, true)
     assert.strictEqual(e.message, message)
   })
 
   it('message', () => {
-    const message = 'bum!'
-    const e = new Error(message)
-    assert.strictEqual(exception.message(e), message)
+    const m = 'bum!'
+    const e = new Error(m)
+    assert.strictEqual(message(e), m)
+  })
+
+  it('stack', () => {
+    try {
+      throw new Error('bum!')
+    } catch (e) {
+      assert.strictEqual(
+        stack(e)
+          .filter(s => s.startsWith('Error: bum!'))
+          .isSome(),
+        true
+      )
+    }
+    const e = new Error('bum!')
+    delete e.stack
+    assert.strictEqual(stack(e).isNone(), true)
   })
 
   it('throwError', () => {
-    const eio = exception.throwError<number>(new Error('bum!'))
+    const eio = throwError<number>(new Error('bum!'))
     assert.throws(() => eio.run())
   })
 
   it('catchError', () => {
-    const eio = exception.throwError<number>(new Error('bum!'))
-    const io = exception.catchError(eio, e => new IO(() => 1))
-    assert.strictEqual(io.run(), 1)
+    assert.strictEqual(
+      catchError(
+        new IO(() => {
+          throw new Error('bum!')
+        }),
+        e => new IO(() => 1)
+      ).run(),
+      1
+    )
+    assert.strictEqual(
+      catchError(
+        new IO(() => {
+          throw 'bum!'
+        }),
+        e => new IO(() => 1)
+      ).run(),
+      1
+    )
   })
 
   it('tryCatch', () => {
-    const eiol = exception.tryCatch(exception.throwError<number>(new Error('bum!')))
+    const eiol = tryCatch(throwError<number>(new Error('bum!')))
     assert.strictEqual(eiol.run().isLeft(), true)
-    const eior = exception.tryCatch(new IO(() => 1))
+    const eior = tryCatch(new IO(() => 1))
     assert.strictEqual(eior.run().isRight(), true)
   })
 })
diff --git a/test/Field.ts b/test/Field.ts
index a7edf8c2..2e61408a 100644
--- a/test/Field.ts
+++ b/test/Field.ts
@@ -1,6 +1,7 @@
 import * as assert from 'assert'
 import * as field from '../src/Field'
 import * as setoid from '../src/Setoid'
+import { fieldNumber } from '../src/Field'
 
 describe('Field', () => {
   it('gcd', () => {
@@ -13,5 +14,12 @@ describe('Field', () => {
   it('lcm', () => {
     const lcm = field.lcm(setoid.setoidNumber, field.fieldNumber)
     assert.strictEqual(lcm(4, 6), 12)
+    assert.strictEqual(lcm(4, 0), 0)
+  })
+
+  it('fieldNumber', () => {
+    assert.strictEqual(fieldNumber.degree(0), 1)
+    assert.strictEqual(fieldNumber.degree(1), 1)
+    assert.strictEqual(fieldNumber.degree(2), 1)
   })
 })
diff --git a/test/IO.ts b/test/IO.ts
index f44d0f0a..2c284721 100644
--- a/test/IO.ts
+++ b/test/IO.ts
@@ -1,16 +1,43 @@
 import * as assert from 'assert'
-import { IO, getSemigroup } from '../src/IO'
-import { semigroupVoid } from '../src/Semigroup'
+import { IO, getSemigroup, io, getMonoid } from '../src/IO'
+import { semigroupSum } from '../src/Semigroup'
+import { monoidSum } from '../src/Monoid'
 
 describe('IO', () => {
+  it('ap', () => {
+    const double = (n: number): number => n * 2
+    const fab = io.of(double)
+    const fa = io.of(1)
+    assert.strictEqual(fa.ap(fab).run(), 2)
+    assert.strictEqual(fab.ap_(fa).run(), 2)
+  })
+
+  it('chain', () => {
+    const f = (n: number) => io.of(n * 2)
+    assert.strictEqual(
+      io
+        .of(1)
+        .chain(f)
+        .run(),
+      2
+    )
+    assert.strictEqual(io.chain(io.of(1), f).run(), 2)
+  })
+
   it('getSemigroup', () => {
-    const S = getSemigroup(semigroupVoid)
+    const S = getSemigroup(semigroupSum)
     const log: Array<string> = []
-    const append = (message: string) =>
-      new IO(() => {
-        log.push(message)
-      })
-    S.concat(append('a'), append('b')).run()
+    const append = (message: string): IO<number> => new IO(() => log.push(message))
+    assert.strictEqual(S.concat(append('a'), append('b')).run(), 3)
+    assert.deepEqual(log, ['a', 'b'])
+  })
+
+  it('getMonoid', () => {
+    const M = getMonoid(monoidSum)
+    const log: Array<string> = []
+    const append = (message: string): IO<number> => new IO(() => log.push(message))
+    assert.strictEqual(M.concat(append('a'), M.empty).run(), 1)
+    assert.strictEqual(M.concat(M.empty, append('b')).run(), 2)
     assert.deepEqual(log, ['a', 'b'])
   })
 
@@ -25,4 +52,9 @@ describe('IO', () => {
     )
     assert.deepEqual(log, ['a', 'b'])
   })
+
+  it('toString', () => {
+    assert.strictEqual(io.of(1).toString(), 'new IO(<function0>)')
+    assert.strictEqual(io.of(1).inspect(), 'new IO(<function0>)')
+  })
 })
diff --git a/test/IxIO.ts b/test/IxIO.ts
index c851a990..8cc7be49 100644
--- a/test/IxIO.ts
+++ b/test/IxIO.ts
@@ -1,7 +1,6 @@
 import * as assert from 'assert'
 import * as io from '../src/IO'
-import * as ixIO from '../src/IxIO'
-import { IxIO } from '../src/IxIO'
+import { IxIO, ixIO, getMonad } from '../src/IxIO'
 import { iapplyFirst, iapplySecond } from '../src/IxMonad'
 
 //
@@ -69,15 +68,42 @@ describe('IxIO', () => {
 
   it('iapplyFirst', () => {
     log = []
-    const action = iapplyFirst(ixIO.ixIO)(new Open(), new Close())
+    const action = iapplyFirst(ixIO)(new Open(), new Close())
     action.run()
     assert.deepEqual(log, ['Opening the door', 'Closing the door'])
   })
 
   it('iapplySecond', () => {
     log = []
-    const action = iapplySecond(ixIO.ixIO)(new Open(), new Close())
+    const action = iapplySecond(ixIO)(new Open(), new Close())
     action.run()
     assert.deepEqual(log, ['Opening the door', 'Closing the door'])
   })
+
+  it('map', () => {
+    const double = (n: number): number => n * 2
+    const x = ixIO.iof(1)
+    assert.strictEqual(x.map(double).run(), 2)
+  })
+
+  it('ap', () => {
+    const double = (n: number): number => n * 2
+    const fab = ixIO.iof(double)
+    const fa = ixIO.iof(1)
+    assert.strictEqual(fa.ap(fab).run(), 2)
+  })
+
+  it('chain', () => {
+    const f = (n: number) => ixIO.iof(n * 2)
+    const fa = ixIO.iof(1)
+    assert.strictEqual(fa.chain(f).run(), 2)
+  })
+
+  it('getMonad', () => {
+    const M = getMonad<number>()
+    const double = (n: number): number => n * 2
+    assert.strictEqual(M.map(M.of(1), double).run(), 2)
+    assert.strictEqual(M.ap(M.of(double), M.of(1)).run(), 2)
+    assert.strictEqual(M.chain(M.of(1), n => M.of(n * 2)).run(), 2)
+  })
 })
diff --git a/test/Monoid.ts b/test/Monoid.ts
index 0b2fe731..2475f478 100644
--- a/test/Monoid.ts
+++ b/test/Monoid.ts
@@ -10,7 +10,8 @@ import {
   monoidAll,
   monoidAny,
   monoidString,
-  monoidSum
+  monoidSum,
+  getProductMonoid
 } from '../src/Monoid'
 import { semigroupSum } from '../src/Semigroup'
 
@@ -78,4 +79,9 @@ describe('Monoid', () => {
     }
     assert.deepEqual(result, expected)
   })
+
+  it('getArrayMonoid', () => {
+    const M = getProductMonoid(monoidString, monoidSum)
+    assert.deepEqual(M.empty, ['', 0])
+  })
 })
diff --git a/test/Monoidal.ts b/test/Monoidal.ts
index 87018a9c..b6e3b133 100644
--- a/test/Monoidal.ts
+++ b/test/Monoidal.ts
@@ -5,19 +5,21 @@ import { none, option, some } from '../src/Option'
 
 describe('Monoidal', () => {
   it('fromApplicative', () => {
-    const monoidalOption = fromApplicative(option)
-    assert.deepEqual(monoidalOption.mult(some(1), some('a')), some([1, 'a']))
-    assert.deepEqual(monoidalOption.mult(some(1), none), none)
+    const M = fromApplicative(option)
+    assert.deepEqual(M.mult(some(1), some('a')), some([1, 'a']))
+    assert.deepEqual(M.mult(some(1), none), none)
+    assert.deepEqual(M.unit(), some(undefined))
     const monoidalEither = fromApplicative(either)
     assert.deepEqual(monoidalEither.mult(right(1), right('a')), right([1, 'a']))
     assert.deepEqual(monoidalEither.mult(right(1), left('error')), left('error'))
   })
 
   it('toApplicative', () => {
-    const monoidalOption = fromApplicative(option)
-    const applicative = toApplicative(monoidalOption)
+    const M = fromApplicative(option)
+    const F = toApplicative(M)
     const double = (n: number) => n * 2
-    assert.deepEqual(applicative.ap(option.of(double), some(1)), some(2))
-    assert.deepEqual(applicative.ap(option.of(double), none), none)
+    assert.deepEqual(F.ap(option.of(double), some(1)), some(2))
+    assert.deepEqual(F.ap(option.of(double), none), none)
+    assert.deepEqual(F.of(1), some(1))
   })
 })
diff --git a/test/NonEmptyArray.ts b/test/NonEmptyArray.ts
index 367b3d78..e4e682d9 100644
--- a/test/NonEmptyArray.ts
+++ b/test/NonEmptyArray.ts
@@ -1,6 +1,6 @@
 import * as assert from 'assert'
 import { fold, monoidSum } from '../src/Monoid'
-import { NonEmptyArray, nonEmptyArray } from '../src/NonEmptyArray'
+import { NonEmptyArray, nonEmptyArray, fromArray, getSemigroup } from '../src/NonEmptyArray'
 import { none, option, some } from '../src/Option'
 import { ordNumber } from '../src/Ord'
 import { traverse } from '../src/Traversable'
@@ -16,21 +16,37 @@ describe('NonEmptyArray', () => {
     const x = new NonEmptyArray(1, [2])
     const double = (n: number) => n * 2
     assert.deepEqual(x.map(double).toArray(), [2, 4])
+    assert.deepEqual(nonEmptyArray.map(x, double).toArray(), [2, 4])
+  })
+
+  it('ap', () => {
+    assert.deepEqual(nonEmptyArray.of(1), new NonEmptyArray(1, []))
+  })
+
+  it('ap', () => {
+    const x = new NonEmptyArray(1, [2])
+    const double = (n: number) => n * 2
+    assert.deepEqual(x.ap(new NonEmptyArray(double, [double])).toArray(), [2, 4, 2, 4])
+    assert.deepEqual(nonEmptyArray.ap(new NonEmptyArray(double, [double]), x).toArray(), [2, 4, 2, 4])
+    assert.deepEqual(new NonEmptyArray(double, [double]).ap_(x).toArray(), [2, 4, 2, 4])
   })
 
   it('chain', () => {
     const x = new NonEmptyArray(1, [2])
     const f = (a: number) => new NonEmptyArray(a, [4])
     assert.deepEqual(x.chain(f).toArray(), [1, 4, 2, 4])
+    assert.deepEqual(nonEmptyArray.chain(x, f).toArray(), [1, 4, 2, 4])
   })
 
   it('extend', () => {
     const sum = (as: NonEmptyArray<number>) => fold(monoidSum)(as.toArray())
     assert.deepEqual(new NonEmptyArray(1, [2, 3, 4]).extend(sum), new NonEmptyArray(10, [9, 7, 4]))
+    assert.deepEqual(nonEmptyArray.extend(new NonEmptyArray(1, [2, 3, 4]), sum), new NonEmptyArray(10, [9, 7, 4]))
   })
 
   it('extract', () => {
     assert.strictEqual(new NonEmptyArray(1, [2, 3]).extract(), 1)
+    assert.strictEqual(nonEmptyArray.extract(new NonEmptyArray(1, [2, 3])), 1)
   })
 
   it('traverse', () => {
@@ -53,4 +69,28 @@ describe('NonEmptyArray', () => {
     assert.deepEqual(new NonEmptyArray(1, [2, 3]).max(ordNumber), 3)
     assert.deepEqual(new NonEmptyArray(1, []).max(ordNumber), 1)
   })
+
+  it('reduce', () => {
+    const x = new NonEmptyArray('a', ['b'])
+    assert.strictEqual(x.reduce('', (b, a) => b + a), 'ab')
+    assert.strictEqual(nonEmptyArray.reduce(x, '', (b, a) => b + a), 'ab')
+  })
+
+  it('toString', () => {
+    const x = new NonEmptyArray(1, [2])
+    assert.strictEqual(x.toString(), 'new NonEmptyArray(1, [2])')
+    assert.strictEqual(x.inspect(), 'new NonEmptyArray(1, [2])')
+  })
+
+  it('fromArray', () => {
+    assert.deepEqual(fromArray([]), none)
+    assert.deepEqual(fromArray([1]), some(new NonEmptyArray(1, [])))
+    assert.deepEqual(fromArray([1, 2]), some(new NonEmptyArray(1, [2])))
+  })
+
+  it('getSemigroup', () => {
+    const S = getSemigroup<number>()
+    assert.deepEqual(S.concat(new NonEmptyArray(1, []), new NonEmptyArray(2, [])), new NonEmptyArray(1, [2]))
+    assert.deepEqual(S.concat(new NonEmptyArray(1, [2]), new NonEmptyArray(3, [4])), new NonEmptyArray(1, [2, 3, 4]))
+  })
 })
diff --git a/test/Option.ts b/test/Option.ts
index 6c546b70..9b355065 100644
--- a/test/Option.ts
+++ b/test/Option.ts
@@ -5,12 +5,15 @@ import {
   Option,
   fromEither,
   fromNullable,
+  fromPredicate,
   fromRefinement,
   getFirstMonoid,
   getLastMonoid,
   getMonoid,
   getOrd,
   getSetoid,
+  isNone,
+  isSome,
   none,
   option,
   some,
@@ -30,6 +33,30 @@ describe('Option', () => {
     assert.strictEqual(some('abc').fold(f, g), 'some3')
   })
 
+  it('foldL', () => {
+    const f = () => 'none'
+    const g = (s: string) => `some${s.length}`
+    assert.strictEqual(none.foldL(f, g), 'none')
+    assert.strictEqual(some('abc').foldL(f, g), 'some3')
+  })
+
+  it('toNullable', () => {
+    assert.strictEqual(none.toNullable(), null)
+    assert.strictEqual(some(1).toNullable(), 1)
+  })
+
+  it('toUndefined', () => {
+    assert.strictEqual(none.toUndefined(), undefined)
+    assert.strictEqual(some(1).toUndefined(), 1)
+  })
+
+  it('toString', () => {
+    assert.strictEqual(none.toString(), 'none')
+    assert.strictEqual(some(1).toString(), 'some(1)')
+    assert.strictEqual(none.inspect(), 'none')
+    assert.strictEqual(some(1).inspect(), 'some(1)')
+  })
+
   it('getOrElse', () => {
     const x: Option<number> = some(1)
     assert.strictEqual(x.getOrElse(0), 1)
@@ -58,6 +85,7 @@ describe('Option', () => {
     const f = (n: number) => n * 2
     assert.deepEqual(some(2).map(f), some(4))
     assert.deepEqual(none.map(f), none)
+    assert.deepEqual(option.map(some(2), f), some(4))
   })
 
   it('getSetoid', () => {
@@ -102,6 +130,9 @@ describe('Option', () => {
     assert.deepEqual(none.ap(some(f)), none)
     assert.deepEqual(some(2).ap(none), none)
     assert.deepEqual(some(2).ap(some(f)), some(4))
+    assert.deepEqual(option.ap(some(f), some(2)), some(4))
+    assert.deepEqual(some(f).ap_(some(2)), some(4))
+    assert.deepEqual(none.ap_(some(2)), none)
   })
 
   it('chain', () => {
@@ -128,12 +159,25 @@ describe('Option', () => {
     assert.deepEqual(none.alt(none), none)
   })
 
+  it('extend', () => {
+    const f = (fa: Option<number>) => fa.getOrElse(0)
+    assert.deepEqual(some(2).extend(f), some(2))
+    assert.deepEqual(none.extend(f), none)
+    assert.deepEqual(option.extend(some(2), f), some(2))
+  })
+
   it('fromNullable', () => {
     assert.deepEqual(fromNullable(2), some(2))
     assert.deepEqual(fromNullable(null), none)
     assert.deepEqual(fromNullable(undefined), none)
   })
 
+  it('fromPredicate', () => {
+    const f = fromPredicate((n: number) => n > 2)
+    assert.deepEqual(f(1), none)
+    assert.deepEqual(f(3), some(3))
+  })
+
   it('traverse', () => {
     assert.deepEqual(traverse(array, option)(some('hello'), s => []), [])
     assert.deepEqual(traverse(array, option)(some('hello'), s => [s.length]), [some(5)])
@@ -169,17 +213,17 @@ describe('Option', () => {
   })
 
   it('isNone', () => {
-    const x: Option<number> = none
-    assert.equal(x.isNone(), true)
+    assert.equal(none.isNone(), true)
     assert.equal(some(1).isNone(), false)
     assert.equal(some(null).isNone(), false)
+    assert.equal(isNone(none), true)
   })
 
   it('isSome', () => {
-    const x: Option<number> = none
-    assert.equal(x.isSome(), false)
+    assert.equal(none.isSome(), false)
     assert.equal(some(1).isSome(), true)
     assert.equal(some(null).isSome(), true)
+    assert.equal(isSome(none), false)
   })
 
   it('exists', () => {
diff --git a/test/OptionT.ts b/test/OptionT.ts
index 22bd11ee..ac3e862e 100644
--- a/test/OptionT.ts
+++ b/test/OptionT.ts
@@ -1,10 +1,10 @@
 import * as assert from 'assert'
 import * as option from '../src/Option'
 import * as optionT from '../src/OptionT'
-import * as task from '../src/Task'
+import { task } from '../src/Task'
 
-const taskOption = optionT.getOptionT(task.task)
-const none = optionT.none(task.task)()
+const taskOption = optionT.getOptionT(task)
+const none = optionT.none(task)()
 
 describe('OptionT', () => {
   it('map', () => {
@@ -28,13 +28,13 @@ describe('OptionT', () => {
     const f = 'none'
     const g = (s: string) => `some${s.length}`
     const p1 = optionT
-      .fold(task.task)(f, g, none)
+      .fold(task)(f, g, none)
       .run()
       .then(s => {
         assert.strictEqual(s, 'none')
       })
     const p2 = optionT
-      .fold(task.task)(f, g, taskOption.of('s'))
+      .fold(task)(f, g, taskOption.of('s'))
       .run()
       .then(s => {
         assert.strictEqual(s, 'some1')
@@ -44,7 +44,7 @@ describe('OptionT', () => {
 
   it('getOrElse', () => {
     const greetingT = taskOption.of('welcome')
-    const getOrElse = optionT.getOrElse(task.task)('hello, there!')
+    const getOrElse = optionT.getOrElse(task)('hello, there!')
     const p1 = getOrElse(greetingT)
       .run()
       .then(s => {
@@ -57,4 +57,30 @@ describe('OptionT', () => {
       })
     return Promise.all([p1, p2])
   })
+
+  it('some', () => {
+    const some = optionT.some(task)
+    return some(1)
+      .run()
+      .then(o => {
+        assert.deepEqual(o, option.some(1))
+      })
+  })
+
+  it('fromOption', () => {
+    const fromOption = optionT.fromOption(task)
+    return Promise.all([fromOption(option.some(1)).run(), fromOption(option.none).run()]).then(([o1, o2]) => {
+      assert.deepEqual(o1, option.some(1))
+      assert.deepEqual(o2, option.none)
+    })
+  })
+
+  it('liftF', () => {
+    const liftF = optionT.liftF(task)
+    return liftF(task.of(1))
+      .run()
+      .then(o => {
+        assert.deepEqual(o, option.some(1))
+      })
+  })
 })
diff --git a/test/Ord.ts b/test/Ord.ts
index 1276e8a9..075e0a46 100644
--- a/test/Ord.ts
+++ b/test/Ord.ts
@@ -9,7 +9,10 @@ import {
   getSemigroup,
   ordDate,
   ordNumber,
-  ordString
+  ordString,
+  lessThanOrEq,
+  greaterThanOrEq,
+  fromCompare
 } from '../src/Ord'
 
 describe('Ord', () => {
@@ -68,4 +71,24 @@ describe('Ord', () => {
     assert.strictEqual(ordDate.compare(new Date(0), new Date(1)), -1)
     assert.strictEqual(ordDate.compare(new Date(1), new Date(0)), 1)
   })
+
+  it('lessThanOrEq', () => {
+    const leq = lessThanOrEq(ordNumber)
+    assert.strictEqual(leq(0, 1), true)
+    assert.strictEqual(leq(1, 1), true)
+    assert.strictEqual(leq(2, 1), false)
+  })
+
+  it('greaterThanOrEq', () => {
+    const geq = greaterThanOrEq(ordNumber)
+    assert.strictEqual(geq(0, 1), false)
+    assert.strictEqual(geq(1, 1), true)
+    assert.strictEqual(geq(2, 1), true)
+  })
+
+  it('fromCompare', () => {
+    const O = fromCompare(ordNumber.compare)
+    assert.strictEqual(O.equals(0, 1), false)
+    assert.strictEqual(O.equals(1, 1), true)
+  })
 })
diff --git a/test/Ordering.ts b/test/Ordering.ts
new file mode 100644
index 00000000..a269111e
--- /dev/null
+++ b/test/Ordering.ts
@@ -0,0 +1,40 @@
+import * as assert from 'assert'
+import { sign, setoidOrdering, semigroupOrdering, invert } from '../src/Ordering'
+
+describe('Ordering', () => {
+  it('sign', () => {
+    assert.strictEqual(sign(10), 1)
+    assert.strictEqual(sign(0), 0)
+    assert.strictEqual(sign(-10), -1)
+  })
+
+  it('setoidOrdering', () => {
+    assert.strictEqual(setoidOrdering.equals(-1, -1), true)
+    assert.strictEqual(setoidOrdering.equals(-1, 0), false)
+    assert.strictEqual(setoidOrdering.equals(-1, 1), false)
+    assert.strictEqual(setoidOrdering.equals(0, -1), false)
+    assert.strictEqual(setoidOrdering.equals(0, 0), true)
+    assert.strictEqual(setoidOrdering.equals(0, 1), false)
+    assert.strictEqual(setoidOrdering.equals(1, -1), false)
+    assert.strictEqual(setoidOrdering.equals(1, 0), false)
+    assert.strictEqual(setoidOrdering.equals(1, 1), true)
+  })
+
+  it('semigroupOrdering', () => {
+    assert.strictEqual(semigroupOrdering.concat(-1, -1), -1)
+    assert.strictEqual(semigroupOrdering.concat(-1, 0), -1)
+    assert.strictEqual(semigroupOrdering.concat(-1, 1), -1)
+    assert.strictEqual(semigroupOrdering.concat(0, -1), -1)
+    assert.strictEqual(semigroupOrdering.concat(0, 0), 0)
+    assert.strictEqual(semigroupOrdering.concat(0, 1), 1)
+    assert.strictEqual(semigroupOrdering.concat(1, -1), 1)
+    assert.strictEqual(semigroupOrdering.concat(1, 0), 1)
+    assert.strictEqual(semigroupOrdering.concat(1, 1), 1)
+  })
+
+  it('invert', () => {
+    assert.strictEqual(invert(-1), 1)
+    assert.strictEqual(invert(0), 0)
+    assert.strictEqual(invert(1), -1)
+  })
+})
diff --git a/test/Pair.ts b/test/Pair.ts
index f4fc94ac..f6ccc690 100644
--- a/test/Pair.ts
+++ b/test/Pair.ts
@@ -1,11 +1,86 @@
 import * as assert from 'assert'
+import { monoidString } from '../src/Monoid'
 import { none, option, some } from '../src/Option'
-import { Pair, pair } from '../src/Pair'
+import { ordNumber } from '../src/Ord'
+import { Pair, getMonoid, getOrd, getSemigroup, getSetoid, pair } from '../src/Pair'
+import { semigroupString } from '../src/Semigroup'
+import { setoidNumber } from '../src/Setoid'
 import { traverse } from '../src/Traversable'
 
 describe('Pair', () => {
+  it('first', () => {
+    const double = (n: number): number => n * 2
+    assert.deepEqual(new Pair(1, 2).first(double), new Pair(2, 2))
+  })
+
+  it('second', () => {
+    const double = (n: number): number => n * 2
+    assert.deepEqual(new Pair(1, 2).second(double), new Pair(1, 4))
+  })
+
+  it('map', () => {
+    const double = (n: number): number => n * 2
+    assert.deepEqual(new Pair(1, 2).map(double), new Pair(2, 4))
+    assert.deepEqual(pair.map(new Pair(1, 2), double), new Pair(2, 4))
+  })
+
+  it('of', () => {
+    assert.deepEqual(pair.of(1), new Pair(1, 1))
+  })
+
+  it('ap', () => {
+    const double = (n: number): number => n * 2
+    const inc = (n: number): number => n + 1
+    assert.deepEqual(new Pair(1, 2).ap(new Pair(double, inc)), new Pair(2, 3))
+    assert.deepEqual(pair.ap(new Pair(double, inc), new Pair(1, 2)), new Pair(2, 3))
+    assert.deepEqual(new Pair(double, inc).ap_(new Pair(1, 2)), new Pair(2, 3))
+  })
+
+  it('reduce', () => {
+    assert.deepEqual(new Pair('a', 'b').reduce('', (b, a) => b + a), 'ab')
+    assert.deepEqual(pair.reduce(new Pair('a', 'b'), '', (b, a) => b + a), 'ab')
+  })
+
+  it('extract', () => {
+    assert.deepEqual(new Pair('a', 'b').extract(), 'a')
+    assert.deepEqual(pair.extract(new Pair('a', 'b')), 'a')
+  })
+
+  it('extend', () => {
+    const f = (p: Pair<string>) => p.fst + p.snd
+    assert.deepEqual(new Pair('a', 'b').extend(f), new Pair('ab', 'ba'))
+    assert.deepEqual(pair.extend(new Pair('a', 'b'), f), new Pair('ab', 'ba'))
+  })
+
   it('traverse', () => {
     assert.deepEqual(traverse(option, pair)(new Pair(0, 1), n => (n >= 0 ? some(n) : none)), some(new Pair(0, 1)))
     assert.deepEqual(traverse(option, pair)(new Pair(0, 1), n => (n >= 2 ? some(n) : none)), none)
   })
+
+  it('getSetoid', () => {
+    const S = getSetoid(setoidNumber)
+    assert.strictEqual(S.equals(new Pair(1, 2), new Pair(1, 2)), true)
+    assert.strictEqual(S.equals(new Pair(1, 2), new Pair(1, 3)), false)
+    assert.strictEqual(S.equals(new Pair(1, 2), new Pair(2, 2)), false)
+  })
+
+  it('getOrd', () => {
+    const O = getOrd(ordNumber)
+    assert.strictEqual(O.compare(new Pair(0, 2), new Pair(1, 2)), -1)
+    assert.strictEqual(O.compare(new Pair(1, 2), new Pair(1, 2)), 0)
+    assert.strictEqual(O.compare(new Pair(2, 2), new Pair(1, 2)), 1)
+    assert.strictEqual(O.compare(new Pair(1, 2), new Pair(1, 3)), -1)
+    assert.strictEqual(O.compare(new Pair(1, 2), new Pair(1, 1)), 1)
+  })
+
+  it('getSemigroup', () => {
+    const S = getSemigroup(semigroupString)
+    assert.deepEqual(S.concat(new Pair('a', 'b'), new Pair('c', 'd')), new Pair('ac', 'bd'))
+  })
+
+  it('getMonoid', () => {
+    const S = getMonoid(monoidString)
+    assert.deepEqual(S.concat(new Pair('a', 'b'), new Pair('', '')), new Pair('a', 'b'))
+    assert.deepEqual(S.concat(new Pair('', ''), new Pair('a', 'b')), new Pair('a', 'b'))
+  })
 })
diff --git a/test/Reader.ts b/test/Reader.ts
new file mode 100644
index 00000000..3916bae2
--- /dev/null
+++ b/test/Reader.ts
@@ -0,0 +1,46 @@
+import * as assert from 'assert'
+import { Reader, reader, ask, asks, local } from '../src/Reader'
+
+describe('Reader', () => {
+  it('map', () => {
+    const double = (n: number): number => n * 2
+    const x = new Reader(() => 1)
+    assert.strictEqual(x.map(double).run({}), 2)
+    assert.strictEqual(reader.map(x, double).run({}), 2)
+  })
+
+  it('of', () => {
+    assert.strictEqual(reader.of(1).run({}), 1)
+  })
+
+  it('of', () => {
+    const double = (n: number): number => n * 2
+    const f = reader.of(double)
+    const x = reader.of(1)
+    assert.strictEqual(x.ap(f).run({}), 2)
+    assert.strictEqual(reader.ap(f, x).run({}), 2)
+    assert.strictEqual(f.ap_(x).run({}), 2)
+  })
+
+  it('chain', () => {
+    const x = new Reader<object, string>(() => 'foo')
+    const f = (s: string): Reader<object, number> => reader.of(s.length)
+    assert.strictEqual(x.chain(f).run({}), 3)
+    assert.strictEqual(reader.chain(x, f).run({}), 3)
+  })
+
+  it('ask', () => {
+    const x = ask()
+    assert.strictEqual(x.run(1), 1)
+  })
+
+  it('asks', () => {
+    const x = asks((s: string) => s.length)
+    assert.strictEqual(x.run('foo'), 3)
+  })
+
+  it('asks', () => {
+    const x = local((s: string) => s + '!')(ask())
+    assert.strictEqual(x.run('foo'), 'foo!')
+  })
+})
diff --git a/test/ReaderT.ts b/test/ReaderT.ts
index 17384445..02cd2006 100644
--- a/test/ReaderT.ts
+++ b/test/ReaderT.ts
@@ -1,14 +1,14 @@
 import * as assert from 'assert'
 import { none, option, some } from '../src/Option'
 import { reader } from '../src/Reader'
-import { fromReader, getReaderT } from '../src/ReaderT'
+import * as readerT from '../src/ReaderT'
 import { StrMap, lookup } from '../src/StrMap'
 
-const readerOption = getReaderT(option)
+const readerOption = readerT.getReaderT(option)
 
 describe('ReaderT', () => {
-  it('fromState', () => {
-    const f = fromReader(option)(reader.of<void, number>(1))
+  it('fromReader', () => {
+    const f = readerT.fromReader(option)(reader.of<void, number>(1))
     assert.deepEqual(f(undefined), some(1))
   })
 
@@ -38,4 +38,28 @@ describe('ReaderT', () => {
 
     assert.deepEqual(setupConnection(badConfig), none)
   })
+
+  it('of', () => {
+    const of = readerT.of(option)
+    assert.deepEqual(of(1)({}), some(1))
+  })
+
+  it('ap', () => {
+    const of = readerT.of(option)
+    const ap = readerT.ap(option)
+    const double = (n: number): number => n * 2
+    const fab = of(double)
+    const fa = of(1)
+    assert.deepEqual(ap(fab, fa)({}), some(2))
+  })
+
+  it('ask', () => {
+    const ask = readerT.ask(option)
+    assert.deepEqual(ask()(1), some(1))
+  })
+
+  it('asks', () => {
+    const asks = readerT.asks(option)
+    assert.deepEqual(asks((s: string) => s.length)('foo'), some(3))
+  })
 })
diff --git a/test/Ring.ts b/test/Ring.ts
index ea3b88e9..a9b2ade0 100644
--- a/test/Ring.ts
+++ b/test/Ring.ts
@@ -1,14 +1,26 @@
 import * as assert from 'assert'
 import { fieldNumber } from '../src/Field'
-import { getProductRing } from '../src/Ring'
+import { getProductRing, negate, getFunctionRing } from '../src/Ring'
 
 describe('Ring', () => {
   it('getProductRing', () => {
-    const ring = getProductRing(fieldNumber, fieldNumber)
-    assert.deepEqual(ring.add([1, 2], [3, 4]), [4, 6])
-    assert.deepEqual(ring.mul([1, 2], [3, 4]), [3, 8])
-    assert.deepEqual(ring.one, [1, 1])
-    assert.deepEqual(ring.sub([1, 2], [3, 4]), [-2, -2])
-    assert.deepEqual(ring.zero, [0, 0])
+    const R = getProductRing(fieldNumber, fieldNumber)
+    assert.deepEqual(R.add([1, 2], [3, 4]), [4, 6])
+    assert.deepEqual(R.mul([1, 2], [3, 4]), [3, 8])
+    assert.deepEqual(R.one, [1, 1])
+    assert.deepEqual(R.sub([1, 2], [3, 4]), [-2, -2])
+    assert.deepEqual(R.zero, [0, 0])
+  })
+
+  it('negate', () => {
+    assert.strictEqual(negate(fieldNumber)(1), -1)
+  })
+
+  it('getFunctionRing', () => {
+    const R = getFunctionRing<string, number>(fieldNumber)
+    const f1 = (s: string): number => s.length
+    const f2 = (s: string): number => s.indexOf('a')
+    assert.strictEqual(R.sub(f1, f2)('foo'), 4)
+    assert.strictEqual(R.sub(f1, f2)('fooa'), 1)
   })
 })
diff --git a/test/Semigroup.ts b/test/Semigroup.ts
index f15bf65e..12fdd2b9 100644
--- a/test/Semigroup.ts
+++ b/test/Semigroup.ts
@@ -5,12 +5,15 @@ import {
   fold,
   getArraySemigroup,
   getDictionarySemigroup,
+  getFirstSemigroup,
   getJoinSemigroup,
   getMeetSemigroup,
   getObjectSemigroup,
   getProductSemigroup,
   getRecordSemigroup,
-  semigroupSum
+  semigroupProduct,
+  semigroupSum,
+  semigroupVoid
 } from '../src/Semigroup'
 
 describe('Semigroup', () => {
@@ -84,4 +87,16 @@ describe('Semigroup', () => {
     assert.strictEqual(result.foo, expected.foo)
     assert.strictEqual(result.bar, expected.bar)
   })
+
+  it('semigroupProduct', () => {
+    assert.strictEqual(semigroupProduct.concat(2, 3), 6)
+  })
+
+  it('getFirstSemigroup', () => {
+    assert.deepEqual(getFirstSemigroup<number>().concat(1, 2), 1)
+  })
+
+  it('semigroupVoid', () => {
+    assert.deepEqual(semigroupVoid.concat(undefined, undefined), undefined)
+  })
 })
diff --git a/test/Semiring.ts b/test/Semiring.ts
new file mode 100644
index 00000000..79aee898
--- /dev/null
+++ b/test/Semiring.ts
@@ -0,0 +1,17 @@
+import * as assert from 'assert'
+import { getFunctionSemiring } from '../src/Semiring'
+import { fieldNumber } from '../src/Field'
+
+describe('Semiring', () => {
+  it('getFunctionSemiring', () => {
+    const S = getFunctionSemiring<string, number>(fieldNumber)
+    const f1 = (s: string): number => s.length
+    const f2 = (s: string): number => s.indexOf('a')
+    assert.strictEqual(S.add(f1, f2)('foo'), 2)
+    assert.strictEqual(S.add(f1, f2)('fooa'), 7)
+    assert.strictEqual(S.zero(''), 0)
+    assert.strictEqual(S.one(''), 1)
+    assert.strictEqual(S.mul(f1, f2)('foo'), -3)
+    assert.strictEqual(S.mul(f1, f2)('fooa'), 12)
+  })
+})
diff --git a/test/Set.ts b/test/Set.ts
index 55e509ab..cdea7b9b 100644
--- a/test/Set.ts
+++ b/test/Set.ts
@@ -143,7 +143,10 @@ describe('Set', () => {
   })
 
   it('insert', () => {
-    assert.deepEqual(insert(setoidNumber)(3, new Set([1, 2])), new Set([1, 2, 3]))
+    const x = new Set([1, 2])
+    assert.deepEqual(insert(setoidNumber)(3, x), new Set([1, 2, 3]))
+    // should return the same ference if the element is already a member
+    assert.strictEqual(insert(setoidNumber)(2, x), x)
   })
 
   it('remove', () => {
diff --git a/test/State.ts b/test/State.ts
index b62c722e..dcb82724 100644
--- a/test/State.ts
+++ b/test/State.ts
@@ -1,7 +1,15 @@
 import * as assert from 'assert'
-import { State, get, gets, modify, put } from '../src/State'
+import { State, get, gets, modify, put, state } from '../src/State'
 
 describe('State', () => {
+  it('eval', () => {
+    assert.deepEqual(state.of(1).eval(0), 1)
+  })
+
+  it('exec', () => {
+    assert.deepEqual(state.of(1).exec(0), 0)
+  })
+
   it('put', () => {
     assert.deepEqual(put(2).run(1), [undefined, 2])
   })
@@ -22,13 +30,24 @@ describe('State', () => {
 
   it('map', () => {
     const double = (n: number) => n * 2
-    const state = new State<number, number>(s => [s - 1, s + 1])
-    assert.deepEqual(state.map(double).run(0), [-2, 1])
+    const x = new State<number, number>(s => [s - 1, s + 1])
+    assert.deepEqual(x.map(double).run(0), [-2, 1])
+    assert.deepEqual(state.map(x, double).run(0), [-2, 1])
+  })
+
+  it('ap', () => {
+    const double = (n: number) => n * 2
+    const fab = state.of(double)
+    const fa = state.of(1)
+    assert.deepEqual(fa.ap(fab).run(0), [2, 0])
+    assert.deepEqual(state.ap(fab, fa).run(0), [2, 0])
+    assert.deepEqual(fab.ap_(fa).run(0), [2, 0])
   })
 
   it('chain', () => {
     const f = (n: number) => new State<number, number>(s => [s - 1, s + 1])
-    const state = new State<number, number>(s => [s - 1, s + 1])
-    assert.deepEqual(state.chain(f).run(0), [0, 2])
+    const x = new State<number, number>(s => [s - 1, s + 1])
+    assert.deepEqual(x.chain(f).run(0), [0, 2])
+    assert.deepEqual(state.chain(x, f).run(0), [0, 2])
   })
 })
diff --git a/test/StateT.ts b/test/StateT.ts
index 9bd10370..bd8feee7 100644
--- a/test/StateT.ts
+++ b/test/StateT.ts
@@ -30,6 +30,18 @@ describe('StateT', () => {
     assert.deepEqual(stateTOption.map(double, state)(0), option.some([-2, 1]))
   })
 
+  it('of', () => {
+    const x = stateTOption.of(1)
+    assert.deepEqual(x(0), option.some([1, 0]))
+  })
+
+  it('ap', () => {
+    const double = (n: number) => n * 2
+    const fab = stateTOption.of(double)
+    const fa = stateTOption.of(1)
+    assert.deepEqual(stateTOption.ap(fab, fa)(0), option.some([2, 0]))
+  })
+
   it('chain', () => {
     const f = (n: number) => (s: number) => option.some<[number, number]>([s - 1, s + 1])
     const state = (s: number) => option.some<[number, number]>([s - 1, s + 1])
diff --git a/test/StrMap.ts b/test/StrMap.ts
index f4c07ada..3cb7aec9 100644
--- a/test/StrMap.ts
+++ b/test/StrMap.ts
@@ -35,8 +35,8 @@ describe('StrMap', () => {
   it('map', () => {
     const d1 = new StrMap<number>({ k1: 1, k2: 2 })
     const double = (n: number): number => n * 2
-    const d2 = d1.map(double)
-    assert.deepEqual(d2, new StrMap({ k1: 2, k2: 4 }))
+    assert.deepEqual(d1.map(double), new StrMap({ k1: 2, k2: 4 }))
+    assert.deepEqual(strmap.map(d1, double), new StrMap({ k1: 2, k2: 4 }))
   })
 
   it('reduce', () => {
@@ -44,6 +44,7 @@ describe('StrMap', () => {
     assert.strictEqual(d1.reduce('', (b, a) => b + a), 'ab')
     const d2 = new StrMap({ k2: 'b', k1: 'a' })
     assert.strictEqual(d2.reduce('', (b, a) => b + a), 'ab')
+    assert.strictEqual(strmap.reduce(d1, '', (b, a) => b + a), 'ab')
   })
 
   it('traverse', () => {
diff --git a/test/Task.ts b/test/Task.ts
index 1ca5440c..9852aaa1 100644
--- a/test/Task.ts
+++ b/test/Task.ts
@@ -2,7 +2,7 @@ import * as assert from 'assert'
 import { left, right } from '../src/Either'
 import { IO } from '../src/IO'
 import { monoidString } from '../src/Monoid'
-import { Task, fromIO, getMonoid, getRaceMonoid, tryCatch } from '../src/Task'
+import { Task, fromIO, getMonoid, getRaceMonoid, task, tryCatch } from '../src/Task'
 
 const delay = <A>(n: number, a: A): Task<A> =>
   new Task<A>(
@@ -12,6 +12,14 @@ const delay = <A>(n: number, a: A): Task<A> =>
       })
   )
 
+const delayReject = <A>(n: number, a: A): Task<A> =>
+  new Task<A>(
+    () =>
+      new Promise<A>((_, reject) => {
+        setTimeout(() => reject(a), n)
+      })
+  )
+
 describe('Task', () => {
   describe('getRaceMonoid', () => {
     const M = getRaceMonoid<number>()
@@ -30,6 +38,11 @@ describe('Task', () => {
         .run()
         .then(x => assert.strictEqual(x, 1))
     })
+    it('concat (rejected)', () => {
+      return M.concat(delayReject(10, 1), delayReject(10, 2))
+        .run()
+        .then(null, x => assert.strictEqual(x, 1))
+    })
   })
 
   describe('getMonoid', () => {
@@ -54,10 +67,10 @@ describe('Task', () => {
   it('map', () => {
     const t1 = delay(0, 1)
     const double = (n: number): number => n * 2
-    return t1
-      .map(double)
-      .run()
-      .then(x => assert.strictEqual(x, 2))
+    return Promise.all([t1.map(double).run(), task.map(t1, double).run()]).then(([x1, x2]) => {
+      assert.strictEqual(x1, 2)
+      assert.strictEqual(x1, x2)
+    })
   })
 
   it('chain', () => {
@@ -73,10 +86,11 @@ describe('Task', () => {
     const double = (n: number): number => n * 2
     const t1 = delay(0, 1)
     const t2 = delay(0, double)
-    return t1
-      .ap(t2)
-      .run()
-      .then(x => assert.strictEqual(x, 2))
+    return Promise.all([t1.ap(t2).run(), task.ap(t2, t1).run(), t2.ap_(t1).run()]).then(([x1, x2, x3]) => {
+      assert.strictEqual(x1, 2)
+      assert.strictEqual(x1, x2)
+      assert.strictEqual(x1, x3)
+    })
   })
 
   it('tryCatch', () => {
@@ -108,4 +122,9 @@ describe('Task', () => {
         assert.deepEqual(log, ['a', 'b'])
       })
   })
+
+  it('toString', () => {
+    assert.strictEqual(task.of(1).toString(), 'new Task(<function0>)')
+    assert.strictEqual(task.of(1).inspect(), 'new Task(<function0>)')
+  })
 })
diff --git a/test/TaskEither.ts b/test/TaskEither.ts
index ae5f72a3..d30cb704 100644
--- a/test/TaskEither.ts
+++ b/test/TaskEither.ts
@@ -1,10 +1,42 @@
 import * as assert from 'assert'
-import { left, right } from '../src/Either'
+import { left as eitherLeft, right as eitherRight } from '../src/Either'
 import { IO } from '../src/IO'
-import { Task } from '../src/Task'
-import { TaskEither, fromIO, fromLeft, right as fromTask, taskEither, taskify } from '../src/TaskEither'
+import { Task, task } from '../src/Task'
+import { TaskEither, fromIO, fromLeft, left, right, taskEither, taskify, tryCatch } from '../src/TaskEither'
 
 describe('TaskEither', () => {
+  it('ap', () => {
+    const double = (n: number): number => n * 2
+    const fab = taskEither.of(double)
+    const fa = taskEither.of(1)
+    return Promise.all([fa.ap(fab).run(), fab.ap_(fa).run(), taskEither.ap(fab, fa).run()]).then(([e1, e2, e3]) => {
+      assert.deepEqual(e1, eitherRight(2))
+      assert.deepEqual(e1, e2)
+      assert.deepEqual(e1, e3)
+    })
+  })
+
+  it('map', () => {
+    const double = (n: number): number => n * 2
+    return taskEither
+      .map(taskEither.of(1), double)
+      .run()
+      .then(e => {
+        assert.deepEqual(e, eitherRight(2))
+      })
+  })
+
+  it('mapLeft', () => {
+    const double = (n: number): number => n * 2
+    const fa = fromLeft(1)
+    return fa
+      .mapLeft(double)
+      .run()
+      .then(e => {
+        assert.deepEqual(e, eitherLeft(2))
+      })
+  })
+
   it('chain', () => {
     const te1 = taskEither.chain(
       taskEither.of<string, string>('foo'),
@@ -15,8 +47,8 @@ describe('TaskEither', () => {
       a => (a.length > 2 ? taskEither.of<string, number>(a.length) : fromLeft<string, number>('foo'))
     )
     return Promise.all([te1.run(), te2.run()]).then(([e1, e2]) => {
-      assert.deepEqual(e1, right(3))
-      assert.deepEqual(e2, left('foo'))
+      assert.deepEqual(e1, eitherRight(3))
+      assert.deepEqual(e2, eitherLeft('foo'))
     })
   })
 
@@ -34,11 +66,16 @@ describe('TaskEither', () => {
   it('bimap', () => {
     const f = (s: string): number => s.length
     const g = (n: number): boolean => n > 2
-    const teRight = taskEither.of<string, number>(1).bimap(f, g)
-    const teLeft = fromLeft<string, number>('foo').bimap(f, g)
-    return Promise.all([teRight.run(), teLeft.run()]).then(([e1, e2]) => {
-      assert.deepEqual(e1, right(false))
-      assert.deepEqual(e2, left(3))
+    const teRight = taskEither.of<string, number>(1)
+    const teLeft = fromLeft<string, number>('foo')
+    return Promise.all([
+      teRight.bimap(f, g).run(),
+      teLeft.bimap(f, g).run(),
+      taskEither.bimap(teRight, f, g).run()
+    ]).then(([e1, e2, e3]) => {
+      assert.deepEqual(e1, eitherRight(false))
+      assert.deepEqual(e2, eitherLeft(3))
+      assert.deepEqual(e1, e3)
     })
   })
 
@@ -48,29 +85,46 @@ describe('TaskEither', () => {
     const tl = l.orElse(l => taskEither.of<number, number>(l.length))
     const tr = r.orElse(() => taskEither.of<number, number>(2))
     return Promise.all([tl.run(), tr.run()]).then(([el, er]) => {
-      assert.deepEqual(el, right(3))
-      assert.deepEqual(er, right(1))
+      assert.deepEqual(el, eitherRight(3))
+      assert.deepEqual(er, eitherRight(1))
     })
   })
 
+  it('left', () => {
+    return left(task.of(1))
+      .run()
+      .then(e => {
+        assert.deepEqual(e, eitherLeft(1))
+      })
+  })
+
   it('applySecond', () => {
     const log: Array<string> = []
     const append = (message: string): TaskEither<string, number> =>
-      fromTask(new Task(() => Promise.resolve(log.push(message))))
+      right(new Task(() => Promise.resolve(log.push(message))))
     return append('a')
       .applySecond(append('b'))
       .run()
       .then(e => {
-        assert.deepEqual(e, right(2))
+        assert.deepEqual(e, eitherRight(2))
         assert.deepEqual(log, ['a', 'b'])
       })
   })
 
+  it('tryCatch', () => {
+    const ok = tryCatch(() => Promise.resolve(1), () => 'error')
+    const ko = tryCatch(() => Promise.reject(undefined), () => 'error')
+    return Promise.all([ok.run(), ko.run()]).then(([eok, eko]) => {
+      assert.deepEqual(eok, eitherRight(1))
+      assert.deepEqual(eko, eitherLeft('error'))
+    })
+  })
+
   it('fromIO', () => {
     const io = new IO(() => 1)
     const fa = fromIO(io)
     return fa.run().then(e => {
-      assert.deepEqual(e, right(1))
+      assert.deepEqual(e, eitherRight(1))
     })
   })
 
@@ -82,8 +136,8 @@ describe('TaskEither', () => {
       callback(new Error('ko'))
     }
     return Promise.all([taskify(api1)('foo').run(), taskify(api2)('foo').run()]).then(([e1, e2]) => {
-      assert.deepEqual(e1, right('ok'))
-      assert.deepEqual(e2, left(new Error('ko')))
+      assert.deepEqual(e1, eitherRight('ok'))
+      assert.deepEqual(e2, eitherLeft(new Error('ko')))
     })
   })
 })
diff --git a/test/These.ts b/test/These.ts
index c8926872..3d4bf5e6 100644
--- a/test/These.ts
+++ b/test/These.ts
@@ -12,9 +12,13 @@ import {
   these,
   theseLeft,
   theseRight,
-  this_
+  this_,
+  isThis,
+  isThat,
+  isBoth
 } from '../src/These'
 import { traverse } from '../src/Traversable'
+import { semigroupString } from '../src/Semigroup'
 
 describe('These', () => {
   it('getSetoid', () => {
@@ -46,17 +50,35 @@ describe('These', () => {
   })
 
   it('map', () => {
-    const { equals } = getSetoid(setoidNumber, setoidNumber)
     const double = (n: number) => n * 2
-    assert.strictEqual(equals(this_<number, number>(2).map(double), this_(2)), true)
-    assert.strictEqual(equals(that<number, number>(2).map(double), that(4)), true)
-    assert.strictEqual(equals(both(1, 2).map(double), both(1, 4)), true)
+    assert.deepEqual(this_<number, number>(2).map(double), this_(2))
+    assert.deepEqual(that<number, number>(2).map(double), that(4))
+    assert.deepEqual(both(1, 2).map(double), both(1, 4))
+    assert.deepEqual(these.map(both(1, 2), double), both(1, 4))
+  })
+
+  it('getMonad', () => {
+    const double = (n: number) => n * 2
+    const F = getMonad(semigroupString)
+    const fab = F.of(double)
+    const fa = F.of(1)
+    assert.deepEqual(F.ap(fab, fa), F.of(2))
+  })
+
+  it('fold', () => {
+    const double = (n: number) => n * 2
+    const len = (s: string) => s.length
+    const f = (s: string, n: number) => len(s) + double(n)
+    assert.strictEqual(this_<string, number>('foo').fold(len, double, f), 3)
+    assert.strictEqual(that<string, number>(1).fold(len, double, f), 2)
+    assert.strictEqual(both<string, number>('foo', 1).fold(len, double, f), 5)
   })
 
   it('bimap', () => {
     const len = (s: string): number => s.length
     const double = (n: number): number => n * 2
     assert.deepEqual(both('foo', 1).bimap(len, double), both(3, 2))
+    assert.deepEqual(these.bimap(both('foo', 1), len, double), both(3, 2))
   })
 
   it('fromThese', () => {
@@ -107,4 +129,49 @@ describe('These', () => {
     assert.deepEqual(theseRight(that(1)), some(1))
     assert.deepEqual(theseRight(both('foo', 1)), some(1))
   })
+
+  it('toString', () => {
+    assert.strictEqual(this_(1).toString(), 'this_(1)')
+    assert.strictEqual(this_(1).inspect(), 'this_(1)')
+    assert.strictEqual(that(1).toString(), 'that(1)')
+    assert.strictEqual(that(1).inspect(), 'that(1)')
+    assert.strictEqual(both('a', 1).toString(), 'both("a", 1)')
+    assert.strictEqual(both('a', 1).inspect(), 'both("a", 1)')
+  })
+
+  it('isThis', () => {
+    assert.strictEqual(this_(1).isThis(), true)
+    assert.strictEqual(that(1).isThis(), false)
+    assert.strictEqual(both('1', 1).isThis(), false)
+    assert.strictEqual(isThis(this_(1)), true)
+    assert.strictEqual(isThis(that(1)), false)
+    assert.strictEqual(isThis(both('1', 1)), false)
+  })
+
+  it('isThat', () => {
+    assert.strictEqual(this_(1).isThat(), false)
+    assert.strictEqual(that(1).isThat(), true)
+    assert.strictEqual(both('1', 1).isThat(), false)
+    assert.strictEqual(isThat(this_(1)), false)
+    assert.strictEqual(isThat(that(1)), true)
+    assert.strictEqual(isThat(both('1', 1)), false)
+  })
+
+  it('isBoth', () => {
+    assert.strictEqual(this_(1).isBoth(), false)
+    assert.strictEqual(that(1).isBoth(), false)
+    assert.strictEqual(both('1', 1).isBoth(), true)
+    assert.strictEqual(isBoth(this_(1)), false)
+    assert.strictEqual(isBoth(that(1)), false)
+    assert.strictEqual(isBoth(both('1', 1)), true)
+  })
+
+  it('reduce', () => {
+    assert.strictEqual(this_('b').reduce('a', (b, a) => b + a), 'a')
+    assert.strictEqual(these.reduce(this_('b'), 'a', (b, a) => b + a), 'a')
+    assert.strictEqual(that('b').reduce('a', (b, a) => b + a), 'ab')
+    assert.strictEqual(these.reduce(that('b'), 'a', (b, a) => b + a), 'ab')
+    assert.strictEqual(both(1, 'b').reduce('a', (b, a) => b + a), 'ab')
+    assert.strictEqual(these.reduce(both(1, 'b'), 'a', (b, a) => b + a), 'ab')
+  })
 })
diff --git a/test/Tuple.ts b/test/Tuple.ts
index 0055c234..0d87a60a 100644
--- a/test/Tuple.ts
+++ b/test/Tuple.ts
@@ -15,23 +15,73 @@ import {
   getOrd,
   getSemigroup,
   getSetoid,
-  tuple
+  tuple,
+  getMonoid
 } from '../src/Tuple'
 
 describe('Tuple', () => {
   it('compose', () => {
-    assert.deepEqual(new Tuple(true, 2).compose(new Tuple(1, 's')), new Tuple(true, 's'))
+    const x = new Tuple(true, 2)
+    const y = new Tuple(1, 's')
+    const z = new Tuple(true, 's')
+    assert.deepEqual(x.compose(y), z)
+    assert.deepEqual(tuple.compose(y, x), z)
   })
 
   it('map', () => {
     const double = (n: number): number => n * 2
-    assert.deepEqual(new Tuple('s', 1).map(double), new Tuple('s', 2))
+    const x = new Tuple('s', 1)
+    const y = new Tuple('s', 2)
+    assert.deepEqual(x.map(double), y)
+    assert.deepEqual(tuple.map(x, double), y)
+  })
+
+  it('extract', () => {
+    const x = new Tuple('a', 1)
+    assert.deepEqual(x.extract(), 1)
+    assert.deepEqual(tuple.extract(x), 1)
+  })
+
+  it('extend', () => {
+    const x = new Tuple('a', 1)
+    const f = (fa: Tuple<string, number>): number => fa.fst.length + fa.snd
+    const y = new Tuple('a', 2)
+    assert.deepEqual(x.extend(f), y)
+    assert.deepEqual(tuple.extend(x, f), y)
+  })
+
+  it('getApplicative', () => {
+    const F = getApplicative(monoidString)
+    const double = (n: number): number => n * 2
+    const x = F.of(double)
+    const y = new Tuple('a', 1)
+    const z = new Tuple('a', 2)
+    assert.deepEqual(F.ap(x, y), z)
+  })
+
+  it('getMonad', () => {
+    const M = getMonad(monoidString)
+    const f = (n: number) => M.of(n * 2)
+    const x = new Tuple('a', 1)
+    const y = new Tuple('a', 2)
+    assert.deepEqual(M.chain(x, f), y)
+  })
+
+  it('getMonoid', () => {
+    const M = getMonoid(monoidString, monoidSum)
+    const x = new Tuple('a', 1)
+    const y = new Tuple('b', 2)
+    const z = new Tuple('ab', 3)
+    assert.deepEqual(M.concat(x, y), z)
   })
 
   it('bimap', () => {
     const double = (n: number): number => n * 2
     const len = (s: string): number => s.length
-    assert.deepEqual(new Tuple('s', 1).bimap(len, double), new Tuple(1, 2))
+    const x = new Tuple('a', 1)
+    const y = new Tuple(1, 2)
+    assert.deepEqual(x.bimap(len, double), y)
+    assert.deepEqual(tuple.bimap(x, len, double), y)
   })
 
   it('getSemigroup', () => {
@@ -41,6 +91,21 @@ describe('Tuple', () => {
 
   it('toString', () => {
     assert.strictEqual(new Tuple('a', 1).toString(), `new Tuple("a", 1)`)
+    assert.strictEqual(new Tuple('a', 1).inspect(), `new Tuple("a", 1)`)
+  })
+
+  it('reduce', () => {
+    const x = new Tuple(1, 'b')
+    assert.strictEqual(x.reduce('a', (b, a) => b + a), 'ab')
+    assert.strictEqual(tuple.reduce(x, 'a', (b, a) => b + a), 'ab')
+  })
+
+  it('swap', () => {
+    assert.deepEqual(new Tuple('a', 1).swap(), new Tuple(1, 'a'))
+  })
+
+  it('toTuple', () => {
+    assert.deepEqual(new Tuple('a', 1).toTuple(), ['a', 1])
   })
 
   it('getApply', () => {
diff --git a/test/Validation.ts b/test/Validation.ts
index eed6b8b1..66c09bf1 100644
--- a/test/Validation.ts
+++ b/test/Validation.ts
@@ -1,42 +1,40 @@
 import * as assert from 'assert'
 import { array } from '../src/Array'
 import * as either from '../src/Either'
-import { monoidString } from '../src/Monoid'
+import { monoidString, monoidSum } from '../src/Monoid'
+import { none, option, some } from '../src/Option'
 import { getArraySemigroup, semigroupString } from '../src/Semigroup'
 import { setoidNumber, setoidString } from '../src/Setoid'
-import { sequence } from '../src/Traversable'
+import { sequence, traverse } from '../src/Traversable'
 import {
   failure,
   fromEither,
+  fromPredicate,
   getAlt,
   getApplicative,
   getMonad,
   getSemigroup,
   getSetoid,
-  success
+  isFailure,
+  isSuccess,
+  success,
+  validation,
+  getMonoid
 } from '../src/Validation'
 
 describe('Validation', () => {
-  it('chain', () => {
+  it('getMonad', () => {
     const M = getMonad(monoidString)
     const f = (s: string) => success<string, number>(s.length)
     assert.deepEqual(M.chain(success<string, string>('abc'), f), success(3))
     assert.deepEqual(M.chain(failure<string, string>('a'), f), failure('a'))
     assert.deepEqual(M.chain(failure<string, string>('a'), () => failure('b')), failure('a'))
-  })
-
-  it('of', () => {
-    const { of } = getApplicative(monoidString)
-    assert.deepEqual(of(1), success(1))
-  })
-
-  it('ap', () => {
-    const { ap } = getApplicative(monoidString)
+    assert.deepEqual(M.of(1), success(1))
     const double = (n: number) => n * 2
-    assert.deepEqual(ap(success(double), success(1)), success(2))
-    assert.deepEqual(ap(success(double), failure('foo')), failure('foo'))
-    assert.deepEqual(ap(failure<string, (n: number) => number>('foo'), success(1)), failure('foo'))
-    assert.deepEqual(ap(failure<string, (n: number) => number>('foo'), failure('bar')), failure('foobar'))
+    assert.deepEqual(M.ap(success(double), success(1)), success(2))
+    assert.deepEqual(M.ap(success(double), failure('foo')), failure('foo'))
+    assert.deepEqual(M.ap(failure<string, (n: number) => number>('foo'), success(1)), failure('foo'))
+    assert.deepEqual(M.ap(failure<string, (n: number) => number>('foo'), failure('bar')), failure('foobar'))
   })
 
   it('traverse', () => {
@@ -101,4 +99,79 @@ describe('Validation', () => {
     assert.deepEqual(alt.alt(success('a'), failure([1])), success('a'))
     assert.deepEqual(alt.alt(failure([1]), failure([2])), failure([1, 2]))
   })
+
+  it('reduce', () => {
+    assert.deepEqual(success('bar').reduce('foo', (b, a) => b + a), 'foobar')
+    assert.deepEqual(failure('bar').reduce('foo', (b, a) => b + a), 'foo')
+    assert.deepEqual(validation.reduce(success('bar'), 'foo', (b, a) => b + a), 'foobar')
+  })
+
+  it('mapFailure', () => {
+    const double = (n: number): number => n * 2
+    assert.deepEqual(success<number, string>('bar').mapFailure(double), success('bar'))
+    assert.deepEqual(failure<number, string>(2).mapFailure(double), failure(4))
+  })
+
+  it('toString', () => {
+    assert.strictEqual(success('bar').toString(), 'success("bar")')
+    assert.strictEqual(success('bar').inspect(), 'success("bar")')
+    assert.strictEqual(failure('bar').toString(), 'failure("bar")')
+    assert.strictEqual(failure('bar').inspect(), 'failure("bar")')
+  })
+
+  it('swap', () => {
+    assert.deepEqual(success('bar').swap(), failure('bar'))
+    assert.deepEqual(failure('bar').swap(), success('bar'))
+  })
+
+  it('isFailure', () => {
+    assert.strictEqual(success(1).isFailure(), false)
+    assert.strictEqual(failure(1).isFailure(), true)
+    assert.strictEqual(isFailure(success(1)), false)
+    assert.strictEqual(isFailure(failure(1)), true)
+  })
+
+  it('isSuccess', () => {
+    assert.strictEqual(success(1).isSuccess(), true)
+    assert.strictEqual(failure(1).isSuccess(), false)
+    assert.strictEqual(isSuccess(success(1)), true)
+    assert.strictEqual(isSuccess(failure(1)), false)
+  })
+
+  it('fold', () => {
+    const f = (s: string) => `failure${s.length}`
+    const g = (s: string) => `success${s.length}`
+    assert.strictEqual(failure<string, string>('abc').fold(f, g), 'failure3')
+    assert.strictEqual(success<string, string>('abc').fold(f, g), 'success3')
+  })
+
+  it('bimap', () => {
+    const f = (s: string): number => s.length
+    const g = (n: number): boolean => n > 2
+    assert.deepEqual(success<string, number>(1).bimap(f, g), success(false))
+    assert.deepEqual(failure<string, number>('foo').bimap(f, g), failure(3))
+    assert.deepEqual(validation.bimap(success<string, number>(1), f, g), success(false))
+  })
+
+  it('fromPredicate', () => {
+    const predicate = (n: number) => n >= 2
+    const handleError = (n: number) => `Invalid number ${n}`
+    const gt2 = fromPredicate(predicate, handleError)
+    assert.deepEqual(gt2(3), success(3))
+    assert.deepEqual(gt2(1), failure('Invalid number 1'))
+  })
+
+  it('traverse', () => {
+    assert.deepEqual(traverse(option, validation)(failure('foo'), a => (a >= 2 ? some(a) : none)), some(failure('foo')))
+    assert.deepEqual(traverse(option, validation)(success(1), a => (a >= 2 ? some(a) : none)), none)
+    assert.deepEqual(traverse(option, validation)(success(3), a => (a >= 2 ? some(a) : none)), some(success(3)))
+  })
+
+  it('getMonoid', () => {
+    const M = getMonoid(monoidString, monoidSum)
+    assert.deepEqual(M.concat(success(1), success(2)), success(3))
+    assert.deepEqual(M.concat(success(1), failure('foo')), failure('foo'))
+    assert.deepEqual(M.concat(failure('foo'), success(1)), failure('foo'))
+    assert.deepEqual(M.concat(failure('foo'), failure('bar')), failure('foobar'))
+  })
 })
diff --git a/test/Writer.ts b/test/Writer.ts
index 3f6ad1f7..3354b138 100644
--- a/test/Writer.ts
+++ b/test/Writer.ts
@@ -1,8 +1,32 @@
 import * as assert from 'assert'
-import { Writer, censor, listen, listens, pass, tell } from '../src/Writer'
+import { Writer, censor, listen, listens, pass, tell, getMonad, writer } from '../src/Writer'
 import { tuple } from '../src/function'
+import { monoidString } from '../src/Monoid'
 
 describe('Writer', () => {
+  it('eval', () => {
+    const fa = new Writer(() => [1, 'a'])
+    assert.strictEqual(fa.eval(), 1)
+  })
+
+  it('exec', () => {
+    const fa = new Writer(() => [1, 'a'])
+    assert.strictEqual(fa.exec(), 'a')
+  })
+
+  it('map', () => {
+    const fa = new Writer(() => [1, 'a'])
+    const double = (n: number): number => n * 2
+    assert.deepEqual(fa.map(double).run(), [2, 'a'])
+    assert.deepEqual(writer.map(fa, double).run(), [2, 'a'])
+  })
+
+  it('map', () => {
+    const fa = new Writer(() => [1, 'a'])
+    const double = (n: number): number => n * 2
+    assert.deepEqual(fa.map(double).run(), [2, 'a'])
+  })
+
   it('tell', () => {
     assert.deepEqual(tell(1).run(), [undefined, 1])
   })
@@ -26,4 +50,13 @@ describe('Writer', () => {
     const fa = new Writer(() => [1, ['a', 'b']])
     assert.deepEqual(censor(fa, w => w.filter(a => a !== 'a')).run(), [1, ['b']])
   })
+
+  it('getMonad', () => {
+    const M = getMonad(monoidString)
+    assert.deepEqual(M.of(1).run(), [1, ''])
+    const double = (n: number): number => n * 2
+    assert.deepEqual(M.ap(M.of(double), M.of(1)).run(), [2, ''])
+    const f = (n: number) => M.of(n * 2)
+    assert.deepEqual(M.chain(M.of(1), f).run(), [2, ''])
+  })
 })
diff --git a/test/function.ts b/test/function.ts
index 601f8bee..ac8788b1 100644
--- a/test/function.ts
+++ b/test/function.ts
@@ -1,5 +1,22 @@
 import * as assert from 'assert'
-import { and, apply, applyFlipped, compose, curry, flip, on, or, pipe } from '../src/function'
+import {
+  and,
+  apply,
+  applyFlipped,
+  compose,
+  curry,
+  flip,
+  identity,
+  on,
+  or,
+  pipe,
+  unsafeCoerce,
+  constTrue,
+  constFalse,
+  constNull,
+  constUndefined,
+  toString
+} from '../src/function'
 
 const f = (n: number) => n + 1
 const g = (n: number) => n * 2
@@ -97,4 +114,36 @@ describe('function', () => {
   it('applyFlipped', () => {
     assert.strictEqual(applyFlipped(2)(n => n * 2), 4)
   })
+
+  it('unsafeCoerce', () => {
+    assert.strictEqual(unsafeCoerce, identity)
+  })
+
+  it('constTrue', () => {
+    assert.strictEqual(constTrue(), true)
+  })
+
+  it('constFalse', () => {
+    assert.strictEqual(constFalse(), false)
+  })
+
+  it('constNull', () => {
+    assert.strictEqual(constNull(), null)
+  })
+
+  it('constUndefined', () => {
+    assert.strictEqual(constUndefined(), undefined)
+  })
+
+  it('toString', () => {
+    assert.strictEqual(toString('a'), '"a"')
+    const date = new Date()
+    assert.strictEqual(toString(date), `new Date('${date.toISOString()}')`)
+    assert.deepEqual(toString(['a', 'b']), '["a", "b"]')
+    assert.deepEqual(toString(() => {}), '<function0>')
+    assert.deepEqual(toString(function f() {}), 'f')
+    const nonStringifyable: { a?: any } = {}
+    nonStringifyable.a = nonStringifyable
+    assert.deepEqual(toString(nonStringifyable), '[object Object]')
+  })
 })