Repository: gcanti/fp-ts
Pull request #639 (2018-11-20 16:08:53)

version 1.11


Patch:
diff --git a/docs/Array.md b/docs/Array.md
index cdaca96d..5353df1b 100644
--- a/docs/Array.md
+++ b/docs/Array.md
@@ -304,7 +304,7 @@ assert.deepEqual(findIndex([], x => x === 2), none)
 ## findLast
 
 ```ts
-<A>(as: Array<A>, predicate: Predicate<A>): Option<A>
+findLast<A>(as: Array<A>, predicate: Predicate<A>): Option<A>
 ```
 
 Added in v1.0.0 (function)
diff --git a/docs/Choice.md b/docs/Choice.md
new file mode 100644
index 00000000..cc215ea5
--- /dev/null
+++ b/docs/Choice.md
@@ -0,0 +1,96 @@
+---
+id: Choice
+title: Module Choice
+---
+
+[← Back](.)
+
+[Source](https://github.com/gcanti/fp-ts/blob/master/src/Choice.ts)
+
+# Choice
+
+```ts
+interface Choice<F> extends Profunctor<F> {
+  readonly left: <A, B, C>(pab: HKT2<F, A, B>) => HKT2<F, Either<A, C>, Either<B, C>>
+  readonly right: <A, B, C>(pbc: HKT2<F, B, C>) => HKT2<F, Either<A, B>, Either<A, C>>
+}
+```
+
+Added in v1.11.0 (type class)
+
+The `Choice` class extends `Profunctor` with combinators for working with
+sum types.
+
+`left` and `right` lift values in a `Profunctor` to act on the `Left` and
+`Right` components of a sum, respectively.
+
+Looking at `Choice` through the intuition of inputs and outputs
+yields the following type signature:
+
+```purescript
+left ::  forall input output a. p input output -> p (Either input a) (Either output a)
+right :: forall input output a. p input output -> p (Either a input) (Either a output)
+```
+
+If we specialize the profunctor `p` to the `function` arrow, we get the following type
+signatures:
+
+```purescript
+left ::  forall input output a. (input -> output) -> (Either input a) -> (Either output a)
+right :: forall input output a. (input -> output) -> (Either a input) -> (Either a output)
+```
+
+When the `profunctor` is `Function` application, `left` allows you to map a function over the
+left side of an `Either`, and `right` maps it over the right side (same as `map` would do).
+
+## fanin
+
+```ts
+fanin<F>(
+  F: Category<F> & Choice<F>
+): <A, B, C>(pac: HKT2<F, A, C>, pbc: HKT2<F, B, C>) => HKT2<F, Either<A, B>, C>
+```
+
+Added in v1.11.0 (function)
+
+Compose a value which eliminates a sum from two values, each eliminating
+one side of the sum.
+
+This combinator is useful when assembling values from smaller components,
+because it provides a way to support two different types of input.
+
+Specializing `(|||)` to function application would look like this:
+
+```purescript
+(|||) :: forall a b c d. (a -> c) -> (b -> c) -> Either a b -> c
+```
+
+We take two functions, `f` and `g`, which both return the same type `c` and we transform them into a
+single function which takes an `Either` value with the parameter type of `f` on the left side and
+the parameter type of `g` on the right side. The function then runs either `f` or `g`, depending on
+whether the `Either` value is a `Left` or a `Right`.
+This allows us to bundle two different computations which both have the same result type into one
+function which will run the approriate computation based on the parameter supplied in the `Either` value.
+
+## splitChoice
+
+```ts
+splitChoice<F>(
+  F: Category<F> & Choice<F>
+): <A, B, C, D>(pab: HKT2<F, A, B>, pcd: HKT2<F, C, D>) => HKT2<F, Either<A, C>, Either<B, D>>
+```
+
+Added in v1.11.0 (function)
+
+Compose a value acting on a sum from two values, each acting on one of
+the components of the sum.
+
+Specializing `(+++)` to function application would look like this:
+
+```purescript
+(+++) :: forall a b c d. (a -> b) -> (c -> d) -> (Either a c) -> (Either b d)
+```
+
+We take two functions, `f` and `g`, and we transform them into a single function which
+takes an `Either`and maps `f` over the left side and `g` over the right side. Just like
+`bi-map` would do for the `bi-functor` instance of `Either`.
diff --git a/docs/Either.md b/docs/Either.md
index d2c10a65..4e1f1e49 100644
--- a/docs/Either.md
+++ b/docs/Either.md
@@ -472,25 +472,27 @@ of this structure
 ## toError
 
 ```ts
-(e: {}): Error
+(e: unknown): Error
 ```
 
 Added in v1.0.0 (function)
 
 Default value for the optional `onerror` argument of `tryCatch`
 
-## tryCatch
+## ~~tryCatch~~ (deprecated)
 
 ```ts
-<A>(f: Lazy<A>, onerror: (e: {}) => Error = toError): Either<Error, A>
+<A>(f: Lazy<A>, onerror: (e: unknown) => Error = toError): Either<Error, A>
 ```
 
 Added in v1.0.0 (function)
 
-Note: `onerror` is typed with `{}` for backward compatibility, however if you are
-running typescript@3.0.0+ it is recommended to add an explicit type annotation
-leveraging the `unknown` type
+Use [tryCatch2v](#trycatch2v)
+
+## tryCatch2v
 
 ```ts
-tryCatch(() => ..., (e: unknown) => ...)
+<L, A>(f: Lazy<A>, onerror: (e: unknown) => L): Either<L, A>
 ```
+
+Added in v1.11.0 (function)
diff --git a/docs/IOEither.md b/docs/IOEither.md
index 81d55cb8..81279bda 100644
--- a/docs/IOEither.md
+++ b/docs/IOEither.md
@@ -162,10 +162,20 @@ Added in v1.6.0 (function)
 
 Added in v1.6.0 (function)
 
-## tryCatch
+## ~~tryCatch~~ (deprecated)
 
 ```ts
-<A>(f: Lazy<A>, onerror: (reason: {}) => Error = toError): IOEither<Error, A>
+<A>(f: Lazy<A>, onerror: (reason: unknown) => Error = toError): IOEither<Error, A>
 ```
 
 Added in v1.6.0 (function)
+
+Use [tryCatch2v](#trycatch2v)
+
+## tryCatch2v
+
+```ts
+<L, A>(f: Lazy<A>, onerror: (reason: unknown) => L): IOEither<L, A>
+```
+
+Added in v1.11.0 (function)
diff --git a/docs/NonEmptyArray.md b/docs/NonEmptyArray.md
index 99489071..93fb7a1d 100644
--- a/docs/NonEmptyArray.md
+++ b/docs/NonEmptyArray.md
@@ -161,6 +161,116 @@ import { NonEmptyArray } from 'fp-ts/lib/NonEmptyArray'
 assert.strictEqual(new NonEmptyArray(1, [2, 3]).extract(), 1)
 ```
 
+## filter
+
+```ts
+(predicate: Predicate<A>): Option<NonEmptyArray<A>>
+```
+
+Added in v1.11.0 (method)
+
+Filter an NonEmptyArray, keeping the elements which satisfy a predicate function, creating a new NonEmptyArray or returning `None` in case the resulting NonEmptyArray would have no remaining elements.
+
+## findFirst
+
+```ts
+(predicate: Predicate<A>): Option<A>
+```
+
+Added in v1.0.0 (method)
+
+## findIndex
+
+```ts
+(predicate: Predicate<A>): Option<number>
+```
+
+Added in v1.11.0 (method)
+
+Find the first index for which a predicate holds
+
+_Example_
+
+```ts
+import { NonEmptyArray } from 'fp-ts/lib/NonEmptyArray'
+import { some, none } from 'fp-ts/lib/Option'
+
+assert.deepEqual(new NonEmptyArray(1, [2, 3]).findIndex(x => x === 2), some(1))
+assert.deepEqual(new NonEmptyArray<number>(1, []).findIndex(x => x === 2), none)
+```
+
+## findLast
+
+```ts
+(predicate: Predicate<A>): Option<A>
+```
+
+Added in v1.0.0 (method)
+
+## findLastIndex
+
+```ts
+(predicate: Predicate<A>): Option<number>
+```
+
+Added in v1.11.0 (method)
+
+Returns the index of the last element of the list which matches the predicate
+
+_Example_
+
+```ts
+import { NonEmptyArray } from 'fp-ts/lib/NonEmptyArray'
+import { some, none } from 'fp-ts/lib/Option'
+
+interface X {
+  a: number
+  b: number
+}
+const xs: NonEmptyArray<X> = new NonEmptyArray({ a: 1, b: 0 }, [{ a: 1, b: 1 }])
+assert.deepEqual(xs.findLastIndex(x => x.a === 1), some(1))
+assert.deepEqual(xs.findLastIndex(x => x.a === 4), none)
+```
+
+## index
+
+```ts
+(i: number): Option<A>
+```
+
+Added in v1.11.0 (method)
+
+This function provides a safe way to read a value at a particular index from an NonEmptyArray
+
+_Example_
+
+```ts
+import { NonEmptyArray } from 'fp-ts/lib/NonEmptyArray'
+import { some, none } from 'fp-ts/lib/Option'
+
+assert.deepEqual(new NonEmptyArray(1, [2, 3]).index(1), some(2))
+assert.deepEqual(new NonEmptyArray(1, [2, 3]).index(3), none)
+```
+
+## insertAt
+
+```ts
+(i: number, a: A): Option<NonEmptyArray<A>>
+```
+
+Added in v1.11.0 (method)
+
+Insert an element at the specified index, creating a new NonEmptyArray, or returning `None` if the index is out of bounds
+
+_Example_
+
+```ts
+import { NonEmptyArray } from 'fp-ts/lib/NonEmptyArray'
+import { some } from 'fp-ts/lib/Option'
+
+assert.deepEqual(new NonEmptyArray(1, [2, 3, 4]).insertAt(2, 5), some(new NonEmptyArray(1, [2, 5, 3, 4])))
+```
+
 ## inspect
 
 ```ts
@@ -339,6 +449,26 @@ Added in v1.0.0 (method)
 
 Return stringified representation of this [NonEmptyArray](./NonEmptyArray.md)
 
+## updateAt
+
+```ts
+(i: number, a: A): Option<NonEmptyArray<A>>
+```
+
+Added in v1.11.0 (method)
+
+Change the element at the specified index, creating a new NonEmptyArray, or returning `None` if the index is out of bounds
+
+_Example_
+
+```ts
+import { NonEmptyArray } from 'fp-ts/lib/NonEmptyArray'
+import { some, none } from 'fp-ts/lib/Option'
+
+assert.deepEqual(new NonEmptyArray(1, [2, 3]).updateAt(1, 1), some(new NonEmptyArray(1, [1, 3])))
+assert.deepEqual(new NonEmptyArray(1, []).updateAt(1, 1), none)
+```
+
 ## fromArray
 
 ```ts
diff --git a/docs/Reader.md b/docs/Reader.md
index bd44ccaa..322e94ac 100644
--- a/docs/Reader.md
+++ b/docs/Reader.md
@@ -10,7 +10,7 @@ title: Module Reader
 ## reader
 
 ```ts
-Monad2<URI>
+Monad2<URI> & Profunctor2<URI> & Category2<URI> & Strong2<URI> & Choice2<URI>
 ```
 
 Added in v1.0.0 (instance)
diff --git a/docs/ReaderTaskEither.md b/docs/ReaderTaskEither.md
index 6b85aeaa..79b20746 100644
--- a/docs/ReaderTaskEither.md
+++ b/docs/ReaderTaskEither.md
@@ -253,7 +253,7 @@ Added in v1.6.0 (function)
 ```ts
 <E, L, A>(
   f: (e: E) => Promise<A>,
-  onrejected: (reason: {}) => L
+  onrejected: (reason: unknown) => L
 ): ReaderTaskEither<E, L, A>
 ```
 
diff --git a/docs/Semigroupoid.md b/docs/Semigroupoid.md
index 8c5b08b1..c35968a0 100644
--- a/docs/Semigroupoid.md
+++ b/docs/Semigroupoid.md
@@ -12,7 +12,7 @@ title: Module Semigroupoid
 ```ts
 interface Semigroupoid<F> {
   readonly URI: F
-  readonly compose: <L, A, B>(bc: HKT2<F, A, B>, ab: HKT2<F, L, A>) => HKT2<F, L, B>
+  readonly compose: <L, A, B>(ab: HKT2<F, A, B>, la: HKT2<F, L, A>) => HKT2<F, L, B>
 }
 ```
 
diff --git a/docs/Strong.md b/docs/Strong.md
new file mode 100644
index 00000000..434966d5
--- /dev/null
+++ b/docs/Strong.md
@@ -0,0 +1,89 @@
+---
+id: Strong
+title: Module Strong
+---
+
+[← Back](.)
+
+[Source](https://github.com/gcanti/fp-ts/blob/master/src/Strong.ts)
+
+# Strong
+
+```ts
+interface Strong<F> extends Profunctor<F> {
+  readonly first: <A, B, C>(pab: HKT2<F, A, B>) => HKT2<F, [A, C], [B, C]>
+  readonly second: <A, B, C>(pab: HKT2<F, B, C>) => HKT2<F, [A, B], [A, C]>
+}
+```
+
+Added in v1.11.0 (type class)
+
+The `Strong` class extends `Profunctor` with combinators for working with product types.
+
+`first` and `second` lift values in a `Profunctor` to act on the first and second components of a tuple,
+respectively.
+
+Another way to think about Strong is to piggyback on the intuition of
+inputs and outputs. Rewriting the type signature in this light then yields:
+
+```purescript
+first ::  forall input output a. p input output -> p (Tuple input a) (Tuple output a)
+second :: forall input output a. p input output -> p (Tuple a input) (Tuple a output)
+```
+
+If we specialize the profunctor p to the function arrow, we get the following type
+signatures, which may look a bit more familiar:
+
+```purescript
+first ::  forall input output a. (input -> output) -> (Tuple input a) -> (Tuple output a)
+second :: forall input output a. (input -> output) -> (Tuple a input) -> (Tuple a output)
+```
+
+So, when the `profunctor` is `Function` application, `first` essentially applies your function
+to the first element of a tuple, and `second` applies it to the second element (same as `map` would do).
+
+## fanout
+
+```ts
+fanout<F>(
+  F: Category<F> & Strong<F>
+): <A, B, C>(pab: HKT2<F, A, B>, pac: HKT2<F, A, C>) => HKT2<F, A, [B, C]>
+```
+
+Added in v1.11.0 (function)
+
+Compose a value which introduces a tuple from two values, each introducing one side of the tuple.
+
+This combinator is useful when assembling values from smaller components, because it provides a way to support two
+different types of output.
+
+Specializing `(&&&)` to function application would look like this:
+
+```purescript
+(&&&) :: forall a b c. (a -> b) -> (a -> c) -> (a -> (Tuple b c))
+```
+
+We take two functions, `f` and `g`, with the same parameter type and we transform them into a single function which
+takes one parameter and returns a tuple of the results of running `f` and `g` on the parameter, respectively. This
+allows us to run two parallel computations on the same input and return both results in a tuple.
+
+## splitStrong
+
+```ts
+splitStrong<F>(
+  F: Category<F> & Strong<F>
+): <A, B, C, D>(pab: HKT2<F, A, B>, pcd: HKT2<F, C, D>) => HKT2<F, [A, C], [B, D]>
+```
+
+Added in v1.11.0 (function)
+
+Compose a value acting on a tuple from two values, each acting on one of the components of the tuple.
+
+Specializing `(***)` to function application would look like this:
+
+```purescript
+(***) :: forall a b c d. (a -> b) -> (c -> d) -> (Tuple a c) -> (Tuple b d)
+```
+
+We take two functions, `f` and `g`, and we transform them into a single function which takes a tuple and maps `f`
+over the first element and `g` over the second. Just like `bi-map` would do for the `bi-functor` instance of tuple.
diff --git a/docs/Task.md b/docs/Task.md
index 4e578a52..07b035bb 100644
--- a/docs/Task.md
+++ b/docs/Task.md
@@ -151,7 +151,7 @@ Added in v1.0.0 (function)
 ## tryCatch
 
 ```ts
-<L, A>(f: Lazy<Promise<A>>, onrejected: (reason: {}) => L): Task<Either<L, A>>
+<L, A>(f: Lazy<Promise<A>>, onrejected: (reason: unknown) => L): Task<Either<L, A>>
 ```
 
 Added in v1.0.0 (function)
diff --git a/docs/TaskEither.md b/docs/TaskEither.md
index b3efeb13..10744b46 100644
--- a/docs/TaskEither.md
+++ b/docs/TaskEither.md
@@ -108,6 +108,22 @@ Added in v1.2.0 (method)
 
 Added in v1.0.0 (method)
 
+## filterOrElse
+
+```ts
+(p: Predicate<A>, zero: L): TaskEither<L, A>
+```
+
+Added in v1.11.0 (method)
+
+## filterOrElseL
+
+```ts
+(p: Predicate<A>, zero: (a: A) => L): TaskEither<L, A>
+```
+
+Added in v1.11.0 (method)
+
 ## fold
 
 ```ts
@@ -306,7 +322,7 @@ assert.strictEqual(stat.length, 0)
 ## tryCatch
 
 ```ts
-<L, A>(f: Lazy<Promise<A>>, onrejected: (reason: {}) => L): TaskEither<L, A>
+<L, A>(f: Lazy<Promise<A>>, onrejected: (reason: unknown) => L): TaskEither<L, A>
 ```
 
 Added in v1.0.0 (function)
diff --git a/docs/index.md b/docs/index.md
index 06c1f4bc..891866cb 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -15,6 +15,7 @@
 - [Category](./Category.md)
 - [Chain](./Chain.md)
 - [ChainRec](./ChainRec.md)
+- [Choice](./Choice.md)
 - [Comonad](./Comonad.md)
 - [Compactable](./Compactable.md)
 - [Console](./Console.md)
@@ -72,6 +73,7 @@
 - [StateT](./StateT.md)
 - [Store](./Store.md)
 - [StrMap](./StrMap.md)
+- [Strong](./Strong.md)
 - [Task](./Task.md)
 - [TaskEither](./TaskEither.md)
 - [These](./These.md)
diff --git a/package-lock.json b/package-lock.json
index ce779560..7ee1279d 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -6814,18 +6814,18 @@
       "integrity": "sha1-mPMMAurjzecAYgHkwzywi0hYHu0=",
       "dev": true,
       "requires": {
-        "babel-code-frame": "^6.22.0",
-        "builtin-modules": "^1.1.1",
-        "chalk": "^2.3.0",
-        "commander": "^2.12.1",
-        "diff": "^3.2.0",
-        "glob": "^7.1.1",
-        "js-yaml": "^3.7.0",
-        "minimatch": "^3.0.4",
-        "resolve": "^1.3.2",
-        "semver": "^5.3.0",
-        "tslib": "^1.8.0",
-        "tsutils": "^2.27.2"
+        "babel-code-frame": "6.26.0",
+        "builtin-modules": "1.1.1",
+        "chalk": "2.4.1",
+        "commander": "2.19.0",
+        "diff": "3.5.0",
+        "glob": "7.1.3",
+        "js-yaml": "3.11.0",
+        "minimatch": "3.0.4",
+        "resolve": "1.6.0",
+        "semver": "5.4.1",
+        "tslib": "1.9.0",
+        "tsutils": "2.29.0"
       },
       "dependencies": {
         "ansi-styles": {
@@ -6843,9 +6843,9 @@
           "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==",
           "dev": true,
           "requires": {
-            "ansi-styles": "^3.2.1",
-            "escape-string-regexp": "^1.0.5",
-            "supports-color": "^5.3.0"
+            "ansi-styles": "3.2.1",
+            "escape-string-regexp": "1.0.5",
+            "supports-color": "5.5.0"
           }
         },
         "glob": {
@@ -6889,28 +6889,28 @@
       }
     },
     "tslint-config-standard": {
-      "version": "7.0.0",
-      "resolved": "https://registry.npmjs.org/tslint-config-standard/-/tslint-config-standard-7.0.0.tgz",
-      "integrity": "sha512-QCrLt8WwiRgZpRSgRsk6cExy8/Vipa/5fHespm4Q1ly90EB6Lni04Ub8dkEW10bV3fPN3SkxEwj41ZOe/knCZA==",
+      "version": "8.0.1",
+      "resolved": "https://registry.npmjs.org/tslint-config-standard/-/tslint-config-standard-8.0.1.tgz",
+      "integrity": "sha512-OWG+NblgjQlVuUS/Dmq3ax2v5QDZwRx4L0kEuDi7qFY9UI6RJhhNfoCV1qI4el8Fw1c5a5BTrjQJP0/jhGXY/Q==",
       "dev": true,
       "requires": {
-        "tslint-eslint-rules": "^4.1.1"
+        "tslint-eslint-rules": "5.4.0"
       }
     },
     "tslint-eslint-rules": {
-      "version": "4.1.1",
-      "resolved": "https://registry.npmjs.org/tslint-eslint-rules/-/tslint-eslint-rules-4.1.1.tgz",
-      "integrity": "sha1-fDDniC8mvCdr/5HSOEl1xp2viLo=",
+      "version": "5.4.0",
+      "resolved": "https://registry.npmjs.org/tslint-eslint-rules/-/tslint-eslint-rules-5.4.0.tgz",
+      "integrity": "sha512-WlSXE+J2vY/VPgIcqQuijMQiel+UtmXS+4nvK4ZzlDiqBfXse8FAvkNnTcYhnQyOTW5KFM+uRRGXxYhFpuBc6w==",
       "dev": true,
       "requires": {
-        "doctrine": "^0.7.2",
-        "tslib": "^1.0.0",
-        "tsutils": "^1.4.0"
+        "doctrine": "0.7.2",
+        "tslib": "1.9.0",
+        "tsutils": "3.5.0"
       },
       "dependencies": {
         "doctrine": {
           "version": "0.7.2",
-          "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-0.7.2.tgz",
+          "resolved": "http://registry.npmjs.org/doctrine/-/doctrine-0.7.2.tgz",
           "integrity": "sha1-fLhgNZujvpDgQLJrcpzkv6ZUxSM=",
           "dev": true,
           "requires": {
@@ -6933,10 +6933,13 @@
       }
     },
     "tsutils": {
-      "version": "1.9.1",
-      "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-1.9.1.tgz",
-      "integrity": "sha1-ufmrROVa+WgYMdXyjQrur1x1DLA=",
-      "dev": true
+      "version": "3.5.0",
+      "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.5.0.tgz",
+      "integrity": "sha512-/FZ+pEJQixWruFejFxNPRSwg+iF6aw7PYZVRqUscJ7EnzV3zieI8byfZziUR7QjCuJFulq8SEe9JcGflO4ze4Q==",
+      "dev": true,
+      "requires": {
+        "tslib": "1.9.0"
+      }
     },
     "tunnel-agent": {
       "version": "0.6.0",
diff --git a/package.json b/package.json
index 384e5ce0..93e60f7e 100644
--- a/package.json
+++ b/package.json
@@ -44,10 +44,10 @@
     "rimraf": "2.6.2",
     "ts-jest": "22.4.2",
     "ts-node": "7.0.1",
-    "ts-simple-ast": "^17.1.1",
+    "ts-simple-ast": "17.1.1",
     "tslint": "5.11.0",
-    "tslint-config-standard": "7.0.0",
-    "typescript": "^3.1.6",
+    "tslint-config-standard": "8.0.1",
+    "typescript": "3.1.6",
     "typings-checker": "2.0.0"
   },
   "tags": [
diff --git a/src/Category.ts b/src/Category.ts
index 13b48229..b15c113c 100644
--- a/src/Category.ts
+++ b/src/Category.ts
@@ -1,5 +1,5 @@
-import { HKT2, Type2, Type3, URIS2, URIS3 } from './HKT'
-import { Semigroupoid, Semigroupoid2, Semigroupoid3, Semigroupoid3C } from './Semigroupoid'
+import { HKT2, Type2, Type3, URIS2, URIS3, URIS4, Type4 } from './HKT'
+import { Semigroupoid, Semigroupoid2, Semigroupoid3, Semigroupoid3C, Semigroupoid4 } from './Semigroupoid'
 
 /**
  * @typeclass
@@ -17,6 +17,10 @@ export interface Category3<F extends URIS3> extends Semigroupoid3<F> {
   readonly id: <U, A>() => Type3<F, U, A, A>
 }
 
+export interface Category4<F extends URIS4> extends Semigroupoid4<F> {
+  readonly id: <X, U, A>() => Type4<F, X, U, A, A>
+}
+
 export interface Category3C<F extends URIS3, U> extends Semigroupoid3C<F, U> {
   readonly id: <A>() => Type3<F, U, A, A>
 }
diff --git a/src/Choice.ts b/src/Choice.ts
new file mode 100644
index 00000000..3f41fc3a
--- /dev/null
+++ b/src/Choice.ts
@@ -0,0 +1,137 @@
+import { Either } from './Either'
+import { HKT2, Type2, Type3, URIS2, URIS3, URIS4, Type4 } from './HKT'
+import { Profunctor, Profunctor2, Profunctor3, Profunctor4 } from './Profunctor'
+import { Category, Category2, Category3 } from './Category'
+import { identity } from './function'
+
+// Adapted from https://github.com/purescript/purescript-profunctor/blob/master/src/Data/Profunctor/Choice.purs
+
+/**
+ * The `Choice` class extends `Profunctor` with combinators for working with
+ * sum types.
+ *
+ * `left` and `right` lift values in a `Profunctor` to act on the `Left` and
+ * `Right` components of a sum, respectively.
+ *
+ * Looking at `Choice` through the intuition of inputs and outputs
+ * yields the following type signature:
+ *
+ * ```purescript
+ * left ::  forall input output a. p input output -> p (Either input a) (Either output a)
+ * right :: forall input output a. p input output -> p (Either a input) (Either a output)
+ * ```
+ *
+ * If we specialize the profunctor `p` to the `function` arrow, we get the following type
+ * signatures:
+ *
+ * ```purescript
+ * left ::  forall input output a. (input -> output) -> (Either input a) -> (Either output a)
+ * right :: forall input output a. (input -> output) -> (Either a input) -> (Either a output)
+ * ```
+ *
+ * When the `profunctor` is `Function` application, `left` allows you to map a function over the
+ * left side of an `Either`, and `right` maps it over the right side (same as `map` would do).
+ *
+ * @typeclass
+ * @since 1.11.0
+ */
+export interface Choice<F> extends Profunctor<F> {
+  readonly left: <A, B, C>(pab: HKT2<F, A, B>) => HKT2<F, Either<A, C>, Either<B, C>>
+  readonly right: <A, B, C>(pbc: HKT2<F, B, C>) => HKT2<F, Either<A, B>, Either<A, C>>
+}
+
+export interface Choice2<F extends URIS2> extends Profunctor2<F> {
+  readonly left: <A, B, C>(pab: Type2<F, A, B>) => Type2<F, Either<A, C>, Either<B, C>>
+  readonly right: <A, B, C>(pbc: Type2<F, B, C>) => Type2<F, Either<A, B>, Either<A, C>>
+}
+
+export interface Choice3<F extends URIS3> extends Profunctor3<F> {
+  readonly left: <U, A, B, C>(pab: Type3<F, U, A, B>) => Type3<F, U, Either<A, C>, Either<B, C>>
+  readonly right: <U, A, B, C>(pbc: Type3<F, U, B, C>) => Type3<F, U, Either<A, B>, Either<A, C>>
+}
+
+export interface Choice4<F extends URIS4> extends Profunctor4<F> {
+  readonly left: <X, U, A, B, C>(pab: Type4<F, X, U, A, B>) => Type4<F, X, U, Either<A, C>, Either<B, C>>
+  readonly right: <X, U, A, B, C>(pbc: Type4<F, X, U, B, C>) => Type4<F, X, U, Either<A, B>, Either<A, C>>
+}
+
+/**
+ * Compose a value acting on a sum from two values, each acting on one of
+ * the components of the sum.
+ *
+ * Specializing `(+++)` to function application would look like this:
+ *
+ * ```purescript
+ * (+++) :: forall a b c d. (a -> b) -> (c -> d) -> (Either a c) -> (Either b d)
+ * ```
+ *
+ * We take two functions, `f` and `g`, and we transform them into a single function which
+ * takes an `Either`and maps `f` over the left side and `g` over the right side.  Just like
+ * `bi-map` would do for the `bi-functor` instance of `Either`.
+ *
+ * @function
+ * @since 1.11.0
+ */
+export function splitChoice<F extends URIS3>(
+  F: Category3<F> & Choice3<F>
+): <U, A, B, C, D>(pab: Type3<F, U, A, B>, pcd: Type3<F, U, C, D>) => Type3<F, U, Either<A, C>, Either<B, D>>
+export function splitChoice<F extends URIS2>(
+  F: Category2<F> & Choice2<F>
+): <A, B, C, D>(pab: Type2<F, A, B>, pcd: Type2<F, C, D>) => Type2<F, Either<A, C>, Either<B, D>>
+export function splitChoice<F>(
+  F: Category<F> & Choice<F>
+): <A, B, C, D>(pab: HKT2<F, A, B>, pcd: HKT2<F, C, D>) => HKT2<F, Either<A, C>, Either<B, D>>
+export function splitChoice<F>(
+  F: Category<F> & Choice<F>
+): <A, B, C, D>(pab: HKT2<F, A, B>, pcd: HKT2<F, C, D>) => HKT2<F, Either<A, C>, Either<B, D>> {
+  return (pab, pcd) =>
+    F.compose(
+      F.left(pab),
+      F.right(pcd)
+    )
+}
+
+/**
+ * Compose a value which eliminates a sum from two values, each eliminating
+ * one side of the sum.
+ *
+ * This combinator is useful when assembling values from smaller components,
+ * because it provides a way to support two different types of input.
+ *
+ * Specializing `(|||)` to function application would look like this:
+ *
+ * ```purescript
+ * (|||) :: forall a b c d. (a -> c) -> (b -> c) -> Either a b -> c
+ * ```
+ *
+ * We take two functions, `f` and `g`, which both return the same type `c` and we transform them into a
+ * single function which takes an `Either` value with the parameter type of `f` on the left side and
+ * the parameter type of `g` on the right side. The function then runs either `f` or `g`, depending on
+ * whether the `Either` value is a `Left` or a `Right`.
+ * This allows us to bundle two different computations which both have the same result type into one
+ * function which will run the approriate computation based on the parameter supplied in the `Either` value.
+ *
+ * @function
+ * @since 1.11.0
+ */
+export function fanin<F extends URIS3>(
+  F: Category3<F> & Choice3<F>
+): <U, A, B, C>(pac: Type3<F, U, A, C>, pbc: Type3<F, U, B, C>) => Type3<F, U, Either<A, B>, C>
+export function fanin<F extends URIS2>(
+  F: Category2<F> & Choice2<F>
+): <A, B, C>(pac: Type2<F, A, C>, pbc: Type2<F, B, C>) => Type2<F, Either<A, B>, C>
+export function fanin<F>(
+  F: Category<F> & Choice<F>
+): <A, B, C>(pac: HKT2<F, A, C>, pbc: HKT2<F, B, C>) => HKT2<F, Either<A, B>, C>
+export function fanin<F>(
+  F: Category<F> & Choice<F>
+): <A, B, C>(pac: HKT2<F, A, C>, pbc: HKT2<F, B, C>) => HKT2<F, Either<A, B>, C> {
+  const splitChoiceF = splitChoice(F)
+  return <A, B, C>(pac: HKT2<F, A, C>, pbc: HKT2<F, B, C>): HKT2<F, Either<A, B>, C> => {
+    const join: HKT2<F, Either<C, C>, C> = F.promap(F.id<C>(), e => e.fold(identity, identity), identity)
+    return F.compose(
+      join,
+      splitChoiceF(pac, pbc)
+    )
+  }
+}
diff --git a/src/Either.ts b/src/Either.ts
index f2bb9119..a7333412 100644
--- a/src/Either.ts
+++ b/src/Either.ts
@@ -462,7 +462,7 @@ export const fromNullable = <L>(defaultValue: L) => <A>(a: A | null | undefined)
  * @function
  * @since 1.0.0
  */
-export const toError = (e: {}): Error => {
+export const toError = (e: unknown): Error => {
   if (e instanceof Error) {
     return e
   } else {
@@ -471,18 +471,20 @@ export const toError = (e: {}): Error => {
 }
 
 /**
- * Note: `onerror` is typed with `{}` for backward compatibility, however if you are
- * running typescript@3.0.0+ it is recommended to add an explicit type annotation
- * leveraging the `unknown` type
- *
- * ```ts
- * tryCatch(() => ..., (e: unknown) => ...)
- * ```
- *
+ * Use {@link tryCatch2v}
  * @function
  * @since 1.0.0
+ * @deprecated
+ */
+export const tryCatch = <A>(f: Lazy<A>, onerror: (e: unknown) => Error = toError): Either<Error, A> => {
+  return tryCatch2v(f, onerror)
+}
+
+/**
+ * @function
+ * @since 1.11.0
  */
-export const tryCatch = <A>(f: Lazy<A>, onerror: (e: {}) => Error = toError): Either<Error, A> => {
+export const tryCatch2v = <L, A>(f: Lazy<A>, onerror: (e: unknown) => L): Either<L, A> => {
   try {
     return right(f())
   } catch (e) {
diff --git a/src/Functor.ts b/src/Functor.ts
index 7d7b3b40..2c4f1d48 100644
--- a/src/Functor.ts
+++ b/src/Functor.ts
@@ -1,5 +1,5 @@
-import { HKT, Type, Type2, Type3, URIS, URIS2, URIS3 } from './HKT'
 import { constant } from './function'
+import { HKT, Type, Type2, Type3, Type4, URIS, URIS2, URIS3, URIS4 } from './HKT'
 
 /**
  * A `Functor` is a type constructor which supports a mapping operation `map`.
@@ -35,6 +35,11 @@ export interface Functor3<F extends URIS3> {
   readonly map: <U, L, A, B>(fa: Type3<F, U, L, A>, f: (a: A) => B) => Type3<F, U, L, B>
 }
 
+export interface Functor4<F extends URIS4> {
+  readonly URI: F
+  readonly map: <X, U, L, A, B>(fa: Type4<F, X, U, L, A>, f: (a: A) => B) => Type4<F, X, U, L, B>
+}
+
 export interface Functor2C<F extends URIS2, L> {
   readonly URI: F
   readonly _L: L
diff --git a/src/HKT.ts b/src/HKT.ts
index 9fd86fe6..963a0a5e 100644
--- a/src/HKT.ts
+++ b/src/HKT.ts
@@ -11,11 +11,16 @@ export interface HKT3<URI, U, L, A> extends HKT2<URI, L, A> {
   readonly _U: U
 }
 
+export interface HKT4<URI, X, U, L, A> extends HKT3<URI, U, L, A> {
+  readonly _X: X
+}
+
 // type-level dictionaries for HKTs
 
 export interface URI2HKT<A> {}
 export interface URI2HKT2<L, A> {}
 export interface URI2HKT3<U, L, A> {}
+export interface URI2HKT4<X, U, L, A> {}
 
 // URI constraints with dictionary integrity constraint
 
@@ -24,10 +29,14 @@ export type URIS2 = (URI2HKT2<any, any> & { never: HKT<never, never> })[keyof UR
 export type URIS3 = (URI2HKT3<any, any, any> & { never: HKT<never, never> })[
   | keyof URI2HKT3<any, any, any>
   | 'never']['_URI']
+export type URIS4 = (URI2HKT4<any, any, any, any> & { never: HKT<never, never> })[
+  | keyof URI2HKT4<any, any, any, any>
+  | 'never']['_URI']
 
 export type Type<URI extends URIS, A> = {} & URI2HKT<A>[URI]
 export type Type2<URI extends URIS2, L, A> = {} & URI2HKT2<L, A>[URI]
 export type Type3<URI extends URIS3, U, L, A> = {} & URI2HKT3<U, L, A>[URI]
+export type Type4<URI extends URIS4, X, U, L, A> = {} & URI2HKT4<X, U, L, A>[URI]
 
 // Type-level integrity check
 
@@ -35,4 +44,5 @@ export type Type3<URI extends URIS3, U, L, A> = {} & URI2HKT3<U, L, A>[URI]
 (null! as URI2HKT<any>) as { [k in keyof URI2HKT<any>]: HKT<k, any> }
 (null! as URI2HKT2<any, any>) as { [k in keyof URI2HKT2<any, any>]: HKT2<k, any, any> }
 (null! as URI2HKT3<any, any, any>) as { [k in keyof URI2HKT3<any, any, any>]: HKT3<k, any, any, any> }
+(null! as URI2HKT4<any, any, any, any>) as { [k in keyof URI2HKT4<any, any, any, any>]: HKT4<k, any, any, any, any> }
 /* tslint:enable */
diff --git a/src/IOEither.ts b/src/IOEither.ts
index e5cc96b6..e5ff6ade 100644
--- a/src/IOEither.ts
+++ b/src/IOEither.ts
@@ -1,5 +1,5 @@
 import { Bifunctor2 } from './Bifunctor'
-import { Either, left as eitherLeft, tryCatch as eitherTryCatch, toError } from './Either'
+import { Either, left as eitherLeft, tryCatch2v as eitherTryCatch2v, toError } from './Either'
 import * as eitherT from './EitherT'
 import { Monad2 } from './Monad'
 import { IO, io } from './IO'
@@ -147,11 +147,21 @@ export const fromLeft = <L, A>(l: L): IOEither<L, A> => {
 }
 
 /**
+ * Use {@link tryCatch2v}
  * @function
  * @since 1.6.0
+ * @deprecated
  */
-export const tryCatch = <A>(f: Lazy<A>, onerror: (reason: {}) => Error = toError): IOEither<Error, A> => {
-  return new IOEither(new IO(() => eitherTryCatch(f, onerror)))
+export const tryCatch = <A>(f: Lazy<A>, onerror: (reason: unknown) => Error = toError): IOEither<Error, A> => {
+  return tryCatch2v(f, onerror)
+}
+
+/**
+ * @function
+ * @since 1.11.0
+ */
+export const tryCatch2v = <L, A>(f: Lazy<A>, onerror: (reason: unknown) => L): IOEither<L, A> => {
+  return new IOEither(new IO(() => eitherTryCatch2v(f, onerror)))
 }
 
 /**
diff --git a/src/Profunctor.ts b/src/Profunctor.ts
index 592fc82d..780f2ab9 100644
--- a/src/Profunctor.ts
+++ b/src/Profunctor.ts
@@ -1,5 +1,5 @@
-import { Functor2, Functor3 } from './Functor'
-import { HKT2, Type2, Type3, URIS2, URIS3 } from './HKT'
+import { Functor2, Functor3, Functor4 } from './Functor'
+import { HKT2, Type2, Type3, Type4, URIS2, URIS3, URIS4 } from './HKT'
 
 /**
  * @typeclass
@@ -19,6 +19,10 @@ export interface Profunctor3<F extends URIS3> extends Functor3<F> {
   readonly promap: <U, A, B, C, D>(fbc: Type3<F, U, B, C>, f: (a: A) => B, g: (c: C) => D) => Type3<F, U, A, D>
 }
 
+export interface Profunctor4<F extends URIS4> extends Functor4<F> {
+  readonly promap: <X, U, A, B, C, D>(fbc: Type4<F, X, U, B, C>, f: (a: A) => B, g: (c: C) => D) => Type4<F, X, U, A, D>
+}
+
 /**
  * @function
  * @since 1.0.0
diff --git a/src/Reader.ts b/src/Reader.ts
index b545c478..5fe3cec1 100644
--- a/src/Reader.ts
+++ b/src/Reader.ts
@@ -1,5 +1,10 @@
+import { Category2 } from './Category'
+import { identity, tuple } from './function'
 import { Monad2 } from './Monad'
-import { identity } from './function'
+import { Profunctor2 } from './Profunctor'
+import { Strong2 } from './Strong'
+import { Choice2 } from './Choice'
+import { Either, left as eitherLeft, right as eitherRight } from './Either'
 
 declare module './HKT' {
   interface URI2HKT2<L, A> {
@@ -87,14 +92,49 @@ export const local = <E, E2 = E>(f: (e: E2) => E) => <A>(fa: Reader<E, A>): Read
   return fa.local(f)
 }
 
+const promap = <A, B, C, D>(fbc: Reader<B, C>, f: (a: A) => B, g: (c: C) => D): Reader<A, D> => {
+  return new Reader(a => g(fbc.run(f(a))))
+}
+
+const compose = <L, A, B>(ab: Reader<A, B>, la: Reader<L, A>): Reader<L, B> => {
+  return new Reader(l => ab.run(la.run(l)))
+}
+
+const id = <A>(): Reader<A, A> => {
+  return new Reader(identity)
+}
+
+const first = <A, B, C>(pab: Reader<A, B>): Reader<[A, C], [B, C]> => {
+  return new Reader(([a, c]) => tuple(pab.run(a), c))
+}
+
+const second = <A, B, C>(pbc: Reader<B, C>): Reader<[A, B], [A, C]> => {
+  return new Reader(([a, b]) => tuple(a, pbc.run(b)))
+}
+
+const left = <A, B, C>(pab: Reader<A, B>): Reader<Either<A, C>, Either<B, C>> => {
+  return new Reader(e => e.fold<Either<B, C>>(a => eitherLeft(pab.run(a)), eitherRight))
+}
+
+const right = <A, B, C>(pbc: Reader<B, C>): Reader<Either<A, B>, Either<A, C>> => {
+  return new Reader(e => e.fold<Either<A, C>>(eitherLeft, b => eitherRight(pbc.run(b))))
+}
+
 /**
  * @instance
  * @since 1.0.0
  */
-export const reader: Monad2<URI> = {
+export const reader: Monad2<URI> & Profunctor2<URI> & Category2<URI> & Strong2<URI> & Choice2<URI> = {
   URI,
   map,
   of,
   ap,
-  chain
+  chain,
+  promap,
+  compose,
+  id,
+  first,
+  second,
+  left,
+  right
 }
diff --git a/src/ReaderTaskEither.ts b/src/ReaderTaskEither.ts
index 1f4c66d4..d894861f 100644
--- a/src/ReaderTaskEither.ts
+++ b/src/ReaderTaskEither.ts
@@ -238,7 +238,7 @@ export const fromPredicate = <E, L, A>(
  */
 export const tryCatch = <E, L, A>(
   f: (e: E) => Promise<A>,
-  onrejected: (reason: {}) => L
+  onrejected: (reason: unknown) => L
 ): ReaderTaskEither<E, L, A> => {
   return new ReaderTaskEither(e => taskEither.tryCatch(() => f(e), onrejected))
 }
diff --git a/src/Semigroupoid.ts b/src/Semigroupoid.ts
index 3e45fad3..f956e47d 100644
--- a/src/Semigroupoid.ts
+++ b/src/Semigroupoid.ts
@@ -1,4 +1,4 @@
-import { HKT2, Type2, Type3, URIS2, URIS3 } from './HKT'
+import { HKT2, Type2, Type3, Type4, URIS2, URIS3, URIS4 } from './HKT'
 
 /**
  * @typeclass
@@ -6,21 +6,26 @@ import { HKT2, Type2, Type3, URIS2, URIS3 } from './HKT'
  */
 export interface Semigroupoid<F> {
   readonly URI: F
-  readonly compose: <L, A, B>(bc: HKT2<F, A, B>, ab: HKT2<F, L, A>) => HKT2<F, L, B>
+  readonly compose: <L, A, B>(ab: HKT2<F, A, B>, la: HKT2<F, L, A>) => HKT2<F, L, B>
 }
 
 export interface Semigroupoid2<F extends URIS2> {
   readonly URI: F
-  readonly compose: <L, A, B>(bc: Type2<F, A, B>, ab: Type2<F, L, A>) => Type2<F, L, B>
+  readonly compose: <L, A, B>(ab: Type2<F, A, B>, la: Type2<F, L, A>) => Type2<F, L, B>
 }
 
 export interface Semigroupoid3<F extends URIS3> {
   readonly URI: F
-  readonly compose: <U, L, A, B>(bc: Type3<F, U, A, B>, ab: Type3<F, U, L, A>) => Type3<F, U, L, B>
+  readonly compose: <U, L, A, B>(ab: Type3<F, U, A, B>, la: Type3<F, U, L, A>) => Type3<F, U, L, B>
+}
+
+export interface Semigroupoid4<F extends URIS4> {
+  readonly URI: F
+  readonly compose: <X, U, L, A, B>(ab: Type4<F, X, U, A, B>, la: Type4<F, X, U, L, A>) => Type4<F, X, U, L, B>
 }
 
 export interface Semigroupoid3C<F extends URIS3, U> {
   readonly URI: F
   readonly _U: U
-  readonly compose: <L, A, B>(bc: Type3<F, U, A, B>, ab: Type3<F, U, L, A>) => Type3<F, U, L, B>
+  readonly compose: <L, A, B>(ab: Type3<F, U, A, B>, la: Type3<F, U, L, A>) => Type3<F, U, L, B>
 }
diff --git a/src/Strong.ts b/src/Strong.ts
new file mode 100644
index 00000000..02c5a7f2
--- /dev/null
+++ b/src/Strong.ts
@@ -0,0 +1,135 @@
+import { Category, Category2, Category3, Category4 } from './Category'
+import { identity, tuple } from './function'
+import { HKT2, Type2, Type3, URIS2, URIS3, URIS4, Type4 } from './HKT'
+import { Profunctor, Profunctor2, Profunctor3, Profunctor4 } from './Profunctor'
+
+// Adapted from https://github.com/purescript/purescript-profunctor/blob/master/src/Data/Profunctor/Strong.purs
+
+/**
+ * The `Strong` class extends `Profunctor` with combinators for working with product types.
+ *
+ * `first` and `second` lift values in a `Profunctor` to act on the first and second components of a tuple,
+ * respectively.
+ *
+ * Another way to think about Strong is to piggyback on the intuition of
+ * inputs and outputs.  Rewriting the type signature in this light then yields:
+ *
+ * ```purescript
+ * first ::  forall input output a. p input output -> p (Tuple input a) (Tuple output a)
+ * second :: forall input output a. p input output -> p (Tuple a input) (Tuple a output)
+ * ```
+ *
+ * If we specialize the profunctor p to the function arrow, we get the following type
+ * signatures, which may look a bit more familiar:
+ *
+ * ```purescript
+ * first ::  forall input output a. (input -> output) -> (Tuple input a) -> (Tuple output a)
+ * second :: forall input output a. (input -> output) -> (Tuple a input) -> (Tuple a output)
+ * ```
+ *
+ * So, when the `profunctor` is `Function` application, `first` essentially applies your function
+ * to the first element of a tuple, and `second` applies it to the second element (same as `map` would do).
+ *
+ * @typeclass
+ * @since 1.11.0
+ */
+export interface Strong<F> extends Profunctor<F> {
+  readonly first: <A, B, C>(pab: HKT2<F, A, B>) => HKT2<F, [A, C], [B, C]>
+  readonly second: <A, B, C>(pab: HKT2<F, B, C>) => HKT2<F, [A, B], [A, C]>
+}
+
+export interface Strong2<F extends URIS2> extends Profunctor2<F> {
+  readonly first: <A, B, C>(pab: Type2<F, A, B>) => Type2<F, [A, C], [B, C]>
+  readonly second: <A, B, C>(pab: Type2<F, B, C>) => Type2<F, [A, B], [A, C]>
+}
+
+export interface Strong3<F extends URIS3> extends Profunctor3<F> {
+  readonly first: <U, A, B, C>(pab: Type3<F, U, A, B>) => Type3<F, U, [A, C], [B, C]>
+  readonly second: <U, A, B, C>(pab: Type3<F, U, B, C>) => Type3<F, U, [A, B], [A, C]>
+}
+
+export interface Strong4<F extends URIS4> extends Profunctor4<F> {
+  readonly first: <X, U, A, B, C>(pab: Type4<F, X, U, A, B>) => Type4<F, X, U, [A, C], [B, C]>
+  readonly second: <X, U, A, B, C>(pab: Type4<F, X, U, B, C>) => Type4<F, X, U, [A, B], [A, C]>
+}
+
+/**
+ * Compose a value acting on a tuple from two values, each acting on one of the components of the tuple.
+ *
+ * Specializing `(***)` to function application would look like this:
+ *
+ * ```purescript
+ * (***) :: forall a b c d. (a -> b) -> (c -> d) -> (Tuple a c) -> (Tuple b d)
+ * ```
+ *
+ * We take two functions, `f` and `g`, and we transform them into a single function which takes a tuple and maps `f`
+ * over the first element and `g` over the second.  Just like `bi-map` would do for the `bi-functor` instance of tuple.
+ *
+ * @function
+ * @since 1.11.0
+ */
+export function splitStrong<F extends URIS4>(
+  F: Category4<F> & Strong4<F>
+): <X, U, A, B, C, D>(pab: Type4<F, X, U, A, B>, pcd: Type4<F, X, U, C, D>) => Type4<F, X, U, [A, C], [B, D]>
+export function splitStrong<F extends URIS3>(
+  F: Category3<F> & Strong3<F>
+): <U, A, B, C, D>(pab: Type3<F, U, A, B>, pcd: Type3<F, U, C, D>) => Type3<F, U, [A, C], [B, D]>
+export function splitStrong<F extends URIS2>(
+  F: Category2<F> & Strong2<F>
+): <A, B, C, D>(pab: Type2<F, A, B>, pcd: Type2<F, C, D>) => Type2<F, [A, C], [B, D]>
+export function splitStrong<F>(
+  F: Category<F> & Strong<F>
+): <A, B, C, D>(pab: HKT2<F, A, B>, pcd: HKT2<F, C, D>) => HKT2<F, [A, C], [B, D]>
+export function splitStrong<F>(
+  F: Category<F> & Strong<F>
+): <A, B, C, D>(pab: HKT2<F, A, B>, pcd: HKT2<F, C, D>) => HKT2<F, [A, C], [B, D]> {
+  return (pab, pcd) =>
+    F.compose(
+      F.first(pab),
+      F.second(pcd)
+    )
+}
+
+/**
+ * Compose a value which introduces a tuple from two values, each introducing one side of the tuple.
+ *
+ * This combinator is useful when assembling values from smaller components, because it provides a way to support two
+ * different types of output.
+ *
+ * Specializing `(&&&)` to function application would look like this:
+ *
+ * ```purescript
+ * (&&&) :: forall a b c. (a -> b) -> (a -> c) -> (a -> (Tuple b c))
+ * ```
+ *
+ * We take two functions, `f` and `g`, with the same parameter type and we transform them into a single function which
+ * takes one parameter and returns a tuple of the results of running `f` and `g` on the parameter, respectively.  This
+ * allows us to run two parallel computations on the same input and return both results in a tuple.
+ *
+ * @function
+ * @since 1.11.0
+ */
+export function fanout<F extends URIS4>(
+  F: Category4<F> & Strong4<F>
+): <X, U, A, B, C>(pab: Type4<F, X, U, A, B>, pac: Type4<F, X, U, A, C>) => Type4<F, X, U, A, [B, C]>
+export function fanout<F extends URIS3>(
+  F: Category3<F> & Strong3<F>
+): <U, A, B, C>(pab: Type3<F, U, A, B>, pac: Type3<F, U, A, C>) => Type3<F, U, A, [B, C]>
+export function fanout<F extends URIS2>(
+  F: Category2<F> & Strong2<F>
+): <A, B, C>(pab: Type2<F, A, B>, pac: Type2<F, A, C>) => Type2<F, A, [B, C]>
+export function fanout<F>(
+  F: Category<F> & Strong<F>
+): <A, B, C>(pab: HKT2<F, A, B>, pac: HKT2<F, A, C>) => HKT2<F, A, [B, C]>
+export function fanout<F>(
+  F: Category<F> & Strong<F>
+): <A, B, C>(pab: HKT2<F, A, B>, pac: HKT2<F, A, C>) => HKT2<F, A, [B, C]> {
+  const splitStrongF = splitStrong(F)
+  return <A, B, C>(pab: HKT2<F, A, B>, pac: HKT2<F, A, C>): HKT2<F, A, [B, C]> => {
+    const split: HKT2<F, A, [A, A]> = F.promap(F.id<A>(), identity, a => tuple(a, a))
+    return F.compose(
+      splitStrongF(pab, pac),
+      split
+    )
+  }
+}
diff --git a/src/Task.ts b/src/Task.ts
index 637b51d6..6c30e587 100644
--- a/src/Task.ts
+++ b/src/Task.ts
@@ -139,7 +139,7 @@ export const getMonoid = <A>(M: Monoid<A>): Monoid<Task<A>> => {
  * @function
  * @since 1.0.0
  */
-export const tryCatch = <L, A>(f: Lazy<Promise<A>>, onrejected: (reason: {}) => L): Task<Either<L, A>> => {
+export const tryCatch = <L, A>(f: Lazy<Promise<A>>, onrejected: (reason: unknown) => L): Task<Either<L, A>> => {
   return new Task(() => f().then(a => right<L, A>(a), reason => left<L, A>(onrejected(reason))))
 }
 
diff --git a/src/TaskEither.ts b/src/TaskEither.ts
index 33204f67..094dbe96 100644
--- a/src/TaskEither.ts
+++ b/src/TaskEither.ts
@@ -125,6 +125,18 @@ export class TaskEither<L, A> {
   attempt<M = L>(): TaskEither<M, Either<L, A>> {
     return new TaskEither(this.value.map<Either<M, Either<L, A>>>(eitherRight))
   }
+  /**
+   * @since 1.11.0
+   */
+  filterOrElse(p: Predicate<A>, zero: L): TaskEither<L, A> {
+    return new TaskEither(this.value.map(e => e.filterOrElse(p, zero)))
+  }
+  /**
+   * @since 1.11.0
+   */
+  filterOrElseL(p: Predicate<A>, zero: (a: A) => L): TaskEither<L, A> {
+    return new TaskEither(this.value.map(e => e.filterOrElseL(p, zero)))
+  }
 }
 
 const map = <L, A, B>(fa: TaskEither<L, A>, f: (a: A) => B): TaskEither<L, B> => {
@@ -276,7 +288,7 @@ export const getApplyMonoid = <L, A>(M: Monoid<A>): Monoid<TaskEither<L, A>> =>
  * @function
  * @since 1.0.0
  */
-export const tryCatch = <L, A>(f: Lazy<Promise<A>>, onrejected: (reason: {}) => L): TaskEither<L, A> => {
+export const tryCatch = <L, A>(f: Lazy<Promise<A>>, onrejected: (reason: unknown) => L): TaskEither<L, A> => {
   return new TaskEither(taskTryCatch(f, onrejected))
 }
 
diff --git a/src/index.ts b/src/index.ts
index 74cc431b..bfe6abb8 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -28,6 +28,8 @@ import * as chain from './Chain'
 export { chain }
 import * as chainRec from './ChainRec'
 export { chainRec }
+import * as choice from './Choice'
+export { choice }
 import * as comonad from './Comonad'
 export { comonad }
 import * as console from './Console'
@@ -140,6 +142,8 @@ import * as store from './Store'
 export { store }
 import * as strmap from './StrMap'
 export { strmap }
+import * as strong from './Strong'
+export { strong }
 import * as task from './Task'
 export { task }
 import * as taskEither from './TaskEither'
diff --git a/tslint.json b/tslint.json
index e9101660..8b4e9867 100644
--- a/tslint.json
+++ b/tslint.json
@@ -8,7 +8,8 @@
     "ter-indent": false,
     "strict-boolean-expressions": true,
     "forin": true,
-    "no-console": false,
-    "array-type": [true, "generic"]
+    "no-console": true,
+    "array-type": [true, "generic"],
+    "deprecation": false
   }
 }


Test patch:
diff --git a/test/Choice.ts b/test/Choice.ts
new file mode 100644
index 00000000..b1c185e1
--- /dev/null
+++ b/test/Choice.ts
@@ -0,0 +1,23 @@
+import * as assert from 'assert'
+import { splitChoice, fanin } from '../src/Choice'
+import { Reader, reader } from '../src/Reader'
+import { right, left } from '../src/Either'
+
+describe('Choice', () => {
+  it('splitChoice', () => {
+    const ab = new Reader<string, number>(s => s.length)
+    const cd = new Reader<number, boolean>(n => n >= 2)
+    assert.deepEqual(splitChoice(reader)(ab, cd).run(right(3)), right(true))
+    assert.deepEqual(splitChoice(reader)(ab, cd).run(right(1)), right(false))
+    assert.deepEqual(splitChoice(reader)(ab, cd).run(left('foo')), left(3))
+  })
+
+  it('fanin', () => {
+    const ac = new Reader<string, boolean>(s => s === s.toLowerCase())
+    const bc = new Reader<number, boolean>(n => n >= 2)
+    assert.deepEqual(fanin(reader)(ac, bc).run(right(3)), true)
+    assert.deepEqual(fanin(reader)(ac, bc).run(right(1)), false)
+    assert.deepEqual(fanin(reader)(ac, bc).run(left('foo')), true)
+    assert.deepEqual(fanin(reader)(ac, bc).run(left('A')), false)
+  })
+})
diff --git a/test/Console.ts b/test/Console.ts
index f36dada6..ffa3fc31 100644
--- a/test/Console.ts
+++ b/test/Console.ts
@@ -3,46 +3,58 @@ import { log, info, error, warn } from '../src/Console'
 
 describe('Console', () => {
   it('log', () => {
+    // tslint:disable-next-line:no-console
     const log_ = console.log
     const logger: Array<any> = []
+    // tslint:disable-next-line:no-console
     console.log = (a: any) => {
       logger.push(a)
     }
     log('log').run()
     assert.deepEqual(logger, ['log'])
+    // tslint:disable-next-line:no-console
     console.log = log_
   })
 
   it('info', () => {
+    // tslint:disable-next-line:no-console
     const info_ = console.info
     const logger: Array<any> = []
+    // tslint:disable-next-line:no-console
     console.info = (a: any) => {
       logger.push(a)
     }
     info('info').run()
     assert.deepEqual(logger, ['info'])
+    // tslint:disable-next-line:no-console
     console.info = info_
   })
 
   it('error', () => {
+    // tslint:disable-next-line:no-console
     const error_ = console.error
     const logger: Array<any> = []
+    // tslint:disable-next-line:no-console
     console.error = (a: any) => {
       logger.push(a)
     }
     error('error').run()
     assert.deepEqual(logger, ['error'])
+    // tslint:disable-next-line:no-console
     console.error = error_
   })
 
   it('warn', () => {
+    // tslint:disable-next-line:no-console
     const warn_ = console.warn
     const logger: Array<any> = []
+    // tslint:disable-next-line:no-console
     console.warn = (a: any) => {
       logger.push(a)
     }
     warn('warn').run()
     assert.deepEqual(logger, ['warn'])
+    // tslint:disable-next-line:no-console
     console.warn = warn_
   })
 })
diff --git a/test/Either.ts b/test/Either.ts
index 57d2b057..069d6475 100644
--- a/test/Either.ts
+++ b/test/Either.ts
@@ -19,7 +19,9 @@ import {
   isRight,
   left,
   right,
-  tryCatch
+  tryCatch,
+  tryCatch2v,
+  toError
 } from '../src/Either'
 import * as F from '../src/Foldable'
 import { identity } from '../src/function'
@@ -87,7 +89,7 @@ describe('Either', () => {
     assert.deepEqual(gt2(1), left('Invalid number 1'))
   })
 
-  it('tryCatch', () => {
+  it('tryCatch2v', () => {
     const e1 = tryCatch(() => {
       return JSON.parse(`{}`)
     })
@@ -103,10 +105,17 @@ describe('Either', () => {
     })
     assert.deepEqual(e3, left(new Error('a string')))
 
+    const e4 = tryCatch(() => {
+      throw new Error('fpp')
+    }, toError)
+    assert.deepEqual(e4, left(new Error('foo')))
+
     type ObjectWithStatusCode = { statusCode: number }
-    const thrownIsObjectWithStatusCode = (thrown: {}): thrown is ObjectWithStatusCode =>
-      typeof thrown === 'object' && 'statusCode' in thrown
-    const onerror = (thrown: {}): Error => {
+    const thrownIsObjectWithStatusCode = (thrown: unknown): thrown is ObjectWithStatusCode => {
+      // tslint:disable-next-line:strict-type-predicates (upstream bug: https://github.com/palantir/tslint/issues/4107)
+      return typeof thrown === 'object' && thrown !== null && 'statusCode' in thrown
+    }
+    const onerror = (thrown: unknown): Error => {
       if (thrownIsObjectWithStatusCode(thrown)) {
         return new Error(`Bad response: ${thrown.statusCode}`)
       } else if (thrown instanceof Error) {
@@ -115,10 +124,10 @@ describe('Either', () => {
         return new Error('Unexpected error')
       }
     }
-    const e4 = tryCatch(() => {
+    const e5 = tryCatch2v(() => {
       throw { statusCode: 404 }
     }, onerror)
-    assert.deepEqual(e4, left(new Error('Bad response: 404')))
+    assert.deepEqual(e5, left(new Error('Bad response: 404')))
   })
 
   it('getOrElse', () => {
diff --git a/test/NonEmptyArray.ts b/test/NonEmptyArray.ts
index 8e30301f..fa9fb439 100644
--- a/test/NonEmptyArray.ts
+++ b/test/NonEmptyArray.ts
@@ -176,6 +176,7 @@ describe('NonEmptyArray', () => {
   it('index', () => {
     const arr = new NonEmptyArray(1, [2, 3])
     assert.deepEqual(arr.index(-1), none)
+    assert.deepEqual(arr.index(0), some(1))
     assert.deepEqual(arr.index(3), none)
     assert.deepEqual(arr.index(1), some(2))
   })
@@ -199,7 +200,8 @@ describe('NonEmptyArray', () => {
     const arr = new NonEmptyArray(a1, [a2, a3])
     assert.deepEqual(arr.findLast(({ x }) => x === 1).map(x => x === a2), some(true))
     assert.deepEqual(arr.findLast(({ x }) => x === 2), some(a3))
-    assert.equal(arr.findLast(({ x }) => x === 10), none)
+    assert.deepEqual(arr.findLast(({ x }) => x === 10), none)
+    assert.deepEqual(new NonEmptyArray(a1, []).findLast(({ x }) => x === 1), some(a1))
   })
 
   it('findIndex', () => {
@@ -222,6 +224,7 @@ describe('NonEmptyArray', () => {
     assert.deepEqual(arr.findLastIndex(({ x }) => x === 1), some(1))
     assert.deepEqual(arr.findLastIndex(({ x }) => x === 2), some(2))
     assert.deepEqual(arr.findLastIndex(({ x }) => x === 10), none)
+    assert.deepEqual(new NonEmptyArray(a1, []).findLastIndex(({ x }) => x === 1), some(0))
   })
 
   it('insertAt', () => {
diff --git a/test/Reader.ts b/test/Reader.ts
index 01341930..b8aa8185 100644
--- a/test/Reader.ts
+++ b/test/Reader.ts
@@ -52,4 +52,27 @@ describe('Reader', () => {
     const x = local((e2: E2) => e2.name)(new Reader((e: E) => e.length))
     assert.strictEqual(x.run({ name: 'foo' }), 3)
   })
+
+  it('promap', () => {
+    const x = new Reader<string, number>(s => s.length)
+    const y = reader.promap(x, (a: { name: string }) => a.name, n => n >= 2)
+    assert.strictEqual(y.run({ name: 'foo' }), true)
+    assert.strictEqual(y.run({ name: 'a' }), false)
+  })
+
+  it('id', () => {
+    const x = reader.id<number>()
+    assert.strictEqual(x.run(1), 1)
+  })
+
+  it('compose', () => {
+    const x = new Reader<string, number>(s => s.length)
+    const y = new Reader<number, boolean>(n => n >= 2)
+    const z = reader.compose(
+      y,
+      x
+    )
+    assert.strictEqual(z.run('foo'), true)
+    assert.strictEqual(z.run('a'), false)
+  })
 })
diff --git a/test/Strong.ts b/test/Strong.ts
new file mode 100644
index 00000000..f5b0db5d
--- /dev/null
+++ b/test/Strong.ts
@@ -0,0 +1,19 @@
+import * as assert from 'assert'
+import { splitStrong, fanout } from '../src/Strong'
+import { Reader, reader } from '../src/Reader'
+
+describe('Strong', () => {
+  it('splitStrong', () => {
+    const ab = new Reader<string, number>(s => s.length)
+    const cd = new Reader<number, boolean>(n => n >= 2)
+    assert.deepEqual(splitStrong(reader)(ab, cd).run(['foo', 2]), [3, true])
+    assert.deepEqual(splitStrong(reader)(ab, cd).run(['a', 1]), [1, false])
+  })
+
+  it('fanout', () => {
+    const ab = new Reader<string, number>(s => s.length)
+    const ac = new Reader<string, boolean>(s => s === s.toLowerCase())
+    assert.deepEqual(fanout(reader)(ab, ac).run('foo'), [3, true])
+    assert.deepEqual(fanout(reader)(ab, ac).run('A'), [1, false])
+  })
+})
diff --git a/test/TaskEither.ts b/test/TaskEither.ts
index 057dbd83..d2ac3f80 100644
--- a/test/TaskEither.ts
+++ b/test/TaskEither.ts
@@ -441,4 +441,32 @@ describe('TaskEither', () => {
       assert.deepEqual(r2, 'right')
     })
   })
+
+  it('filterOrElse', () => {
+    const tasks = [
+      taskEither.of<string, number>(12).filterOrElse(n => n > 10, 'bar'),
+      taskEither.of<string, number>(7).filterOrElse(n => n > 10, 'bar'),
+      fromLeft<string, number>('foo').filterOrElse(n => n > 10, 'bar')
+    ]
+    return Promise.all(tasks.map(te => te.run())).then(([r1, r2, r3]) => {
+      assert.deepEqual(r1, eitherRight(12))
+      assert.deepEqual(r2, eitherLeft('bar'))
+      assert.deepEqual(r3, eitherLeft('foo'))
+    })
+  })
+
+  it('filterOrElseL', () => {
+    const tasks = [
+      taskEither.of<string, number>(12).filterOrElseL(n => n > 10, () => 'bar'),
+      taskEither.of<string, number>(7).filterOrElseL(n => n > 10, () => 'bar'),
+      fromLeft<string, number>('foo').filterOrElseL(n => n > 10, () => 'bar'),
+      taskEither.of<string, number>(7).filterOrElseL(n => n > 10, n => `invalid ${n}`)
+    ]
+    return Promise.all(tasks.map(te => te.run())).then(([r1, r2, r3, r4]) => {
+      assert.deepEqual(r1, eitherRight(12))
+      assert.deepEqual(r2, eitherLeft('bar'))
+      assert.deepEqual(r3, eitherLeft('foo'))
+      assert.deepEqual(r4, eitherLeft('invalid 7'))
+    })
+  })
 })
diff --git a/test/Trace.ts b/test/Trace.ts
index f644a509..3814df5e 100644
--- a/test/Trace.ts
+++ b/test/Trace.ts
@@ -4,48 +4,60 @@ import { some, option } from '../src/Option'
 
 describe('Trace', () => {
   it('trace', () => {
+    // tslint:disable-next-line:no-console
     const log_ = console.log
     const logger: Array<any> = []
+    // tslint:disable-next-line:no-console
     console.log = (a: any) => {
       logger.push(a)
     }
     trace('trace', () => 1)
     assert.deepEqual(logger, ['trace'])
+    // tslint:disable-next-line:no-console
     console.log = log_
   })
 
   it('spy', () => {
+    // tslint:disable-next-line:no-console
     const log_ = console.log
     const logger: Array<any> = []
+    // tslint:disable-next-line:no-console
     console.log = (a: any) => {
       logger.push(a)
     }
     spy(some(1))
     assert.deepEqual(logger, [some(1)])
+    // tslint:disable-next-line:no-console
     console.log = log_
   })
 
   it('traceA', () => {
+    // tslint:disable-next-line:no-console
     const log_ = console.log
     const logger: Array<any> = []
+    // tslint:disable-next-line:no-console
     console.log = (a: any) => {
       logger.push(a)
     }
     const x = traceA(option)('traceA')
     assert.deepEqual(logger, ['traceA'])
     assert.deepEqual(x, some(undefined))
+    // tslint:disable-next-line:no-console
     console.log = log_
   })
 
   it('traceM', () => {
+    // tslint:disable-next-line:no-console
     const log_ = console.log
     const logger: Array<any> = []
+    // tslint:disable-next-line:no-console
     console.log = (a: any) => {
       logger.push(a)
     }
     const x = traceM(option)('traceM')
     assert.deepEqual(logger, ['traceM'])
     assert.deepEqual(x, some('traceM'))
+    // tslint:disable-next-line:no-console
     console.log = log_
   })
 })