Repository: gcanti/fp-ts
Pull request #481 (2018-06-13 10:42:09)

 Docs: add support for @example tag to data
+ applicative validation example


Patch:
diff --git a/.prettierignore b/.prettierignore
new file mode 100644
index 00000000..126d17fe
--- /dev/null
+++ b/.prettierignore
@@ -0,0 +1,6 @@
+/lib
+/docs
+/docs-generator
+/perf
+/node_modules
+/typings-checker
\ No newline at end of file
diff --git a/.prettierrc b/.prettierrc
new file mode 100644
index 00000000..a5187b8f
--- /dev/null
+++ b/.prettierrc
@@ -0,0 +1,6 @@
+{
+  "semi": false,
+  "singleQuote": true,
+  "printWidth": 120,
+  "parser": "typescript"
+}
\ No newline at end of file
diff --git a/HKT.md b/HKT.md
index d5c0c105..21f9485a 100644
--- a/HKT.md
+++ b/HKT.md
@@ -107,7 +107,7 @@ Adding an entry means to leverage the module augmentation feature
 
 declare module './HKT' {
   interface URI2HKT<A> {
-    Identity: Identity<A> // maps the key "Identity" to the type constructor `Identity`
+    Identity: Identity<A> // maps the key "Identity" to the type `Identity`
   }
 }
 ```
diff --git a/README.md b/README.md
index 42766414..c9b5c180 100644
--- a/README.md
+++ b/README.md
@@ -34,52 +34,52 @@ npm install --save fp-ts
 
 # TypeScript compatibility
 
-The stable version is tested against TypeScript 2.8.x, but should run with TypeScript 2.2.2+ too
+The stable version is tested against TypeScript 2.9.x, but should run with TypeScript 2.2.2+ too
 
 # Ecosystem
 
-* [io-ts](https://github.com/gcanti/io-ts) - TypeScript compatible runtime type system for IO validation
-* [monocle-ts](https://github.com/gcanti/monocle-ts) - Functional optics: a (partial) porting of scala monocle to
+- [io-ts](https://github.com/gcanti/io-ts) - TypeScript compatible runtime type system for IO validation
+- [monocle-ts](https://github.com/gcanti/monocle-ts) - Functional optics: a (partial) porting of scala monocle to
   TypeScript
-* [newtype-ts](https://github.com/gcanti/newtype-ts) - Implementation of newtypes in TypeScript
-* [logging-ts](https://github.com/gcanti/logging-ts) - Composable loggers for TypeScript
-* [fp-ts-routing](https://github.com/gcanti/fp-ts-routing) - A type-safe bidirectional routing library for TypeScript
-* [parser-ts](https://github.com/gcanti/parser-ts) - String parser combinators for TypeScript
-* [remote-data-ts](https://github.com/devex-web-frontend/remote-data-ts) - RemoteData type (check [this article](https://medium.com/@gcanti/slaying-a-ui-antipattern-with-flow-5eed0cfb627b))
+- [newtype-ts](https://github.com/gcanti/newtype-ts) - Implementation of newtypes in TypeScript
+- [logging-ts](https://github.com/gcanti/logging-ts) - Composable loggers for TypeScript
+- [fp-ts-routing](https://github.com/gcanti/fp-ts-routing) - A type-safe bidirectional routing library for TypeScript
+- [parser-ts](https://github.com/gcanti/parser-ts) - String parser combinators for TypeScript
+- [remote-data-ts](https://github.com/devex-web-frontend/remote-data-ts) - RemoteData type (check [this article](https://medium.com/@gcanti/slaying-a-ui-antipattern-with-flow-5eed0cfb627b))
 
 ## Bindings
 
-* [fp-ts-rxjs](https://github.com/gcanti/fp-ts-rxjs) - fp-ts bindings for RxJS
-* [fp-ts-fluture](https://github.com/gcanti/fp-ts-fluture) - fp-ts bindings for Fluture
+- [fp-ts-rxjs](https://github.com/gcanti/fp-ts-rxjs) - fp-ts bindings for RxJS
+- [fp-ts-fluture](https://github.com/gcanti/fp-ts-fluture) - fp-ts bindings for Fluture
 
 # Documentation
 
-* [API Reference](docs/index.md)
+- [API Reference](docs/index.md)
 
 ## Internals
 
-* [How `HKT`, `URI2HKT`, `URIS` and `Type` work](HKT.md)
+- [How `HKT`, `URI2HKT`, `URIS` and `Type` work](HKT.md)
 
 ## Examples
 
-* [Free monad](examples/Free.ts)
-* [MTL-style](examples/mtl.ts)
-* OptionT monad transformer
-  * [ArrayOption](examples/ArrayOption.ts)
-  * [TaskOption](examples/TaskOption.ts)
-* EitherT monad transformer
-  * [EitherOption](examples/EitherOption.ts)
-* StateT monad transformer
-  * [StateIO](examples/StateIO.ts)
-  * [StateTaskEither](examples/StateTaskEither.ts)
-* ReaderT monad transformer
-  * [ReaderIO](examples/ReaderIO.ts)
-* Applicative composition
-  * [TaskValidation](examples/TaskValidation.ts)
-* [Type safe finite state machines with `IxIO`](examples/ixIO.ts)
-* [Moore machines](examples/Moore.ts)
-* [Debugging with `Trace`](examples/debugging-with-Trace.ts)
-* [fantas-eel-and-specification](fantas-eel-and-specification) - Code for the
+- [Free monad](examples/Free.ts)
+- [MTL-style](examples/mtl.ts)
+- OptionT monad transformer
+  - [ArrayOption](examples/ArrayOption.ts)
+  - [TaskOption](examples/TaskOption.ts)
+- EitherT monad transformer
+  - [EitherOption](examples/EitherOption.ts)
+- StateT monad transformer
+  - [StateIO](examples/StateIO.ts)
+  - [StateTaskEither](examples/StateTaskEither.ts)
+- ReaderT monad transformer
+  - [ReaderIO](examples/ReaderIO.ts)
+- Applicative composition
+  - [TaskValidation](examples/TaskValidation.ts)
+- [Type safe finite state machines with `IxIO`](examples/ixIO.ts)
+- [Moore machines](examples/Moore.ts)
+- [Debugging with `Trace`](examples/debugging-with-Trace.ts)
+- [fantas-eel-and-specification](fantas-eel-and-specification) - Code for the
   [Fantas, Eel, and Specification](http://www.tomharding.me/2017/03/03/fantas-eel-and-specification/) blog series on
   functional programming by Tom Harding
 
diff --git a/docs-generator/code2comment.html b/docs-generator/code2comment.html
index 64271dd4..a9b217f1 100644
--- a/docs-generator/code2comment.html
+++ b/docs-generator/code2comment.html
@@ -21,7 +21,7 @@
     }
     function t(src) {
       const lines = src.split('\n').map(line => '* ' + line.replace('../src/', 'fp-ts/lib/'))
-      return '*\n* Example\n*\n* ```ts\n' + lines.join('\n') + '\n* ```\n*'
+      return '*\n* @example\n*\n* ' + lines.join('\n') + '\n*'
     }
   </script>
 </body>
diff --git a/docs-generator/domain.ts b/docs-generator/domain.ts
index e164fa52..5366ea8b 100644
--- a/docs-generator/domain.ts
+++ b/docs-generator/domain.ts
@@ -25,7 +25,8 @@ export class Data {
     readonly signature: string,
     readonly description: Option<string>,
     readonly constructors: Array<Constructor>,
-    readonly since: string
+    readonly since: string,
+    readonly example: Option<string>
   ) {}
 }
 
diff --git a/docs-generator/main.ts b/docs-generator/main.ts
index 1085b372..4ad76dc0 100644
--- a/docs-generator/main.ts
+++ b/docs-generator/main.ts
@@ -7,6 +7,7 @@ import { Env, parseModule, ParseError } from './parser'
 import { write, indexOutputPath, readModule, writeModule } from './fs'
 import chalk from 'chalk'
 import Ast from 'ts-simple-ast'
+// tslint:disable-next-line:no-duplicate-imports
 import { SourceFile } from 'ts-simple-ast'
 
 const printError = (error: ParseError): string => {
diff --git a/docs-generator/markdown.ts b/docs-generator/markdown.ts
index 50734973..4da78d79 100644
--- a/docs-generator/markdown.ts
+++ b/docs-generator/markdown.ts
@@ -122,6 +122,7 @@ const printData = (d: Data): string => {
         .map(m => printMethod(m))
         .join('')
   }
+  s += printExample(d.example)
   return s
 }
 
diff --git a/docs-generator/parser.ts b/docs-generator/parser.ts
index 8646db94..6b0de992 100644
--- a/docs-generator/parser.ts
+++ b/docs-generator/parser.ts
@@ -168,10 +168,11 @@ const parseTypeAliasDeclarationData = (tad: TypeAliasDeclaration): ParseResult<E
       }
       const constructors = rights(eitherConstructors)
       const since = getSince(annotation)
+      const example = getExample(annotation)
       if (since.isNone()) {
         return ko(new SinceMissing(e.currentModuleName, dataName))
       } else {
-        return ok(new Data(dataName, signature, description, constructors, since.value))
+        return ok(new Data(dataName, signature, description, constructors, since.value, example))
       }
     }
     return notFound
@@ -189,10 +190,11 @@ const parseClassDeclarationData = (c: ClassDeclaration): ParseResult<Export> =>
       const methods = getClassMethods(c)
       const constructors = [new Constructor(dataName, methods)]
       const since = getSince(annotation)
+      const example = getExample(annotation)
       if (since.isNone()) {
         return ko(new SinceMissing(e.currentModuleName, dataName))
       } else {
-        return ok(new Data(dataName, signature, description, constructors, since.value))
+        return ok(new Data(dataName, signature, description, constructors, since.value, example))
       }
     }
     return notFound
diff --git a/docs/Validation.md b/docs/Validation.md
index 717cfba6..36564952 100644
--- a/docs/Validation.md
+++ b/docs/Validation.md
@@ -187,6 +187,46 @@ _Signature_
 (): string
 ```
 
+_Example_
+
+```ts
+import { Validation, getApplicative, success, failure } from 'fp-ts/lib/Validation'
+import { NonEmptyArray, getSemigroup } from 'fp-ts/lib/NonEmptyArray'
+
+interface Person {
+  readonly name: string
+  readonly age: number
+}
+
+// curried constructor
+const person = (name: string) => (age: number): Person => ({ name, age })
+
+// validators
+function validateName(input: string): Validation<NonEmptyArray<string>, string> {
+  return input.length === 0 ? failure(new NonEmptyArray('Invalid name: empty string', [])) : success(input)
+}
+function validateAge(input: string): Validation<NonEmptyArray<string>, number> {
+  const n = parseFloat(input)
+  if (isNaN(n)) {
+    return failure(new NonEmptyArray(`Invalid age: not a number ${input}`, []))
+  }
+  return n % 1 !== 0 ? failure(new NonEmptyArray(`Invalid age: not an integer ${n}`, [])) : success(n)
+}
+
+// get an `Applicative` instance for Validation<NonEmptyArray<string>, ?>
+const A = getApplicative(getSemigroup<string>())
+
+function validatePerson(input: Record<string, string>): Validation<NonEmptyArray<string>, Person> {
+  return A.ap(validateName(input['name']).map(person), validateAge(input['age']))
+}
+
+console.log(validatePerson({ name: '', age: '1.2' }))
+// failure(new NonEmptyArray("Invalid name: empty string", ["Invalid age: not an integer 1.2"]))
+
+console.log(validatePerson({ name: 'Giulio', age: '44' }))
+// success({ "name": "Giulio", "age": 44 })
+```
+
 ## Instances
 
 ### validation
diff --git a/examples/tsconfig.json b/examples/tsconfig.json
deleted file mode 100644
index 65fc306e..00000000
--- a/examples/tsconfig.json
+++ /dev/null
@@ -1,19 +0,0 @@
-{
-  "compilerOptions": {
-    "noEmit": true,
-    "declaration": true,
-    "module": "commonjs",
-    "strict": true,
-    "noImplicitReturns": false,
-    "noUnusedLocals": true,
-    "noUnusedParameters": false,
-    "noFallthroughCasesInSwitch": true,
-    "noEmitOnError": false,
-    "allowJs": false,
-    "target": "es5",
-    "moduleResolution": "node",
-    "forceConsistentCasingInFileNames": true,
-    "lib": ["es6", "dom"]
-  },
-  "include": ["../src/**/*", "./**/*"]
-}
diff --git a/exercises/answer1.ts b/exercises/answer1.ts
index c6174fe7..894b74c9 100644
--- a/exercises/answer1.ts
+++ b/exercises/answer1.ts
@@ -1,5 +1,4 @@
-import { Ord } from 'fp-ts/lib/Ord'
-import { ordNumber, ordString } from 'fp-ts/lib/Ord'
+import { Ord, ordNumber, ordString } from '../src/Ord'
 
 export function binarySearch<A>(xs: Array<A>, x: A, ord: Ord<A>): number {
   function go(low: number, mid: number, high: number): number {
diff --git a/exercises/answer2.ts b/exercises/answer2.ts
index 0fe6dc72..aa4fc5d6 100644
--- a/exercises/answer2.ts
+++ b/exercises/answer2.ts
@@ -1,5 +1,4 @@
-import { Ord } from 'fp-ts/lib/Ord'
-import { greaterThan, ordNumber } from 'fp-ts/lib/Ord'
+import { greaterThan, Ord, ordNumber } from '../src/Ord'
 
 export function isSorted<A>(xs: Array<A>, ord: Ord<A>): boolean {
   const len = xs.length
diff --git a/exercises/answer3.ts b/exercises/answer3.ts
index fb45f76c..e48fca3d 100644
--- a/exercises/answer3.ts
+++ b/exercises/answer3.ts
@@ -1,6 +1,6 @@
-import { Functor1 } from 'fp-ts/lib/Functor'
+import { Functor1 } from '../src/Functor'
 
-declare module 'fp-ts/lib/HKT' {
+declare module '../src/HKT' {
   interface URI2HKT<A> {
     List: List<A>
   }
diff --git a/exercises/answer4.ts b/exercises/answer4.ts
index 47829ef6..0394129c 100644
--- a/exercises/answer4.ts
+++ b/exercises/answer4.ts
@@ -1,4 +1,5 @@
-import { Option, none, option } from 'fp-ts/lib/Option'
+import { Either, left, right } from '../src/Either'
+import { none, Option, option } from '../src/Option'
 
 export function head<A>(xs: Array<A>): Option<A> {
   if (xs.length) {
@@ -10,17 +11,14 @@ export function head<A>(xs: Array<A>): Option<A> {
 console.log(head([1, 2, 3])) // => some(1)
 console.log(head([])) // => none
 
-import { Either } from 'fp-ts/lib/Either'
-import * as either from 'fp-ts/lib/Either'
-
 export function elementAt<A>(xs: Array<A>, i: number): Either<string, A> {
   if (i < 0) {
-    return either.left<string, A>('out of lower bound')
+    return left<string, A>('out of lower bound')
   }
   if (i >= xs.length) {
-    return either.left<string, A>('out of upper bound')
+    return left<string, A>('out of upper bound')
   }
-  return either.right<string, A>(xs[i])
+  return right<string, A>(xs[i])
 }
 
 console.log(elementAt([1, 2, 3], -1)) // => left('out of lower bound')
diff --git a/exercises/answer5.ts b/exercises/answer5.ts
index 35d148ed..61b41d7a 100644
--- a/exercises/answer5.ts
+++ b/exercises/answer5.ts
@@ -1,6 +1,6 @@
-import { Option, some, none, option } from 'fp-ts/lib/Option'
-import { array } from 'fp-ts/lib/Array'
-import { sequence } from 'fp-ts/lib/Traversable'
+import { Option, some, none, option } from '../src/Option'
+import { array } from '../src/Array'
+import { sequence } from '../src/Traversable'
 
 export function getAllSomesOrNone<A>(xs: Array<Option<A>>): Option<Array<A>> {
   return sequence(option, array)(xs)
diff --git a/exercises/answer6.ts b/exercises/answer6.ts
index 6461712e..cdb6d797 100644
--- a/exercises/answer6.ts
+++ b/exercises/answer6.ts
@@ -1,6 +1,6 @@
-import { array } from 'fp-ts/lib/Array'
-import { some, none } from 'fp-ts/lib/Option'
-import { tuple } from 'fp-ts/lib/function'
+import { array } from '../src/Array'
+import { some, none } from '../src/Option'
+import { tuple } from '../src/function'
 
 export function evens(count: number): Array<number> {
   let i = count
diff --git a/exercises/exercise2.ts b/exercises/exercise2.ts
index 0b020fa1..dcb5df74 100644
--- a/exercises/exercise2.ts
+++ b/exercises/exercise2.ts
@@ -6,6 +6,6 @@
 
 */
 
-import { Ord } from 'fp-ts/lib/Ord'
+import { Ord } from '../src/Ord'
 
 declare function isSorted<A>(xs: Array<A>, ord: Ord<A>): boolean
diff --git a/exercises/exercise4.ts b/exercises/exercise4.ts
index 0b8e7a8d..5ee68173 100644
--- a/exercises/exercise4.ts
+++ b/exercises/exercise4.ts
@@ -5,7 +5,7 @@
   Write a type safe head function.
 
 */
-import { Option } from 'fp-ts/lib/Option'
+import { Option } from '../src/Option'
 
 declare function head<A>(xs: Array<A>): Option<A>
 
@@ -14,6 +14,6 @@ declare function head<A>(xs: Array<A>): Option<A>
   Write a type safe elementAt function
 
 */
-import { Either } from 'fp-ts/lib/Either'
+import { Either } from '../src/Either'
 
 declare function elementAt<A>(xs: Array<A>, i: number): Either<string, A>
diff --git a/exercises/exercise5.ts b/exercises/exercise5.ts
index c8b0f01c..bd5de46b 100644
--- a/exercises/exercise5.ts
+++ b/exercises/exercise5.ts
@@ -8,6 +8,6 @@
   Some with a list of all the values
 
 */
-import { Option } from 'fp-ts/lib/Option'
+import { Option } from '../src/Option'
 
 declare function getAllSomesOrNone<A>(xs: Array<Option<A>>): Option<Array<A>>
diff --git a/exercises/tsconfig.json b/exercises/tsconfig.json
deleted file mode 100644
index ab297f58..00000000
--- a/exercises/tsconfig.json
+++ /dev/null
@@ -1,25 +0,0 @@
-{
-  "compilerOptions": {
-    "noStrictGenericChecks": true,
-    "declaration": true,
-    "module": "commonjs",
-    "noImplicitAny": true,
-    "noImplicitReturns": false,
-    "noImplicitThis": false,
-    "noUnusedLocals": false,
-    "noUnusedParameters": false,
-    "noFallthroughCasesInSwitch": true,
-    "noEmitOnError": false,
-    "allowJs": false,
-    "strictNullChecks": true,
-    "target": "es5",
-    "moduleResolution": "node",
-    "forceConsistentCasingInFileNames": true,
-    "lib": ["es6", "dom"],
-    "baseUrl": ".",
-    "paths": {
-      "fp-ts/lib/*": ["../src/*"],
-      "fp-ts": ["../src/index"]
-    }
-  }
-}
diff --git a/fantas-eel-and-specification/1.ts b/fantas-eel-and-specification/1.ts
index a504b127..11755dab 100644
--- a/fantas-eel-and-specification/1.ts
+++ b/fantas-eel-and-specification/1.ts
@@ -23,16 +23,16 @@ export type Line = {
 
 // however in order to add methods to Coord we have to change the enconding using `class`
 
+/** A coordinate in 3D space */
 export class Coord {
-  /** A coordinate in 3D space */
   constructor(readonly x: number, readonly y: number, readonly z: number) {}
   translate(deltaX: number, deltaY: number, deltaZ: number) {
     return new Coord(this.x + deltaX, this.y + deltaY, this.x + deltaZ)
   }
 }
 
+/** A line between two coordinates */
 export class Line {
-  /** A line between two coordinates */
   constructor(readonly from: Coord, readonly to: Coord) {}
 }
 
diff --git a/fantas-eel-and-specification/11.ts b/fantas-eel-and-specification/11.ts
index 1e39d664..349d80cd 100644
--- a/fantas-eel-and-specification/11.ts
+++ b/fantas-eel-and-specification/11.ts
@@ -17,8 +17,8 @@ export type URI = typeof URI
 export class Leaf<A> {
   static value = new Leaf<never>()
   readonly _tag: 'Leaf' = 'Leaf'
-  readonly '-A': A
-  readonly '-URI' = URI
+  readonly _A!: A
+  readonly _URI!: URI
   private constructor() {}
   inspect(): string {
     return this.toString()
@@ -30,8 +30,8 @@ export class Leaf<A> {
 
 export class Node<A> {
   readonly _tag: 'Node' = 'Node'
-  readonly '-A': A
-  readonly '-URI' = URI
+  readonly _A!: A
+  readonly _URI!: URI
   constructor(readonly left: BTree<A>, readonly value: A, readonly right: BTree<A>) {}
   inspect(): string {
     return this.toString()
diff --git a/fantas-eel-and-specification/16.ts b/fantas-eel-and-specification/16.ts
index e961f7bc..3a443126 100644
--- a/fantas-eel-and-specification/16.ts
+++ b/fantas-eel-and-specification/16.ts
@@ -7,8 +7,8 @@ export const URI = 'RoseTree'
 export type URI = typeof URI
 
 export class RoseTree<A> {
-  readonly '-A': A
-  readonly '-URI': URI
+  readonly _A!: A
+  readonly _URI!: URI
   constructor(readonly root: A, readonly forest: Array<RoseTree<A>>) {}
   map<B>(f: (a: A) => B): RoseTree<B> {
     return new RoseTree(f(this.root), this.forest.map(rt => rt.map(f)))
@@ -25,7 +25,9 @@ console.log(
     myTree.extend(rt => {
       return rt.forest.length < 1
         ? { value: rt.root, color: 'RED' }
-        : rt.forest.length < 5 ? { value: rt.root, color: 'ORANGE' } : { value: rt.root, color: 'GREEN' }
+        : rt.forest.length < 5
+          ? { value: rt.root, color: 'ORANGE' }
+          : { value: rt.root, color: 'GREEN' }
     }),
     null,
     2
diff --git a/fantas-eel-and-specification/2.ts b/fantas-eel-and-specification/2.ts
index 59dc2b75..bc691b5f 100644
--- a/fantas-eel-and-specification/2.ts
+++ b/fantas-eel-and-specification/2.ts
@@ -2,14 +2,13 @@
 // Code for http://www.tomharding.me/2017/03/08/fantas-eel-and-specification-2/
 //
 
-import { zipWith } from '../src/Array'
+import { filter, zipWith } from '../src/Array'
+import { Setoid } from '../src/Setoid'
 
 const sum = (x: number, y: number) => x + y
 console.log(zipWith([1, 2], [4, 5, 6], sum))
 // => [5, 7]
 
-import { filter } from '../src/Array'
-
 const gt2 = (n: number) => n > 2
 console.log(filter([1, 2, 3], gt2))
 // => [3]
@@ -24,8 +23,6 @@ export function naiveEquals<A>(xs: Array<A>, ys: Array<A>): boolean {
 
 // let's write a better equals...
 
-import { Setoid } from '../src/Setoid'
-
 export const equals = <A>(S: Setoid<A>) => (xs: Array<A>) => (ys: Array<A>): boolean =>
   xs.length === ys.length && xs.every((x, i) => S.equals(x, ys[i])) // <- this too restrictive
 
diff --git a/fantas-eel-and-specification/3.5.ts b/fantas-eel-and-specification/3.5.ts
index e4b09251..60ac1909 100644
--- a/fantas-eel-and-specification/3.5.ts
+++ b/fantas-eel-and-specification/3.5.ts
@@ -2,15 +2,13 @@
 // Code for http://www.tomharding.me/2017/04/09/fantas-eel-and-specification-3.5/
 //
 
-import { Ord, lessThanOrEq } from '../src/Ord'
 import { fold } from '../src/Array'
+import { lessThanOrEq, Ord, ordNumber } from '../src/Ord'
 
 export const lte = <A>(ord: Ord<A>) => (xs: Array<A>, ys: Array<A>): boolean =>
   fold(xs, true, (head, tail) =>
     fold(ys, false, (head_, tail_) => lessThanOrEq(ord)(head, head_) && lte(ord)(tail, tail_))
   )
 
-import { ordNumber } from '../src/Ord'
-
 console.log(lte(ordNumber)([1, 2], [1])) // => false
 console.log(lte(ordNumber)([1], [1, 2])) // => true
diff --git a/fantas-eel-and-specification/3.ts b/fantas-eel-and-specification/3.ts
index 97b10816..31bd6fce 100644
--- a/fantas-eel-and-specification/3.ts
+++ b/fantas-eel-and-specification/3.ts
@@ -2,7 +2,8 @@
 // Code for http://www.tomharding.me/2017/03/09/fantas-eel-and-specification-3/
 //
 
-import { Setoid } from '../src/Setoid'
+import { findIndex } from '../src/Array'
+import { Setoid, setoidNumber } from '../src/Setoid'
 
 export class Coord {
   /** A coordinate in 3D space */
@@ -72,9 +73,6 @@ export const notEquals = <A>(S: Setoid<A>) => (x: A) => (y: A): boolean => !S.eq
 // nub implementation
 //
 
-import { findIndex } from '../src/Array'
-import { setoidNumber } from '../src/Setoid'
-
 const nub = <A>(S: Setoid<A>) => (xs: Array<A>): Array<A> =>
   xs.filter((x, i) => findIndex(xs, (a: A) => S.equals(x, a)).exists(j => i === j))
 
diff --git a/fantas-eel-and-specification/4.ts b/fantas-eel-and-specification/4.ts
index 86062cc0..99b5e7c4 100644
--- a/fantas-eel-and-specification/4.ts
+++ b/fantas-eel-and-specification/4.ts
@@ -2,7 +2,7 @@
 // Code for http://www.tomharding.me/2017/03/13/fantas-eel-and-specification-4/
 //
 
-import { Semigroup } from '../src/Semigroup'
+import { fold, Semigroup } from '../src/Semigroup'
 
 export const semigroupString: Semigroup<string> = {
   concat: (x, y) => x + y // string concatenation
@@ -140,11 +140,9 @@ console.log(mySemigroup.concat(me, oldMe))
   name: 'Giulio',
   favouriteThings: [ 'climbing', 'math' ],
   registrationDate: 10,
-  hasMadePurchase: true }
+  hasMadePurchase: true
+}
 */
 
 // What if we want to merge more than two customers?
-
-import { fold } from '../src/Semigroup'
-
 console.log(fold(merge(strategy, iso))(me)([oldMe /* ... */]))
diff --git a/fantas-eel-and-specification/5.ts b/fantas-eel-and-specification/5.ts
index cb52f940..1820436b 100644
--- a/fantas-eel-and-specification/5.ts
+++ b/fantas-eel-and-specification/5.ts
@@ -2,8 +2,9 @@
 // Code for http://www.tomharding.me/2017/03/21/fantas-eel-and-specification-5/
 //
 
+import { fold, getFunctionMonoid, monoidAll, monoidString, monoidSum } from '../src/Monoid'
 import { getFirstMonoid, none, some } from '../src/Option'
-import { monoidSum } from '../src/Monoid'
+import { getMonoid } from '../src/Tuple'
 
 const firstMonoid = getFirstMonoid<number>()
 
@@ -13,8 +14,6 @@ console.log(firstMonoid.concat(none, some(1)))
 console.log(firstMonoid.concat(some(1), some(2)))
 // => some(1)
 
-import { fold } from '../src/Monoid'
-
 const sumAll = fold(monoidSum)
 
 console.log(sumAll([1, 2, 3, 4, 5]))
@@ -23,16 +22,11 @@ console.log(sumAll([1, 2, 3, 4, 5]))
 console.log(sumAll([]))
 // => 0
 
-import { getMonoid } from '../src/Tuple'
-import { monoidString } from '../src/Monoid'
-
 const monoidTuple = getMonoid(monoidSum, monoidString)
 
 console.log(monoidTuple.empty)
 // => new Tuple([0, ""])
 
-import { getFunctionMonoid, monoidAll } from '../src/Monoid'
-
 export const monoidFunction = getFunctionMonoid(monoidAll)<string>()
 
 export const gt2 = (s: string) => s.length > 2
diff --git a/fantas-eel-and-specification/6.ts b/fantas-eel-and-specification/6.ts
index 699a4540..92bd1a84 100644
--- a/fantas-eel-and-specification/6.ts
+++ b/fantas-eel-and-specification/6.ts
@@ -17,8 +17,8 @@ export const URI = 'MyType'
 export type URI = typeof URI
 
 export class MyType<A> {
-  readonly '-A': A
-  readonly '-URI': URI
+  readonly _A!: A
+  readonly _URI!: URI
   constructor(readonly value: A) {}
 }
 
diff --git a/fantas-eel-and-specification/9.ts b/fantas-eel-and-specification/9.ts
index 7b227199..dc6b68bf 100644
--- a/fantas-eel-and-specification/9.ts
+++ b/fantas-eel-and-specification/9.ts
@@ -2,9 +2,12 @@
 // Code for http://www.tomharding.me/2017/04/17/fantas-eel-and-specification-9/
 //
 
-import { liftA2 } from '../src/Apply'
 import { Applicative, Applicative1 } from '../src/Applicative'
-import { HKT, URIS, Type } from '../src/HKT'
+import { liftA2 } from '../src/Apply'
+import { HKT, Type, URIS } from '../src/HKT'
+import { getArrayMonoid, Monoid } from '../src/Monoid'
+import { getMonoid as getOptionMonoid, none, option, some } from '../src/Option'
+import * as tuple from '../src/Tuple'
 
 export const append = <A>(y: A) => (xs: Array<A>) => xs.concat([y])
 
@@ -12,12 +15,9 @@ export const append = <A>(y: A) => (xs: Array<A>) => xs.concat([y])
 export function insideOut<F extends URIS>(F: Applicative1<F>): <A>(xs: Array<Type<F, A>>) => Type<F, Array<A>>
 export function insideOut<F>(F: Applicative<F>): <A>(xs: Array<HKT<F, A>>) => HKT<F, Array<A>>
 export function insideOut<F>(F: Applicative<F>): <A>(xs: Array<HKT<F, A>>) => HKT<F, Array<A>> {
-  return <A>(xs: Array<HKT<F, A>>) =>
-    xs.reduce((acc, x) => liftA2(F)<A, Array<A>, Array<A>>(append)(x)(acc), F.of<A[]>([]))
+  return xs => xs.reduce((acc, x) => liftA2(F)(append)(x)(acc), F.of([]))
 }
 
-import { option, some, none, getMonoid as getOptionMonoid } from '../src/Option'
-
 console.log(insideOut(option)([some(2), some(10), some(3)])) // => some([2, 10, 3])
 
 console.log(insideOut(option)([some(2), none, some(3)])) // => none
@@ -26,15 +26,11 @@ console.log(insideOut(option)([some(2), none, some(3)])) // => none
 // Monoids from Applicatives
 //
 
-import { Monoid } from '../src/Monoid'
-
 export const getMonoid = <F extends URIS, A>(F: Applicative1<F>, M: Monoid<A>): Monoid<Type<F, A>> => ({
   concat: (x, y) => liftA2(F)((x: A) => (y: A) => M.concat(x, y))(x)(y),
   empty: F.of(M.empty)
 })
 
-import { getArrayMonoid } from '../src/Monoid'
-
 const monoidArrayNumber = getArrayMonoid<number>()
 
 // Usual implementation:
@@ -59,8 +55,6 @@ console.log(monoid.concat(none, none)) // => none
 // Tuple
 //
 
-import * as tuple from '../src/Tuple'
-
 const monoidTuple = tuple.getApplicative(monoidArrayNumber)
 
 console.log(monoidTuple.of(2)) // => new Tuple([[], 2])
diff --git a/fantas-eel-and-specification/tsconfig.json b/fantas-eel-and-specification/tsconfig.json
deleted file mode 100644
index fea9c116..00000000
--- a/fantas-eel-and-specification/tsconfig.json
+++ /dev/null
@@ -1,15 +0,0 @@
-{
-  "compilerOptions": {
-    "noEmit": true,
-    "target": "es5",
-    "module": "commonjs",
-    "moduleResolution": "node",
-    "strict": true,
-    "noImplicitReturns": false,
-    "noUnusedLocals": true,
-    "noUnusedParameters": false,
-    "noFallthroughCasesInSwitch": true,
-    "forceConsistentCasingInFileNames": true,
-    "lib": ["es6", "dom"]
-  }
-}
diff --git a/package-lock.json b/package-lock.json
index aeba2cac..275088fb 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -6376,9 +6376,9 @@
       }
     },
     "typescript": {
-      "version": "2.9.1",
-      "resolved": "https://registry.npmjs.org/typescript/-/typescript-2.9.1.tgz",
-      "integrity": "sha512-h6pM2f/GDchCFlldnriOhs1QHuwbnmj6/v7499eMHqPeW4V2G0elua2eIc2nu8v2NdHV0Gm+tzX83Hr6nUFjQA==",
+      "version": "2.9.2",
+      "resolved": "https://registry.npmjs.org/typescript/-/typescript-2.9.2.tgz",
+      "integrity": "sha512-Gr4p6nFNaoufRIY4NMdpQRNmgxVIGMs4Fcu/ujdYk3nAZqk7supzBE9idmvfZIlH/Cuj//dvi+019qEue9lV0w==",
       "dev": true
     },
     "typings-checker": {
diff --git a/package.json b/package.json
index fdc7ff75..8ce38e4b 100644
--- a/package.json
+++ b/package.json
@@ -11,8 +11,8 @@
     "lint": "tslint -p tsconfig.json src/**/*.ts test/**/*.ts examples/**/*.ts exercises/**/*.ts fantas-eel-and-specification/**/*.ts",
     "jest": "jest --ci",
     "jest-coverage": "jest --ci --coverage",
-    "prettier": "prettier --no-semi --single-quote --print-width 120 --parser typescript --list-different \"{src,test,examples,exercises}/**/*.ts\"",
-    "fix-prettier": "prettier --no-semi --single-quote --print-width 120 --parser typescript --write \"{src,test,examples,exercises}/**/*.ts\"",
+    "prettier": "prettier --list-different \"./{src,test,examples,exercises}/**/*.ts\"",
+    "fix-prettier": "prettier --write \"./{src,test,examples,exercises}/**/*.ts\"",
     "typings": "typings-checker --project ./typings-checker/tsconfig.json ./typings-checker/index.ts",
     "test": "npm run lint && npm run prettier && npm run typings && npm run jest",
     "clean": "rimraf lib/*",
@@ -45,7 +45,7 @@
     "ts-simple-ast": "0.74.0",
     "tslint": "5.9.1",
     "tslint-config-standard": "7.0.0",
-    "typescript": "2.9.1",
+    "typescript": "^2.9.2",
     "typings-checker": "2.0.0"
   },
   "tags": [
diff --git a/src/Array.ts b/src/Array.ts
index a19b4d20..0fea391a 100644
--- a/src/Array.ts
+++ b/src/Array.ts
@@ -14,7 +14,9 @@ import { Plus1 } from './Plus'
 import { Setoid, getArraySetoid } from './Setoid'
 import { Traversable1 } from './Traversable'
 import { Unfoldable1 } from './Unfoldable'
-import { Endomorphism, Predicate, Refinement, concat, identity, tuple } from './function'
+import { Endomorphism, Predicate, Refinement, concat, tuple } from './function'
+import { Compactable1, Separated } from './Compactable'
+import { Filterable1 } from './Filterable'
 
 // Adapted from https://github.com/purescript/purescript-arrays
 
@@ -179,25 +181,6 @@ const extend = <A, B>(fa: Array<A>, f: (fa: Array<A>) => B): Array<B> => {
   return fa.map((_, i, as) => f(as.slice(i)))
 }
 
-/**
- * @function
- * @since 1.0.0
- */
-export const partitionMap = <A, L, R>(fa: Array<A>, f: (a: A) => Either<L, R>): { left: Array<L>; right: Array<R> } => {
-  const left: Array<L> = []
-  const right: Array<R> = []
-  const len = fa.length
-  for (let i = 0; i < len; i++) {
-    const v = f(fa[i])
-    if (v.isLeft()) {
-      left.push(v.value)
-    } else {
-      right.push(v.value)
-    }
-  }
-  return { left, right }
-}
-
 /**
  * @example
  * flatten([[1], [2], [3]]) // [1, 2, 3]
@@ -520,23 +503,6 @@ export const findLast = <A>(as: Array<A>, predicate: Predicate<A>): Option<A> =>
   return fromNullable(a)
 }
 
-/**
- * Filter an array, keeping the elements which satisfy a predicate function, creating a new array
- * @function
- * @since 1.0.0
- */
-export const filter = <A>(as: Array<A>, predicate: Predicate<A>): Array<A> => {
-  const l = as.length
-  const r = []
-  for (let i = 0; i < l; i++) {
-    const v = as[i]
-    if (predicate(v)) {
-      r.push(v)
-    }
-  }
-  return r
-}
-
 /**
  * @function
  * @since 1.0.0
@@ -634,33 +600,6 @@ export const reverse = <A>(as: Array<A>): Array<A> => {
   return copy(as).reverse()
 }
 
-/**
- * Apply a function to each element in an array, keeping only the results
- * which contain a value, creating a new array
- * @function
- * @since 1.0.0
- */
-export const mapOption = <A, B>(as: Array<A>, f: (a: A) => Option<B>): Array<B> => {
-  const r: Array<B> = []
-  const len = as.length
-  for (let i = 0; i < len; i++) {
-    const v = f(as[i])
-    if (v.isSome()) {
-      r.push(v.value)
-    }
-  }
-  return r
-}
-
-/**
- * Filter an array of optional values, keeping only the elements which contain a value, creating a new array
- * @function
- * @since 1.0.0
- */
-export const catOptions = <A>(as: Array<Option<A>>): Array<A> => {
-  return mapOption(as, identity)
-}
-
 /**
  * Extracts from a list of `Either` all the `Right` elements. All the `Right` elements are extracted in order
  * @function
@@ -802,15 +741,115 @@ export const sortBy1 = <A>(head: Ord<A>, tail: Array<Ord<A>>): Endomorphism<Arra
   return sort(tail.reduce(getSemigroup<A>().concat, head))
 }
 
+const filterMap = <A, B>(fa: A[], f: (a: A) => Option<B>): B[] => {
+  const result: B[] = []
+  for (const a of fa) {
+    const optionB = f(a)
+    if (optionB.isSome()) {
+      result.push(optionB.value)
+    }
+  }
+  return result
+}
+/**
+ * @function
+ * @since 1.0.0
+ */
+export const partitionMap = <A, L, R>(fa: A[], f: (a: A) => Either<L, R>): Separated<L[], R[]> => {
+  const left: L[] = []
+  const right: R[] = []
+  for (const a of fa) {
+    const e = f(a)
+    if (e.isLeft()) {
+      left.push(e.value)
+    } else {
+      right.push(e.value)
+    }
+  }
+  return {
+    left,
+    right
+  }
+}
+/**
+ * Filter an array, keeping the elements which satisfy a predicate function, creating a new array
+ * @function
+ * @since 1.0.0
+ */
+export const filter = <A>(fa: A[], p: Predicate<A>): A[] => fa.filter(p)
+const partition = <A>(fa: A[], p: Predicate<A>): Separated<A[], A[]> => {
+  const left: A[] = []
+  const right: A[] = []
+  for (const a of fa) {
+    if (p(a)) {
+      right.push(a)
+    } else {
+      left.push(a)
+    }
+  }
+  return {
+    left,
+    right
+  }
+}
+const compact = <A>(fa: Option<A>[]): A[] => {
+  const result: A[] = []
+  for (const optionA of fa) {
+    if (optionA.isSome()) {
+      result.push(optionA.value)
+    }
+  }
+  return result
+}
+const separate = <RL, RR>(fa: Either<RL, RR>[]): Separated<RL[], RR[]> => {
+  const left: RL[] = []
+  const right: RR[] = []
+  for (const e of fa) {
+    if (e.isLeft()) {
+      left.push(e.value)
+    } else {
+      right.push(e.value)
+    }
+  }
+  return {
+    left,
+    right
+  }
+}
+
+/**
+ * Filter an array of optional values, keeping only the elements which contain a value, creating a new array
+ * Alias for {@link Compactable.compact}
+ * @function
+ * @since 1.0.0
+ */
+export const catOptions = compact
+
+/**
+ * Apply a function to each element in an array, keeping only the results which contain a value, creating a new array
+ * Alias for {@link Filterable.filterMap}
+ * @function
+ * @since 1.0.0
+ */
+export const mapOption = filterMap
+
 export const array: Monad1<URI> &
   Foldable1<URI> &
   Unfoldable1<URI> &
   Traversable1<URI> &
   Alternative1<URI> &
   Plus1<URI> &
-  Extend1<URI> = {
+  Extend1<URI> &
+  Compactable1<URI> &
+  Filterable1<URI> = {
   URI,
   map,
+  compact,
+  separate,
+  filter,
+  filterMap,
+  partition,
+  partitionMap,
   of,
   ap,
   chain,
diff --git a/src/Compactable.ts b/src/Compactable.ts
new file mode 100644
index 00000000..a5e6e894
--- /dev/null
+++ b/src/Compactable.ts
@@ -0,0 +1,88 @@
+import { HKT, Type, Type2, Type3, URIS, URIS2, URIS3 } from './HKT'
+import { Option } from './Option'
+import { Either } from './Either'
+
+/**
+ * `Separated` type which holds `left` and `right` parts
+ * @since 1.7.0
+ */
+export type Separated<A, B> = {
+  readonly left: A
+  readonly right: B
+}
+
+/**
+ * @typeclass
+ * `Compactable` represents data structures which can be _compacted_/_filtered_.
+ * This is a generalization of catOptions as a new function `compact`. `compact` has relations with {@link Functor}, {@link Applicative}, {@link Monad}, {@link Plus}, and {@link Traversable} in that we can use these classes to provide the ability to operate on a data type by eliminating intermediate Nones. This is useful for representing the filtering out of values, or failure.
+ * - {@link compact}
+ * - {@link separate}
+ * @since 1.7.0
+ * @see https://github.com/LiamGoodacre/purescript-filterable/blob/master/src/Data/Compactable.purs
+ */
+export interface Compactable<F> {
+  readonly URI: F
+  /**
+   * Compacts data structure unwrapping inner {@link Option}
+   * @since 1.7.0
+   */
+  readonly compact: <A>(fa: HKT<F, Option<A>>) => HKT<F, A>
+  /**
+   * Separates data structure moving inner {@link Left} to the left side and inner {@link Right} to the right side of {@link Separated}
+   * @since 1.7.0
+   */
+  readonly separate: <A, B>(fa: HKT<F, Either<A, B>>) => Separated<HKT<F, A>, HKT<F, B>>
+}
+
+/**
+ * @see Compactable
+ * @since 1.7.0
+ */
+export interface Compactable1<F extends URIS> {
+  readonly URI: F
+  readonly compact: <A>(fa: Type<F, Option<A>>) => Type<F, A>
+  readonly separate: <A, B>(fa: Type<F, Either<A, B>>) => Separated<Type<F, A>, Type<F, B>>
+}
+
+/**
+ * @see Compactable
+ * @since 1.7.0
+ */
+export interface Compactable2<F extends URIS2> {
+  readonly URI: F
+  readonly compact: <L, A>(fa: Type2<F, L, Option<A>>) => Type2<F, L, A>
+  readonly separate: <L, A, B>(fa: Type2<F, L, Either<A, B>>) => Separated<Type2<F, L, A>, Type2<F, L, B>>
+}
+
+/**
+ * @see Compactable
+ * @since 1.7.0
+ */
+export interface Compactable2C<F extends URIS2, L> {
+  readonly URI: F
+  readonly _L: L
+  readonly compact: <A>(fa: Type2<F, L, Option<A>>) => Type2<F, L, A>
+  readonly separate: <A, B>(fa: Type2<F, L, Either<A, B>>) => Separated<Type2<F, L, A>, Type2<F, L, B>>
+}
+
+/**
+ * @see Compactable
+ * @since 1.7.0
+ */
+export interface Compactable3<F extends URIS3> {
+  readonly URI: F
+  readonly compact: <U, L, A>(fa: Type3<F, U, L, Option<A>>) => Type3<F, U, L, A>
+  readonly separate: <U, L, A, B>(fa: Type3<F, U, L, Either<A, B>>) => Separated<Type3<F, U, L, A>, Type3<F, U, L, B>>
+}
+
+/**
+ * @see Compactable
+ * @since 1.7.0
+ */
+export interface Compactable3C<F extends URIS3, U, L> {
+  readonly URI: F
+  readonly _L: L
+  readonly _U: U
+  readonly compact: <A>(fa: Type3<F, U, L, Option<A>>) => Type3<F, U, L, A>
+  readonly separate: <A, B>(fa: Type3<F, U, L, Either<A, B>>) => Separated<Type3<F, U, L, A>, Type3<F, U, L, B>>
+}
diff --git a/src/Const.ts b/src/Const.ts
index f6738a32..f1d75f1b 100644
--- a/src/Const.ts
+++ b/src/Const.ts
@@ -77,7 +77,7 @@ export const getApply = <L>(S: Semigroup<L>): Apply2C<URI, L> => {
   }
 }
 
-const of = <L>(M: Monoid<L>) => <A>(b: A): Const<L, A> => {
+const of = <L>(M: Monoid<L>) => <A>(a: A): Const<L, A> => {
   return new Const(M.empty)
 }
 
diff --git a/src/Either.ts b/src/Either.ts
index 5b266454..b40eb16b 100644
--- a/src/Either.ts
+++ b/src/Either.ts
@@ -4,13 +4,16 @@ import { Bifunctor2 } from './Bifunctor'
 import { ChainRec2, tailRec } from './ChainRec'
 import { Extend2 } from './Extend'
 import { Foldable2 } from './Foldable'
-import { Lazy, Predicate, Refinement, toString } from './function'
+import { Lazy, phantom, Predicate, Refinement, toString } from './function'
 import { HKT } from './HKT'
 import { Monad2 } from './Monad'
 import { Option } from './Option'
 import { Setoid } from './Setoid'
 import { Traversable2 } from './Traversable'
 import { Validation } from './Validation'
+import { Compactable2C, Separated } from './Compactable'
+import { Monoid } from './Monoid'
+import { Filterable2C } from './Filterable'
 
 declare module './HKT' {
   interface URI2HKT2<L, A> {
@@ -55,6 +58,9 @@ export type URI = typeof URI
  */
 export type Either<L, A> = Left<L, A> | Right<L, A>
 
+/**
+ * Left side of {@link Either}
+ */
 export class Left<L, A> {
   readonly _tag: 'Left' = 'Left'
   readonly _A!: A
@@ -170,6 +176,9 @@ export class Left<L, A> {
   }
 }
 
+/**
+ * Right side of {@link Either}
+ */
 export class Right<L, A> {
   readonly _tag: 'Right' = 'Right'
   readonly _A!: A
@@ -422,6 +431,116 @@ export const isRight = <L, A>(fa: Either<L, A>): fa is Right<L, A> => {
   return fa.isRight()
 }
 
+/**
+ * Builds {@link Compactable} instance for {@link Either} given {@link Monoid} for the left side
+ * @function
+ * @since 1.7.0
+ */
+export function getCompactable<L>(ML: Monoid<L>): Compactable2C<URI, L> {
+  const compact = <A>(fa: Either<L, Option<A>>): Either<L, A> => {
+    if (fa.isLeft()) {
+      return fa as any
+    }
+    if (fa.value.isNone()) {
+      return left(ML.empty)
+    }
+    return right(fa.value.value)
+  }
+  const separate = <A, B>(fa: Either<L, Either<A, B>>): Separated<Either<L, A>, Either<L, B>> => {
+    if (fa.isLeft()) {
+      return {
+        left: fa as any,
+        right: fa as any
+      }
+    }
+    if (fa.value.isLeft()) {
+      return {
+        left: right(fa.value.value),
+        right: left(ML.empty)
+      }
+    }
+    return {
+      left: left(ML.empty),
+      right: right(fa.value.value)
+    }
+  }
+
+  return {
+    URI,
+    _L: phantom,
+    compact,
+    separate
+  }
+}
+
+/**
+ * Builds {@link Filterable} instance for {@link Either} gived {@link Monoid} for the left side
+ * @function
+ * @since 1.7.0
+ */
+export function getFilterable<L>(ML: Monoid<L>): Filterable2C<URI, L> {
+  const C = getCompactable(ML)
+  const partitionMap = <RL, RR, A>(
+    fa: Either<L, A>,
+    f: (a: A) => Either<RL, RR>
+  ): Separated<Either<L, RL>, Either<L, RR>> => {
+    if (fa.isLeft()) {
+      return {
+        left: fa as any,
+        right: fa as any
+      }
+    }
+    const e = f(fa.value)
+    if (e.isLeft()) {
+      return {
+        left: right(e.value),
+        right: left(ML.empty)
+      }
+    }
+    return {
+      left: left(ML.empty),
+      right: right(e.value)
+    }
+  }
+  const partition = <A>(fa: Either<L, A>, p: Predicate<A>): Separated<Either<L, A>, Either<L, A>> => {
+    if (fa.isLeft()) {
+      return {
+        left: fa,
+        right: fa
+      }
+    }
+    if (p(fa.value)) {
+      return {
+        left: left(ML.empty),
+        right: right(fa.value)
+      }
+    }
+    return {
+      left: right(fa.value),
+      right: left(ML.empty)
+    }
+  }
+  const filterMap = <A, B>(fa: Either<L, A>, f: (a: A) => Option<B>): Either<L, B> => {
+    if (fa.isLeft()) {
+      return fa as any
+    }
+    const optionB = f(fa.value)
+    if (optionB.isSome()) {
+      return right(optionB.value)
+    }
+    return left(ML.empty)
+  }
+  const filter = <A>(fa: Either<L, A>, p: Predicate<A>): Either<L, A> => fa.filterOrElse(p, ML.empty)
+  return {
+    ...C,
+    map,
+    partitionMap,
+    filterMap,
+    partition,
+    filter
+  }
+}
+
 /**
  * @instance
  * @since 1.0.0
diff --git a/src/Filterable.ts b/src/Filterable.ts
new file mode 100644
index 00000000..10773a68
--- /dev/null
+++ b/src/Filterable.ts
@@ -0,0 +1,118 @@
+import { Functor, Functor1, Functor2, Functor2C, Functor3, Functor3C } from './Functor'
+import {
+  Compactable,
+  Compactable1,
+  Compactable2,
+  Compactable2C,
+  Compactable3,
+  Compactable3C,
+  Separated
+} from './Compactable'
+import { HKT, Type, Type2, Type3, URIS, URIS2, URIS3 } from './HKT'
+import { Either } from './Either'
+import { Predicate } from './function'
+import { Option } from './Option'
+
+/**
+ * @typeclass
+ * `Filterable` represents data structures which can be _partitioned_/_filtered_.
+ * - {@link partitionMap}
+ * - {@link partition}
+ * - {@link filterMap}
+ * - {@link filter}
+ *
+ * @see https://github.com/LiamGoodacre/purescript-filterable/blob/master/src/Data/Filterable.purs
+ * @since 1.7.0
+ */
+export interface Filterable<F> extends Functor<F>, Compactable<F> {
+  /**
+   * Partition a data structure based on an either predicate.
+   * @since 1.7.0
+   */
+  readonly partitionMap: <RL, RR, A>(fa: HKT<F, A>, f: (a: A) => Either<RL, RR>) => Separated<HKT<F, RL>, HKT<F, RR>>
+  /**
+   * Partition a data structure based on boolean predicate.
+   * @since 1.7.0
+   */
+  readonly partition: <A>(fa: HKT<F, A>, p: Predicate<A>) => Separated<HKT<F, A>, HKT<F, A>>
+  /**
+   * Map over a data structure and filter based on a maybe.
+   * @since 1.7.0
+   */
+  readonly filterMap: <A, B>(fa: HKT<F, A>, f: (a: A) => Option<B>) => HKT<F, B>
+  /**
+   * Filter a data structure based on a boolean.
+   * @since 1.7.0
+   */
+  readonly filter: <A>(fa: HKT<F, A>, p: Predicate<A>) => HKT<F, A>
+}
+
+/**
+ * @since 1.7.0
+ * @see Filterable
+ */
+export interface Filterable1<F extends URIS> extends Functor1<F>, Compactable1<F> {
+  readonly partitionMap: <RL, RR, A>(fa: Type<F, A>, f: (a: A) => Either<RL, RR>) => Separated<Type<F, RL>, Type<F, RR>>
+  readonly partition: <A>(fa: Type<F, A>, p: Predicate<A>) => Separated<Type<F, A>, Type<F, A>>
+  readonly filterMap: <A, B>(fa: Type<F, A>, f: (a: A) => Option<B>) => Type<F, B>
+  readonly filter: <A>(fa: Type<F, A>, p: Predicate<A>) => Type<F, A>
+}
+
+/**
+ * @since 1.7.0
+ * @see Filterable
+ */
+export interface Filterable2<F extends URIS2> extends Functor2<F>, Compactable2<F> {
+  readonly partitionMap: <RL, RR, L, A>(
+    fa: Type2<F, L, A>,
+    f: (a: A) => Either<RL, RR>
+  ) => Separated<Type2<F, L, RL>, Type2<F, L, RR>>
+  readonly partition: <L, A>(fa: Type2<F, L, A>, p: Predicate<A>) => Separated<Type2<F, L, A>, Type2<F, L, A>>
+  readonly filterMap: <L, A, B>(fa: Type2<F, L, A>, f: (a: A) => Option<B>) => Type2<F, L, B>
+  readonly filter: <L, A>(fa: Type2<F, L, A>, p: Predicate<A>) => Type2<F, L, A>
+}
+
+/**
+ * @since 1.7.0
+ * @see Filterable
+ */
+export interface Filterable2C<F extends URIS2, L> extends Functor2C<F, L>, Compactable2C<F, L> {
+  readonly partitionMap: <RL, RR, A>(
+    fa: Type2<F, L, A>,
+    f: (a: A) => Either<RL, RR>
+  ) => Separated<Type2<F, L, RL>, Type2<F, L, RR>>
+  readonly partition: <A>(fa: Type2<F, L, A>, p: Predicate<A>) => Separated<Type2<F, L, A>, Type2<F, L, A>>
+  readonly filterMap: <A, B>(fa: Type2<F, L, A>, f: (a: A) => Option<B>) => Type2<F, L, B>
+  readonly filter: <A>(fa: Type2<F, L, A>, p: Predicate<A>) => Type2<F, L, A>
+}
+
+/**
+ * @since 1.7.0
+ * @see Filterable
+ */
+export interface Filterable3<F extends URIS3> extends Functor3<F>, Compactable3<F> {
+  readonly partitionMap: <RL, RR, U, L, A>(
+    fa: Type3<F, U, L, A>,
+    f: (a: A) => Either<RL, RR>
+  ) => Separated<Type3<F, U, L, RL>, Type3<F, U, L, RR>>
+  readonly partition: <U, L, A>(
+    fa: Type3<F, U, L, A>,
+    p: Predicate<A>
+  ) => Separated<Type3<F, U, L, A>, Type3<F, U, L, A>>
+  readonly filterMap: <U, L, A, B>(fa: Type3<F, U, L, A>, f: (a: A) => Option<B>) => Type3<F, U, L, B>
+  readonly filter: <U, L, A>(fa: Type3<F, U, L, A>, p: Predicate<A>) => Type3<F, U, L, A>
+}
+
+/**
+ * @since 1.7.0
+ * @see Filterable
+ */
+export interface Filterable3C<F extends URIS3, U, L> extends Functor3C<F, U, L>, Compactable3C<F, U, L> {
+  readonly partitionMap: <RL, RR, A>(
+    fa: Type3<F, U, L, A>,
+    f: (a: A) => Either<RL, RR>
+  ) => Separated<Type3<F, U, L, RL>, Type3<F, U, L, RR>>
+  readonly partition: <A>(fa: Type3<F, U, L, A>, p: Predicate<A>) => Separated<Type3<F, U, L, A>, Type3<F, U, L, A>>
+  readonly filterMap: <A, B>(fa: Type3<F, U, L, A>, f: (a: A) => Option<B>) => Type3<F, U, L, B>
+  readonly filter: <A>(fa: Type3<F, U, L, A>, p: Predicate<A>) => Type3<F, U, L, A>
+}
diff --git a/src/IOEither.ts b/src/IOEither.ts
index cd77ee46..175a3ec1 100644
--- a/src/IOEither.ts
+++ b/src/IOEither.ts
@@ -125,7 +125,7 @@ const chain = <L, A, B>(fa: IOEither<L, A>, f: (a: A) => IOEither<L, B>): IOEith
   return fa.chain(f)
 }
 
-const alt = <L, A, B>(fx: IOEither<L, A>, fy: IOEither<L, A>): IOEither<L, A> => {
+const alt = <L, A>(fx: IOEither<L, A>, fy: IOEither<L, A>): IOEither<L, A> => {
   return fx.alt(fy)
 }
 
diff --git a/src/Option.ts b/src/Option.ts
index ee9178ca..56b2dd6d 100644
--- a/src/Option.ts
+++ b/src/Option.ts
@@ -5,13 +5,15 @@ import { Extend1 } from './Extend'
 import { Foldable1 } from './Foldable'
 import { HKT } from './HKT'
 import { Monad1 } from './Monad'
-import { Monoid, getDualMonoid } from './Monoid'
+import { getDualMonoid, Monoid } from './Monoid'
 import { Ord } from './Ord'
 import { Plus1 } from './Plus'
 import { Semigroup } from './Semigroup'
 import { Setoid } from './Setoid'
 import { Traversable1 } from './Traversable'
-import { Lazy, Predicate, Refinement, toString } from './function'
+import { identity, Lazy, not, Predicate, Refinement, toString } from './function'
+import { Compactable1, Separated } from './Compactable'
+import { Filterable1 } from './Filterable'
 
 declare module './HKT' {
   interface URI2HKT<A> {
@@ -554,6 +556,35 @@ export const isNone = <A>(fa: Option<A>): fa is None<A> => {
 export const fromRefinement = <A, B extends A>(refinement: Refinement<A, B>) => (a: A): Option<B> => {
   return refinement(a) ? some(a) : none
 }
+const compact = <A>(fa: Option<Option<A>>): Option<A> => fa.chain(identity)
+const separate = <RL, RR>(fa: Option<Either<RL, RR>>): Separated<Option<RL>, Option<RR>> => {
+  if (fa.isNone()) {
+    return {
+      left: none,
+      right: none
+    }
+  }
+  const e = fa.value
+  if (e.isLeft()) {
+    return {
+      left: some(e.value),
+      right: none
+    }
+  }
+  return {
+    left: none,
+    right: some(e.value)
+  }
+}
+
+const filter = <A>(fa: Option<A>, p: Predicate<A>): Option<A> => fa.filter(p)
+const filterMap = chain
+const partitionMap = <RL, RR, A>(fa: Option<A>, f: (a: A) => Either<RL, RR>): Separated<Option<RL>, Option<RR>> =>
+  separate(fa.map(f))
+const partition = <A>(fa: Option<A>, p: Predicate<A>): Separated<Option<A>, Option<A>> => ({
+  left: fa.filter(not(p)),
+  right: fa.filter(p)
+})
 
 /**
  * @instance
@@ -564,7 +595,9 @@ export const option: Monad1<URI> &
   Plus1<URI> &
   Traversable1<URI> &
   Alternative1<URI> &
-  Extend1<URI> = {
+  Extend1<URI> &
+  Compactable1<URI> &
+  Filterable1<URI> = {
   URI,
   map,
   of,
@@ -574,5 +607,11 @@ export const option: Monad1<URI> &
   traverse,
   zero,
   alt,
-  extend
+  extend,
+  compact,
+  separate,
+  filter,
+  filterMap,
+  partition,
+  partitionMap
 }
diff --git a/src/Set.ts b/src/Set.ts
index 18be9707..6df767a1 100644
--- a/src/Set.ts
+++ b/src/Set.ts
@@ -4,6 +4,7 @@ import { Ord } from './Ord'
 import { Semigroup } from './Semigroup'
 import { Setoid } from './Setoid'
 import { Predicate, not } from './function'
+import { Separated } from './Compactable'
 
 /**
  * @function
@@ -114,7 +115,7 @@ export const filter = <A>(x: Set<A>, predicate: Predicate<A>): Set<A> => {
  * @function
  * @since 1.2.0
  */
-export const partition = <A>(x: Set<A>, predicate: Predicate<A>): { right: Set<A>; left: Set<A> } => {
+export const partition = <A>(x: Set<A>, predicate: Predicate<A>): Separated<Set<A>, Set<A>> => {
   const values = x.values()
   let e: IteratorResult<A>
   let t = new Set()
@@ -182,7 +183,7 @@ export const intersection = <A>(S: Setoid<A>): ((x: Set<A>, y: Set<A>) => Set<A>
  * @function
  * @since 1.2.0
  */
-export const partitionMap = <A, L, R>(x: Set<A>, f: (a: A) => Either<L, R>): { left: Set<L>; right: Set<R> } => {
+export const partitionMap = <A, L, R>(x: Set<A>, f: (a: A) => Either<L, R>): Separated<Set<L>, Set<R>> => {
   const values = x.values()
   let e: IteratorResult<A>
   let l = new Set()
diff --git a/src/StrMap.ts b/src/StrMap.ts
index 7594dd81..ad5e7d1d 100644
--- a/src/StrMap.ts
+++ b/src/StrMap.ts
@@ -10,6 +10,9 @@ import { Setoid } from './Setoid'
 import { Traversable1 } from './Traversable'
 import { Unfoldable } from './Unfoldable'
 import { Predicate, tuple } from './function'
+import { Either } from './Either'
+import { Compactable1, Separated } from './Compactable'
+import { Filterable1 } from './Filterable'
 
 // https://github.com/purescript/purescript-maps
 
@@ -57,15 +60,7 @@ export class StrMap<A> {
    * @since 1.4.0
    */
   filter(p: Predicate<A>): StrMap<A> {
-    const o = this.value
-    const r: { [key: string]: A } = {}
-    for (const k in o) {
-      const value = o[k]
-      if (p(value)) {
-        r[k] = value
-      }
-    }
-    return new StrMap(r)
+    return filter(this, p)
   }
 }
 
@@ -293,13 +288,115 @@ export const pop = <A>(k: string, d: StrMap<A>): Option<[A, StrMap<A>]> => {
   return a.isNone() ? none : some(tuple(a.value, remove(k, d)))
 }
 
+const filterMap = <A, B>(fa: StrMap<A>, f: (a: A) => Option<B>): StrMap<B> => {
+  const value = fa.value
+  const result: { [key: string]: B } = {}
+  const keys = Object.keys(value)
+  for (const key of keys) {
+    const optionB = f(value[key])
+    if (optionB.isSome()) {
+      result[key] = optionB.value
+    }
+  }
+  return new StrMap(result)
+}
+
+const filter = <A>(fa: StrMap<A>, p: Predicate<A>): StrMap<A> => {
+  const value = fa.value
+  const result: { [key: string]: A } = {}
+  const keys = Object.keys(value)
+  for (const key of keys) {
+    const a = value[key]
+    if (p(a)) {
+      result[key] = a
+    }
+  }
+  return new StrMap(result)
+}
+
+const compact = <A>(fa: StrMap<Option<A>>): StrMap<A> => {
+  const value = fa.value
+  const result: { [key: string]: A } = {}
+  const keys = Object.keys(value)
+  for (const key of keys) {
+    const optionA = value[key]
+    if (optionA.isSome()) {
+      result[key] = optionA.value
+    }
+  }
+  return new StrMap(result)
+}
+
+const partitionMap = <RL, RR, A>(fa: StrMap<A>, f: (a: A) => Either<RL, RR>): Separated<StrMap<RL>, StrMap<RR>> => {
+  const value = fa.value
+  const left: { [key: string]: RL } = {}
+  const right: { [key: string]: RR } = {}
+  const keys = Object.keys(value)
+  for (const key of keys) {
+    const e = f(value[key])
+    if (e.isLeft()) {
+      left[key] = e.value
+    } else {
+      right[key] = e.value
+    }
+  }
+  return {
+    left: new StrMap(left),
+    right: new StrMap(right)
+  }
+}
+
+const partition = <A>(fa: StrMap<A>, p: Predicate<A>): Separated<StrMap<A>, StrMap<A>> => {
+  const value = fa.value
+  const left: { [key: string]: A } = {}
+  const right: { [key: string]: A } = {}
+  const keys = Object.keys(value)
+  for (const key of keys) {
+    const a = value[key]
+    if (p(a)) {
+      right[key] = a
+    } else {
+      left[key] = a
+    }
+  }
+  return {
+    left: new StrMap(left),
+    right: new StrMap(right)
+  }
+}
+
+const separate = <RL, RR>(fa: StrMap<Either<RL, RR>>): Separated<StrMap<RL>, StrMap<RR>> => {
+  const value = fa.value
+  const left: { [key: string]: RL } = {}
+  const right: { [key: string]: RR } = {}
+  const keys = Object.keys(value)
+  for (const key of keys) {
+    const e = value[key]
+    if (e.isLeft()) {
+      left[key] = e.value
+    } else {
+      right[key] = e.value
+    }
+  }
+  return {
+    left: new StrMap(left),
+    right: new StrMap(right)
+  }
+}
+
 /**
  * @instance
  * @since 1.0.0
  */
-export const strmap: Functor1<URI> & Foldable1<URI> & Traversable1<URI> = {
+export const strmap: Functor1<URI> & Foldable1<URI> & Traversable1<URI> & Compactable1<URI> & Filterable1<URI> = {
   URI,
   map,
   reduce,
-  traverse
+  traverse,
+  compact,
+  separate,
+  filter,
+  filterMap,
+  partition,
+  partitionMap
 }
diff --git a/src/Validation.ts b/src/Validation.ts
index 64c3c37f..efe1fffe 100644
--- a/src/Validation.ts
+++ b/src/Validation.ts
@@ -11,6 +11,9 @@ import { Setoid } from './Setoid'
 import { Traversable2 } from './Traversable'
 import { Predicate, phantom, toString } from './function'
 import { Bifunctor2 } from './Bifunctor'
+import { Compactable2C, Separated } from './Compactable'
+import { Option } from './Option'
+import { Filterable2C } from './Filterable'
 
 // Adapted from https://github.com/purescript/purescript-validation
 
@@ -29,6 +32,44 @@ export type URI = typeof URI
  *
  * The `Applicative` instance collects multiple failures in an arbitrary `Semigroup`.
  *
+ * @example
+ *
+ * import { Validation, getApplicative, success, failure } from 'fp-ts/lib/Validation'
+ * import { NonEmptyArray, getSemigroup } from 'fp-ts/lib/NonEmptyArray'
+ *
+ * interface Person {
+ *   readonly name: string
+ *   readonly age: number
+ * }
+ *
+ * // curried constructor
+ * const person = (name: string) => (age: number): Person => ({ name, age })
+ *
+ * // validators
+ * function validateName(input: string): Validation<NonEmptyArray<string>, string> {
+ *   return input.length === 0 ? failure(new NonEmptyArray('Invalid name: empty string', [])) : success(input)
+ * }
+ * function validateAge(input: string): Validation<NonEmptyArray<string>, number> {
+ *   const n = parseFloat(input)
+ *   if (isNaN(n)) {
+ *     return failure(new NonEmptyArray(`Invalid age: not a number ${input}`, []))
+ *   }
+ *   return n % 1 !== 0 ? failure(new NonEmptyArray(`Invalid age: not an integer ${n}`, [])) : success(n)
+ * }
+ *
+ * // get an `Applicative` instance for Validation<NonEmptyArray<string>, ?>
+ * const A = getApplicative(getSemigroup<string>())
+ *
+ * function validatePerson(input: Record<string, string>): Validation<NonEmptyArray<string>, Person> {
+ *   return A.ap(validateName(input['name']).map(person), validateAge(input['age']))
+ * }
+ *
+ * console.log(validatePerson({ name: '', age: '1.2' }))
+ * // failure(new NonEmptyArray("Invalid name: empty string", ["Invalid age: not an integer 1.2"]))
+ *
+ * console.log(validatePerson({ name: 'Giulio', age: '44' }))
+ * // success({ "name": "Giulio", "age": 44 })
+ *
  * @data
  * @constructor Failure
  * @constructor Success
@@ -323,6 +364,125 @@ export const isSuccess = <L, A>(fa: Validation<L, A>): fa is Success<L, A> => {
   return fa.isSuccess()
 }
 
+/**
+ * Builds {@link Compactable} instance for {@link Validation} given {@link Monoid} for the failure side
+ * @function
+ * @since 1.7.0
+ */
+export function getCompactable<L>(ML: Monoid<L>): Compactable2C<URI, L> {
+  const compact = <A>(fa: Validation<L, Option<A>>): Validation<L, A> => {
+    if (fa.isFailure()) {
+      return fa as any
+    }
+    if (fa.value.isNone()) {
+      return failure(ML.empty)
+    }
+    return success(fa.value.value)
+  }
+
+  const separate = <RL, RR, A>(fa: Validation<L, Either<RL, RR>>): Separated<Validation<L, RL>, Validation<L, RR>> => {
+    if (fa.isFailure()) {
+      return {
+        left: fa as any,
+        right: fa as any
+      }
+    }
+    if (fa.value.isLeft()) {
+      return {
+        left: success(fa.value.value),
+        right: failure(ML.empty)
+      }
+    }
+    return {
+      left: failure(ML.empty),
+      right: success(fa.value.value)
+    }
+  }
+  return {
+    URI,
+    _L: phantom,
+    compact,
+    separate
+  }
+}
+
+/**
+ * Builds {@link Filterable} instance for {@link Validation} gived {@link Monoid} for the left side
+ * @function
+ * @since 1.7.0
+ */
+export function getFilterable<L>(ML: Monoid<L>): Filterable2C<URI, L> {
+  const C = getCompactable(ML)
+  const partitionMap = <RL, RR, A>(
+    fa: Validation<L, A>,
+    f: (a: A) => Either<RL, RR>
+  ): Separated<Validation<L, RL>, Validation<L, RR>> => {
+    if (fa.isFailure()) {
+      return {
+        left: fa as any,
+        right: fa as any
+      }
+    }
+    const e = f(fa.value)
+    if (e.isLeft()) {
+      return {
+        left: success(e.value),
+        right: failure(ML.empty)
+      }
+    }
+    return {
+      left: failure(ML.empty),
+      right: success(e.value)
+    }
+  }
+  const partition = <A>(fa: Validation<L, A>, p: Predicate<A>): Separated<Validation<L, A>, Validation<L, A>> => {
+    if (fa.isFailure()) {
+      return {
+        left: fa,
+        right: fa
+      }
+    }
+    if (p(fa.value)) {
+      return {
+        left: failure(ML.empty),
+        right: success(fa.value)
+      }
+    }
+    return {
+      left: success(fa.value),
+      right: failure(ML.empty)
+    }
+  }
+  const filterMap = <A, B>(fa: Validation<L, A>, f: (a: A) => Option<B>): Validation<L, B> => {
+    if (fa.isFailure()) {
+      return fa as any
+    }
+    const optionB = f(fa.value)
+    if (optionB.isSome()) {
+      return success(optionB.value)
+    }
+    return failure(ML.empty)
+  }
+  const filter = <A>(fa: Validation<L, A>, p: Predicate<A>): Validation<L, A> => {
+    if (fa.isFailure()) {
+      return fa
+    }
+    const a = fa.value
+    if (p(a)) {
+      return success(a)
+    }
+    return failure(ML.empty)
+  }
+  return {
+    ...C,
+    map,
+    partitionMap,
+    filterMap,
+    partition,
+    filter
+  }
+}
+
 /**
  * @instance
  * @since 1.0.0
diff --git a/src/index.ts b/src/index.ts
index 9ab41c21..934fdfa0 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -148,3 +148,7 @@ import * as validation from './Validation'
 export { validation }
 import * as writer from './Writer'
 export { writer }
+import * as compactable from './Compactable'
+export { compactable }
+import * as filterable from './Filterable'
+export { filterable }


Test patch:
diff --git a/test/Array.ts b/test/Array.ts
index ab4c13d6..17ee9a9f 100644
--- a/test/Array.ts
+++ b/test/Array.ts
@@ -225,18 +225,6 @@ describe('Array', () => {
     assert.deepEqual(modifyAt(as, 1, double), some([1, 4, 3]))
   })
 
-  it('mapOption', () => {
-    const f = (a: number) => (a % 2 === 0 ? none : some(a))
-    assert.deepEqual(mapOption([], f), [])
-    assert.deepEqual(mapOption(as, f), [1, 3])
-  })
-
-  it('catOptions', () => {
-    assert.deepEqual(catOptions([]), [])
-    assert.deepEqual(catOptions([some(1), some(2), some(3)]), [1, 2, 3])
-    assert.deepEqual(catOptions([some(1), none, some(3)]), [1, 3])
-  })
-
   it('sort', () => {
     assert.deepEqual(sort(ordNumber)([3, 2, 1]), [1, 2, 3])
   })
@@ -272,11 +260,6 @@ describe('Array', () => {
     assert.deepEqual(flatten([[1], [2], [3]]), [1, 2, 3])
   })
 
-  it('partitionMap', () => {
-    assert.deepEqual(partitionMap([], x => x), { left: [], right: [] })
-    assert.deepEqual(partitionMap([right(1), left('foo'), right(2)], x => x), { left: ['foo'], right: [1, 2] })
-  })
-
   it('rotate', () => {
     assert.deepEqual(rotate(1, []), [])
     assert.deepEqual(rotate(1, [1]), [1])
@@ -289,10 +272,6 @@ describe('Array', () => {
     assert.deepEqual(rotate(-2, [1, 2, 3, 4, 5]), [3, 4, 5, 1, 2])
   })
 
-  it('filter', () => {
-    assert.deepEqual(filter([1, 2, 3], n => n % 2 === 1), [1, 3])
-  })
-
   it('map', () => {
     assert.deepEqual(array.map([1, 2, 3], n => n * 2), [2, 4, 6])
   })
@@ -435,4 +414,44 @@ describe('Array', () => {
       { name: 'b', age: 3 }
     ])
   })
+
+  it('compact/catOptions', () => {
+    assert.deepEqual(array.compact([]), [])
+    assert.deepEqual(array.compact([some(1), some(2), some(3)]), [1, 2, 3])
+    assert.deepEqual(array.compact([some(1), none, some(3)]), [1, 3])
+    assert.deepEqual(catOptions([]), [])
+    assert.deepEqual(catOptions([some(1), some(2), some(3)]), [1, 2, 3])
+    assert.deepEqual(catOptions([some(1), none, some(3)]), [1, 3])
+  })
+
+  it('separate', () => {
+    assert.deepEqual(array.separate([]), { left: [], right: [] })
+    assert.deepEqual(array.separate([left(123), right('123')]), { left: [123], right: ['123'] })
+  })
+
+  it('filter', () => {
+    assert.deepEqual(filter([1, 2, 3], n => n % 2 === 1), [1, 3])
+    assert.deepEqual(array.filter([1, 2, 3], n => n % 2 === 1), [1, 3])
+  })
+
+  it('filterMap/mapOption', () => {
+    const f = (a: number) => (a % 2 === 0 ? none : some(a))
+    assert.deepEqual(mapOption([], f), [])
+    assert.deepEqual(mapOption(as, f), [1, 3])
+    assert.deepEqual(array.filterMap([], f), [])
+    assert.deepEqual(array.filterMap(as, f), [1, 3])
+  })
+
+  it('partitionMap', () => {
+    assert.deepEqual(partitionMap([], x => x), { left: [], right: [] })
+    assert.deepEqual(partitionMap([right(1), left('foo'), right(2)], x => x), { left: ['foo'], right: [1, 2] })
+    assert.deepEqual(array.partitionMap([], x => x), { left: [], right: [] })
+    assert.deepEqual(array.partitionMap([right(1), left('foo'), right(2)], x => x), { left: ['foo'], right: [1, 2] })
+  })
+
+  it('partition', () => {
+    const p = (n: number) => n > 2
+    assert.deepEqual(array.partition([], p), { left: [], right: [] })
+    assert.deepEqual(array.partition([1, 3], p), { left: [1], right: [3] })
+  })
 })
diff --git a/test/Either.ts b/test/Either.ts
index 9446594e..fafe9b6f 100644
--- a/test/Either.ts
+++ b/test/Either.ts
@@ -13,12 +13,15 @@ import {
   tryCatch,
   isLeft,
   isRight,
-  fromRefinement
+  fromRefinement,
+  getCompactable,
+  getFilterable
 } from '../src/Either'
 import { none, option, some } from '../src/Option'
 import { setoidNumber, setoidString } from '../src/Setoid'
 import { traverse } from '../src/Traversable'
 import { failure, success } from '../src/Validation'
+import { monoidString } from '../src/Monoid'
 
 describe('Either', () => {
   it('fold', () => {
@@ -88,7 +91,7 @@ describe('Either', () => {
     assert.deepEqual(e2, left(new SyntaxError('Unexpected end of JSON input')))
 
     const e3 = tryCatch(() => {
-      throw 'a string'
+      throw 'a string' // tslint:disable-line no-string-throw
     })
     assert.deepEqual(e3, left(new Error('a string')))
 
@@ -248,7 +251,7 @@ describe('Either', () => {
 
   it('refineOrElse', () => {
     type Color = 'red' | 'blue'
-    const isColor = (s: string): s is Color => s === 'red' || s == 'blue'
+    const isColor = (s: string): s is Color => s === 'red' || s === 'blue'
     assert.deepEqual(right('red').refineOrElse(isColor, -1), right('red'))
     assert.deepEqual(right('foo').refineOrElse(isColor, -1), left(-1))
     assert.deepEqual(left<number, string>(12).refineOrElse(isColor, -1), left(12))
@@ -256,7 +259,7 @@ describe('Either', () => {
 
   it('refineOrElseL', () => {
     type Color = 'red' | 'blue'
-    const isColor = (s: string): s is Color => s === 'red' || s == 'blue'
+    const isColor = (s: string): s is Color => s === 'red' || s === 'blue'
     const errorHandler = (s: string) => `invalid color ${s}`
     assert.deepEqual(right('red').refineOrElseL(isColor, errorHandler), right('red'))
     assert.deepEqual(right('foo').refineOrElseL(isColor, errorHandler), left('invalid color foo'))
@@ -265,9 +268,57 @@ describe('Either', () => {
 
   it('fromRefinement', () => {
     type Color = 'red' | 'blue'
-    const isColor = (s: string): s is Color => s === 'red' || s == 'blue'
+    const isColor = (s: string): s is Color => s === 'red' || s === 'blue'
     const from = fromRefinement(isColor, s => `invalid color ${s}`)
     assert.deepEqual(from('red'), right('red'))
     assert.deepEqual(from('foo'), left('invalid color foo'))
   })
+
+  describe('getCompactable', () => {
+    const C = getCompactable(monoidString)
+    it('compact', () => {
+      assert.deepEqual(C.compact(left('1')), left('1'))
+      assert.deepEqual(C.compact(right(none)), left(monoidString.empty))
+      assert.deepEqual(C.compact(right(some(123))), right(123))
+    })
+
+    it('separate', () => {
+      assert.deepEqual(C.separate(left('123')), { left: left('123'), right: left('123') })
+      assert.deepEqual(C.separate(right(left('123'))), { left: right('123'), right: left(monoidString.empty) })
+      assert.deepEqual(C.separate(right(right('123'))), { left: left(monoidString.empty), right: right('123') })
+    })
+  })
+
+  describe('getFilterable', () => {
+    const F = getFilterable(monoidString)
+    const p = (n: number) => n > 2
+    it('partition', () => {
+      assert.deepEqual(F.partition(left<string, number>('123'), p), {
+        left: left('123'),
+        right: left('123')
+      })
+      assert.deepEqual(F.partition(right<string, number>(1), p), { left: right(1), right: left(monoidString.empty) })
+      assert.deepEqual(F.partition(right<string, number>(3), p), { left: left(monoidString.empty), right: right(3) })
+    })
+    it('partitionMap', () => {
+      const f = (n: number) => (p(n) ? right(n + 1) : left(n - 1))
+      assert.deepEqual(F.partitionMap(left<string, number>('123'), f), {
+        left: left('123'),
+        right: left('123')
+      })
+      assert.deepEqual(F.partitionMap(right<string, number>(1), f), { left: right(0), right: left(monoidString.empty) })
+      assert.deepEqual(F.partitionMap(right<string, number>(3), f), { left: left(monoidString.empty), right: right(4) })
+    })
+    it('filter', () => {
+      assert.deepEqual(F.filter(left<string, number>('123'), p), left('123'))
+      assert.deepEqual(F.filter(right<string, number>(1), p), left(monoidString.empty))
+      assert.deepEqual(F.filter(right<string, number>(3), p), right(3))
+    })
+    it('filterMap', () => {
+      const f = (n: number) => (p(n) ? some(n + 1) : none)
+      assert.deepEqual(F.filterMap(left<string, number>('123'), f), left('123'))
+      assert.deepEqual(F.filterMap(right<string, number>(1), f), left(monoidString.empty))
+      assert.deepEqual(F.filterMap(right<string, number>(3), f), right(4))
+    })
+  })
 })
diff --git a/test/Option.ts b/test/Option.ts
index 7d73ca94..a2e4bd23 100644
--- a/test/Option.ts
+++ b/test/Option.ts
@@ -25,6 +25,8 @@ import { setoidNumber } from '../src/Setoid'
 import { traverse } from '../src/Traversable'
 import { identity } from '../src/function'
 
+const p = (n: number): boolean => n > 2
+
 describe('Option', () => {
   it('fold', () => {
     const f = 'none'
@@ -180,7 +182,7 @@ describe('Option', () => {
   })
 
   it('fromPredicate', () => {
-    const f = fromPredicate((n: number) => n > 2)
+    const f = fromPredicate(p)
     assert.deepEqual(f(1), none)
     assert.deepEqual(f(3), some(3))
   })
@@ -242,15 +244,6 @@ describe('Option', () => {
     assert.equal(some(2).exists(is2), true)
   })
 
-  it('filter', () => {
-    const x: Option<number> = none
-    const is2 = (a: number) => a === 2
-    assert.equal(x.filter(is2), x)
-    assert.equal(some(1).filter(is2), none)
-    const some2 = some(2)
-    assert.equal(some2.filter(is2), some2)
-  })
-
   it('refine', () => {
     const x: Option<number | string> = none
     const isString = (a: any): a is string => typeof a === 'string'
@@ -285,4 +278,48 @@ describe('Option', () => {
     assert.deepEqual(parseDirection('asc'), some('asc'))
     assert.deepEqual(parseDirection('foo'), none)
   })
+
+  it('compact', () => {
+    assert.deepEqual(option.compact(none), none)
+    assert.deepEqual(option.compact(some(none)), none)
+    assert.deepEqual(option.compact(some(some('123'))), some('123'))
+  })
+
+  it('separate', () => {
+    assert.deepEqual(option.separate(none), { left: none, right: none })
+    assert.deepEqual(option.separate(some(left('123'))), { left: some('123'), right: none })
+    assert.deepEqual(option.separate(some(right('123'))), { left: none, right: some('123') })
+  })
+
+  it('filter', () => {
+    const x: Option<number> = none
+    const is2 = (a: number) => a === 2
+    assert.equal(x.filter(is2), x)
+    assert.equal(option.filter(x, is2), x)
+    assert.equal(some(1).filter(is2), none)
+    assert.equal(option.filter(some(1), is2), none)
+    const some2 = some(2)
+    assert.equal(some2.filter(is2), some2)
+    assert.equal(option.filter(some2, is2), some2)
+  })
+
+  it('filterMap', () => {
+    const f = (n: number) => (p(n) ? some(n + 1) : none)
+    assert.deepEqual(option.filterMap(none, f), none)
+    assert.deepEqual(option.filterMap(some(1), f), none)
+    assert.deepEqual(option.filterMap(some(3), f), some(4))
+  })
+
+  it('partition', () => {
+    assert.deepEqual(option.partition(none, p), { left: none, right: none })
+    assert.deepEqual(option.partition(some(1), p), { left: some(1), right: none })
+    assert.deepEqual(option.partition(some(3), p), { left: none, right: some(3) })
+  })
+
+  it('partitionMap', () => {
+    const f = (n: number) => (p(n) ? right(n + 1) : left(n - 1))
+    assert.deepEqual(option.partitionMap(none, f), { left: none, right: none })
+    assert.deepEqual(option.partitionMap(some(1), f), { left: some(0), right: none })
+    assert.deepEqual(option.partitionMap(some(3), f), { left: none, right: some(4) })
+  })
 })
diff --git a/test/StrMap.ts b/test/StrMap.ts
index 3cb7aec9..0c862968 100644
--- a/test/StrMap.ts
+++ b/test/StrMap.ts
@@ -20,6 +20,9 @@ import {
 } from '../src/StrMap'
 import { traverse } from '../src/Traversable'
 import { semigroupSum } from '../src/Semigroup'
+import { left, right } from '../src/Either'
+
+const p = (n: number) => n > 2
 
 describe('StrMap', () => {
   it('getMonoid', () => {
@@ -127,8 +130,43 @@ describe('StrMap', () => {
     assert.deepEqual(insert('c', 3, new StrMap({ a: 1, b: 2 })), new StrMap({ a: 1, b: 2, c: 3 }))
   })
 
+  it('compact', () => {
+    assert.deepEqual(strmap.compact(new StrMap({ foo: none, bar: some(123) })), new StrMap({ bar: 123 }))
+  })
+
+  it('separate', () => {
+    assert.deepEqual(strmap.separate(new StrMap({ foo: left(123), bar: right(123) })), {
+      left: new StrMap({ foo: 123 }),
+      right: new StrMap({ bar: 123 })
+    })
+  })
+
   it('filter', () => {
-    const d = new StrMap({ a: 1, b: 2 })
-    assert.deepEqual(d.filter(a => a === 1), new StrMap({ a: 1 }))
+    const d = new StrMap({ a: 1, b: 3 })
+    assert.deepEqual(d.filter(p), new StrMap({ b: 3 }))
+    assert.deepEqual(strmap.filter(d, p), new StrMap({ b: 3 }))
+  })
+
+  it('filterMap', () => {
+    const f = (n: number) => (p(n) ? some(n + 1) : none)
+    assert.deepEqual(strmap.filterMap(new StrMap<number>({}), f), new StrMap({}))
+    assert.deepEqual(strmap.filterMap(new StrMap({ a: 1, b: 3 }), f), new StrMap({ b: 4 }))
+  })
+
+  it('partition', () => {
+    assert.deepEqual(strmap.partition(new StrMap<number>({}), p), { left: new StrMap({}), right: new StrMap({}) })
+    assert.deepEqual(strmap.partition(new StrMap<number>({ a: 1, b: 3 }), p), {
+      left: new StrMap({ a: 1 }),
+      right: new StrMap({ b: 3 })
+    })
+  })
+
+  it('partitionMap', () => {
+    const f = (n: number) => (p(n) ? right(n + 1) : left(n - 1))
+    assert.deepEqual(strmap.partitionMap(new StrMap<number>({}), f), { left: new StrMap({}), right: new StrMap({}) })
+    assert.deepEqual(strmap.partitionMap(new StrMap<number>({ a: 1, b: 3 }), f), {
+      left: new StrMap({ a: 0 }),
+      right: new StrMap({ b: 4 })
+    })
   })
 })
diff --git a/test/Validation.ts b/test/Validation.ts
index 66c09bf1..05090e68 100644
--- a/test/Validation.ts
+++ b/test/Validation.ts
@@ -1,6 +1,5 @@
 import * as assert from 'assert'
 import { array } from '../src/Array'
-import * as either from '../src/Either'
 import { monoidString, monoidSum } from '../src/Monoid'
 import { none, option, some } from '../src/Option'
 import { getArraySemigroup, semigroupString } from '../src/Semigroup'
@@ -19,8 +18,13 @@ import {
   isSuccess,
   success,
   validation,
-  getMonoid
+  getMonoid,
+  getCompactable,
+  getFilterable
 } from '../src/Validation'
+import { left, right } from '../src/Either'
+
+const p = (n: number): boolean => n > 2
 
 describe('Validation', () => {
   it('getMonad', () => {
@@ -53,8 +57,8 @@ describe('Validation', () => {
   })
 
   it('fromEither', () => {
-    assert.deepEqual(fromEither(either.right<string, number>(1)), success(1))
-    assert.deepEqual(fromEither(either.left<string, number>('error')), failure('error'))
+    assert.deepEqual(fromEither(right<string, number>(1)), success(1))
+    assert.deepEqual(fromEither(left<string, number>('error')), failure('error'))
   })
 
   it('getSetoid', () => {
@@ -147,10 +151,9 @@ describe('Validation', () => {
 
   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))
+    assert.deepEqual(success<string, number>(1).bimap(f, p), success(false))
+    assert.deepEqual(failure<string, number>('foo').bimap(f, p), failure(3))
+    assert.deepEqual(validation.bimap(success<string, number>(1), f, p), success(false))
   })
 
   it('fromPredicate', () => {
@@ -174,4 +177,63 @@ describe('Validation', () => {
     assert.deepEqual(M.concat(failure('foo'), success(1)), failure('foo'))
     assert.deepEqual(M.concat(failure('foo'), failure('bar')), failure('foobar'))
   })
+
+  describe('getCompactable', () => {
+    const C = getCompactable(monoidString)
+    it('compact', () => {
+      assert.deepEqual(C.compact(failure('1')), failure('1'))
+      assert.deepEqual(C.compact(success(none)), failure(monoidString.empty))
+      assert.deepEqual(C.compact(success(some(123))), success(123))
+    })
+
+    it('separate', () => {
+      assert.deepEqual(C.separate(failure('123')), { left: failure('123'), right: failure('123') })
+      assert.deepEqual(C.separate(success(left('123'))), { left: success('123'), right: failure(monoidString.empty) })
+      assert.deepEqual(C.separate(success(right('123'))), { left: failure(monoidString.empty), right: success('123') })
+    })
+  })
+
+  describe('getFilterable', () => {
+    const F = getFilterable(monoidString)
+    it('partition', () => {
+      assert.deepEqual(F.partition(failure<string, number>('123'), p), {
+        left: failure('123'),
+        right: failure('123')
+      })
+      assert.deepEqual(F.partition(success<string, number>(1), p), {
+        left: success(1),
+        right: failure(monoidString.empty)
+      })
+      assert.deepEqual(F.partition(success<string, number>(3), p), {
+        left: failure(monoidString.empty),
+        right: success(3)
+      })
+    })
+    it('partitionMap', () => {
+      const f = (n: number) => (p(n) ? right(n + 1) : left(n - 1))
+      assert.deepEqual(F.partitionMap(failure<string, number>('123'), f), {
+        left: failure('123'),
+        right: failure('123')
+      })
+      assert.deepEqual(F.partitionMap(success<string, number>(1), f), {
+        left: success(0),
+        right: failure(monoidString.empty)
+      })
+      assert.deepEqual(F.partitionMap(success<string, number>(3), f), {
+        left: failure(monoidString.empty),
+        right: success(4)
+      })
+    })
+    it('filter', () => {
+      assert.deepEqual(F.filter(failure<string, number>('123'), p), failure('123'))
+      assert.deepEqual(F.filter(success<string, number>(1), p), failure(monoidString.empty))
+      assert.deepEqual(F.filter(success<string, number>(3), p), success(3))
+    })
+    it('filterMap', () => {
+      const f = (n: number) => (p(n) ? some(n + 1) : none)
+      assert.deepEqual(F.filterMap(failure<string, number>('123'), f), failure('123'))
+      assert.deepEqual(F.filterMap(success<string, number>(1), f), failure(monoidString.empty))
+      assert.deepEqual(F.filterMap(success<string, number>(3), f), success(4))
+    })
+  })
 })