Repository: gcanti/fp-ts
Pull request #1064 (2019-12-23 17:18:38)

add TheseT, TaskThese modules


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 26cf0caf..daba6d20 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -18,6 +18,10 @@ high state of flux, you're at risk of it changing without notice.
 
 - **New Feature**
   - add `WriterT` module, closes #1050 (@gcanti)
+  - add `TheseT` module (@gcanti)
+  - add `TaskThese` module (@gcanti)
+  - `These`
+    - add `swap` (@gcanti)
   - `Ord`
     - add `getMonoid` (@vicrac)
   - `Ordering`
@@ -37,6 +41,8 @@ high state of flux, you're at risk of it changing without notice.
     - add `chainEither`, `chainIOEither`, `chainTaskEither` (@gcanti)
   - `StateReaderTaskEither`
     - add `chainEither`, `chainIOEither`, `chainTaskEither`, `chainReaderTaskEither` (@gcanti)
+  - `function`
+    - add `tupled`, `untupled` functions, closes #1062 (@gcanti)
 - **Deprecation**
   - `Ord`
     - deprecate `getSemigroup` in favor of `getMonoid` (@gcanti)
diff --git a/docs/modules/TaskThese.ts.md b/docs/modules/TaskThese.ts.md
new file mode 100644
index 00000000..2dfa3530
--- /dev/null
+++ b/docs/modules/TaskThese.ts.md
@@ -0,0 +1,240 @@
+---
+title: TaskThese.ts
+nav_order: 83
+parent: Modules
+---
+
+# TaskThese overview
+
+Added in v2.4.0
+
+---
+
+<h2 class="text-delta">Table of contents</h2>
+
+- [TaskThese (interface)](#taskthese-interface)
+- [URI (type alias)](#uri-type-alias)
+- [URI (constant)](#uri-constant)
+- [both (constant)](#both-constant)
+- [fromIOEither (constant)](#fromioeither-constant)
+- [left (constant)](#left-constant)
+- [leftTask (constant)](#lefttask-constant)
+- [right (constant)](#right-constant)
+- [rightTask (constant)](#righttask-constant)
+- [swap (constant)](#swap-constant)
+- [taskThese (constant)](#taskthese-constant)
+- [fold (function)](#fold-function)
+- [getMonad (function)](#getmonad-function)
+- [getSemigroup (function)](#getsemigroup-function)
+- [leftIO (function)](#leftio-function)
+- [rightIO (function)](#rightio-function)
+- [toTuple (function)](#totuple-function)
+- [bimap (export)](#bimap-export)
+- [map (export)](#map-export)
+- [mapLeft (export)](#mapleft-export)
+
+---
+
+# TaskThese (interface)
+
+**Signature**
+
+```ts
+export interface TaskThese<E, A> extends Task<These<E, A>> {}
+```
+
+Added in v2.4.0
+
+# URI (type alias)
+
+**Signature**
+
+```ts
+export type URI = typeof URI
+```
+
+Added in v2.4.0
+
+# URI (constant)
+
+**Signature**
+
+```ts
+export const URI: "TaskThese" = ...
+```
+
+Added in v2.4.0
+
+# both (constant)
+
+**Signature**
+
+```ts
+export const both: <E, A>(e: E, a: A) => TaskThese<E, A> = ...
+```
+
+Added in v2.4.0
+
+# fromIOEither (constant)
+
+**Signature**
+
+```ts
+export const fromIOEither: <E, A>(fa: IOEither<E, A>) => TaskThese<E, A> = ...
+```
+
+Added in v2.4.0
+
+# left (constant)
+
+**Signature**
+
+```ts
+export const left: <E = ...
+```
+
+Added in v2.4.0
+
+# leftTask (constant)
+
+**Signature**
+
+```ts
+export const leftTask: <E = ...
+```
+
+Added in v2.4.0
+
+# right (constant)
+
+**Signature**
+
+```ts
+export const right: <E = ...
+```
+
+Added in v2.4.0
+
+# rightTask (constant)
+
+**Signature**
+
+```ts
+export const rightTask: <E = ...
+```
+
+Added in v2.4.0
+
+# swap (constant)
+
+**Signature**
+
+```ts
+export const swap: <E, A>(fa: TaskThese<E, A>) => TaskThese<A, E> = ...
+```
+
+Added in v2.4.0
+
+# taskThese (constant)
+
+**Signature**
+
+```ts
+export const taskThese: Functor2<URI> & Bifunctor2<URI> = ...
+```
+
+Added in v2.4.0
+
+# fold (function)
+
+**Signature**
+
+```ts
+export function fold<E, A, B>(
+  onLeft: (e: E) => Task<B>,
+  onRight: (a: A) => Task<B>,
+  onBoth: (e: E, a: A) => Task<B>
+): (fa: TaskThese<E, A>) => Task<B> { ... }
+```
+
+Added in v2.4.0
+
+# getMonad (function)
+
+**Signature**
+
+```ts
+export function getMonad<E>(S: Semigroup<E>): Monad2C<URI, E> & MonadTask2C<URI, E> { ... }
+```
+
+Added in v2.4.0
+
+# getSemigroup (function)
+
+**Signature**
+
+```ts
+export function getSemigroup<E, A>(SE: Semigroup<E>, SA: Semigroup<A>): Semigroup<TaskThese<E, A>> { ... }
+```
+
+Added in v2.4.0
+
+# leftIO (function)
+
+**Signature**
+
+```ts
+export function leftIO<E = never, A = never>(me: IO<E>): TaskThese<E, A> { ... }
+```
+
+Added in v2.4.0
+
+# rightIO (function)
+
+**Signature**
+
+```ts
+export function rightIO<E = never, A = never>(ma: IO<A>): TaskThese<E, A> { ... }
+```
+
+Added in v2.4.0
+
+# toTuple (function)
+
+**Signature**
+
+```ts
+export function toTuple<E, A>(e: E, a: A): (fa: TaskThese<E, A>) => Task<[E, A]> { ... }
+```
+
+Added in v2.4.0
+
+# bimap (export)
+
+**Signature**
+
+```ts
+<E, G, A, B>(f: (e: E) => G, g: (a: A) => B) => (fa: TaskThese<E, A>) => TaskThese<G, B>
+```
+
+Added in v2.4.0
+
+# map (export)
+
+**Signature**
+
+```ts
+<A, B>(f: (a: A) => B) => <E>(fa: TaskThese<E, A>) => TaskThese<E, B>
+```
+
+Added in v2.4.0
+
+# mapLeft (export)
+
+**Signature**
+
+```ts
+<E, G>(f: (e: E) => G) => <A>(fa: TaskThese<E, A>) => TaskThese<G, A>
+```
+
+Added in v2.4.0
diff --git a/docs/modules/These.ts.md b/docs/modules/These.ts.md
index 37f61717..360d9411 100644
--- a/docs/modules/These.ts.md
+++ b/docs/modules/These.ts.md
@@ -1,6 +1,6 @@
 ---
 title: These.ts
-nav_order: 83
+nav_order: 84
 parent: Modules
 ---
 
@@ -34,6 +34,7 @@ Added in v2.0.0
 - [These (type alias)](#these-type-alias)
 - [URI (type alias)](#uri-type-alias)
 - [URI (constant)](#uri-constant)
+- [swap (constant)](#swap-constant)
 - [these (constant)](#these-constant)
 - [both (function)](#both-function)
 - [fold (function)](#fold-function)
@@ -107,6 +108,16 @@ export const URI: "These" = ...
 
 Added in v2.0.0
 
+# swap (constant)
+
+**Signature**
+
+```ts
+export const swap: <E, A>(fa: These<E, A>) => These<A, E> = ...
+```
+
+Added in v2.4.0
+
 # these (constant)
 
 **Signature**
@@ -177,7 +188,7 @@ Added in v2.0.0
 
 # getLeft (function)
 
-Returns an `L` value if possible
+Returns an `E` value if possible
 
 **Signature**
 
@@ -200,7 +211,7 @@ Added in v2.0.0
 
 # getLeftOnly (function)
 
-Returns the `L` value if and only if the value is constructed with `Left`
+Returns the `E` value if and only if the value is constructed with `Left`
 
 **Signature**
 
@@ -282,7 +293,7 @@ Added in v2.0.0
 **Signature**
 
 ```ts
-export function getSemigroup<E, A>(SL: Semigroup<E>, SA: Semigroup<A>): Semigroup<These<E, A>> { ... }
+export function getSemigroup<E, A>(SE: Semigroup<E>, SA: Semigroup<A>): Semigroup<These<E, A>> { ... }
 ```
 
 Added in v2.0.0
@@ -348,7 +359,7 @@ Added in v2.0.0
 **Signature**
 
 ```ts
-export function leftOrBoth<E>(defaultLeft: E): <A>(ma: Option<A>) => These<E, A> { ... }
+export function leftOrBoth<E>(e: E): <A>(ma: Option<A>) => These<E, A> { ... }
 ```
 
 **Example**
@@ -378,7 +389,7 @@ Added in v2.0.0
 **Signature**
 
 ```ts
-export function rightOrBoth<A>(defaultRight: A): <E>(me: Option<E>) => These<E, A> { ... }
+export function rightOrBoth<A>(a: A): <E>(me: Option<E>) => These<E, A> { ... }
 ```
 
 **Example**
diff --git a/docs/modules/TheseT.ts.md b/docs/modules/TheseT.ts.md
new file mode 100644
index 00000000..bda0c74b
--- /dev/null
+++ b/docs/modules/TheseT.ts.md
@@ -0,0 +1,173 @@
+---
+title: TheseT.ts
+nav_order: 85
+parent: Modules
+---
+
+# TheseT overview
+
+Added in v2.4.0
+
+---
+
+<h2 class="text-delta">Table of contents</h2>
+
+- [TheseM (interface)](#thesem-interface)
+- [TheseM1 (interface)](#thesem1-interface)
+- [TheseM2 (interface)](#thesem2-interface)
+- [TheseT (interface)](#theset-interface)
+- [TheseT1 (type alias)](#theset1-type-alias)
+- [TheseT2 (type alias)](#theset2-type-alias)
+- [getTheseM (function)](#getthesem-function)
+
+---
+
+# TheseM (interface)
+
+**Signature**
+
+```ts
+export interface TheseM<M> {
+  readonly map: <E, A, B>(fa: TheseT<M, E, A>, f: (a: A) => B) => TheseT<M, E, B>
+  readonly bimap: <E, A, N, B>(fa: TheseT<M, E, A>, f: (e: E) => N, g: (a: A) => B) => TheseT<M, N, B>
+  readonly mapLeft: <E, A, N>(fa: TheseT<M, E, A>, f: (e: E) => N) => TheseT<M, N, A>
+  readonly fold: <E, A, R>(
+    fa: TheseT<M, E, A>,
+    onLeft: (e: E) => HKT<M, R>,
+    onRight: (a: A) => HKT<M, R>,
+    onBoth: (e: E, a: A) => HKT<M, R>
+  ) => HKT<M, R>
+  readonly swap: <E, A>(fa: TheseT<M, E, A>) => TheseT<M, A, E>
+  readonly rightM: <E, A>(ma: HKT<M, A>) => TheseT<M, E, A>
+  readonly leftM: <E, A>(me: HKT<M, E>) => TheseT<M, E, A>
+  readonly left: <E, A>(e: E) => TheseT<M, E, A>
+  readonly right: <E, A>(a: A) => TheseT<M, E, A>
+  readonly both: <E, A>(e: E, a: A) => TheseT<M, E, A>
+  readonly toTuple: <E, A>(fa: TheseT<M, E, A>, e: E, a: A) => HKT<M, [E, A]>
+  readonly getMonad: <E>(
+    S: Semigroup<E>
+  ) => {
+    readonly _E: E
+    readonly map: <A, B>(ma: TheseT<M, E, A>, f: (a: A) => B) => TheseT<M, E, B>
+    readonly of: <A>(a: A) => TheseT<M, E, A>
+    readonly ap: <A, B>(mab: TheseT<M, E, (a: A) => B>, ma: TheseT<M, E, A>) => TheseT<M, E, B>
+    readonly chain: <A, B>(ma: TheseT<M, E, A>, f: (a: A) => TheseT<M, E, B>) => TheseT<M, E, B>
+  }
+}
+```
+
+Added in v2.4.0
+
+# TheseM1 (interface)
+
+**Signature**
+
+```ts
+export interface TheseM1<M extends URIS> {
+  readonly map: <E, A, B>(fa: TheseT1<M, E, A>, f: (a: A) => B) => TheseT1<M, E, B>
+  readonly bimap: <E, A, N, B>(fa: TheseT1<M, E, A>, f: (e: E) => N, g: (a: A) => B) => TheseT1<M, N, B>
+  readonly mapLeft: <E, A, N>(fa: TheseT1<M, E, A>, f: (e: E) => N) => TheseT1<M, N, A>
+  readonly fold: <E, A, R>(
+    fa: TheseT1<M, E, A>,
+    onLeft: (e: E) => Kind<M, R>,
+    onRight: (a: A) => Kind<M, R>,
+    onBoth: (e: E, a: A) => Kind<M, R>
+  ) => Kind<M, R>
+  readonly swap: <E, A>(fa: TheseT1<M, E, A>) => TheseT1<M, A, E>
+  readonly rightM: <E, A>(ma: Kind<M, A>) => TheseT1<M, E, A>
+  readonly leftM: <E, A>(me: Kind<M, E>) => TheseT1<M, E, A>
+  readonly left: <E, A>(e: E) => TheseT1<M, E, A>
+  readonly right: <E, A>(a: A) => TheseT1<M, E, A>
+  readonly both: <E, A>(e: E, a: A) => TheseT1<M, E, A>
+  readonly toTuple: <E, A>(fa: TheseT1<M, E, A>, e: E, a: A) => Kind<M, [E, A]>
+  readonly getMonad: <E>(
+    S: Semigroup<E>
+  ) => {
+    readonly _E: E
+    readonly map: <A, B>(ma: TheseT1<M, E, A>, f: (a: A) => B) => TheseT1<M, E, B>
+    readonly of: <A>(a: A) => TheseT1<M, E, A>
+    readonly ap: <A, B>(mab: TheseT1<M, E, (a: A) => B>, ma: TheseT1<M, E, A>) => TheseT1<M, E, B>
+    readonly chain: <A, B>(ma: TheseT1<M, E, A>, f: (a: A) => TheseT1<M, E, B>) => TheseT1<M, E, B>
+  }
+}
+```
+
+Added in v2.4.0
+
+# TheseM2 (interface)
+
+**Signature**
+
+```ts
+export interface TheseM2<M extends URIS2> {
+  readonly map: <R, E, A, B>(fa: TheseT2<M, R, E, A>, f: (a: A) => B) => TheseT2<M, R, E, B>
+  readonly bimap: <R, E, A, N, B>(fa: TheseT2<M, R, E, A>, f: (e: E) => N, g: (a: A) => B) => TheseT2<M, R, N, B>
+  readonly mapLeft: <R, E, A, N>(fa: TheseT2<M, R, E, A>, f: (e: E) => N) => TheseT2<M, R, N, A>
+  readonly fold: <R, E, A, B>(
+    fa: TheseT2<M, R, E, A>,
+    onLeft: (e: E) => Kind2<M, R, B>,
+    onRight: (a: A) => Kind2<M, R, B>,
+    onBoth: (e: E, a: A) => Kind2<M, R, B>
+  ) => Kind2<M, R, B>
+  readonly swap: <R, E, A>(fa: TheseT2<M, R, E, A>) => TheseT2<M, R, A, E>
+  readonly rightM: <R, E, A>(ma: Kind2<M, R, A>) => TheseT2<M, R, E, A>
+  readonly leftM: <R, E, A>(me: Kind2<M, R, E>) => TheseT2<M, R, E, A>
+  readonly left: <R, E, A>(e: E) => TheseT2<M, R, E, A>
+  readonly right: <R, E, A>(a: A) => TheseT2<M, R, E, A>
+  readonly both: <R, E, A>(e: E, a: A) => TheseT2<M, R, E, A>
+  readonly toTuple: <R, E, A>(fa: TheseT2<M, R, E, A>, e: E, a: A) => Kind2<M, R, [E, A]>
+  readonly getMonad: <E>(
+    S: Semigroup<E>
+  ) => {
+    readonly _E: E
+    readonly map: <R, A, B>(ma: TheseT2<M, R, E, A>, f: (a: A) => B) => TheseT2<M, R, E, B>
+    readonly of: <R, A>(a: A) => TheseT2<M, R, E, A>
+    readonly ap: <R, A, B>(mab: TheseT2<M, R, E, (a: A) => B>, ma: TheseT2<M, R, E, A>) => TheseT2<M, R, E, B>
+    readonly chain: <R, A, B>(ma: TheseT2<M, R, E, A>, f: (a: A) => TheseT2<M, R, E, B>) => TheseT2<M, R, E, B>
+  }
+}
+```
+
+Added in v2.4.0
+
+# TheseT (interface)
+
+**Signature**
+
+```ts
+export interface TheseT<M, E, A> extends HKT<M, These<E, A>> {}
+```
+
+Added in v2.4.0
+
+# TheseT1 (type alias)
+
+**Signature**
+
+```ts
+export type TheseT1<M extends URIS, E, A> = Kind<M, These<E, A>>
+```
+
+Added in v2.4.0
+
+# TheseT2 (type alias)
+
+**Signature**
+
+```ts
+export type TheseT2<M extends URIS2, R, E, A> = Kind2<M, R, These<E, A>>
+```
+
+Added in v2.4.0
+
+# getTheseM (function)
+
+**Signature**
+
+```ts
+export function getTheseM<M extends URIS2>(M: Monad2<M>): TheseM2<M>
+export function getTheseM<M extends URIS>(M: Monad1<M>): TheseM1<M>
+export function getTheseM<M>(M: Monad<M>): TheseM<M> { ... }
+```
+
+Added in v2.4.0
diff --git a/docs/modules/Traced.ts.md b/docs/modules/Traced.ts.md
index b0dd441e..9601d05b 100644
--- a/docs/modules/Traced.ts.md
+++ b/docs/modules/Traced.ts.md
@@ -1,6 +1,6 @@
 ---
 title: Traced.ts
-nav_order: 84
+nav_order: 86
 parent: Modules
 ---
 
diff --git a/docs/modules/Traversable.ts.md b/docs/modules/Traversable.ts.md
index d9eb82ca..8f3986ff 100644
--- a/docs/modules/Traversable.ts.md
+++ b/docs/modules/Traversable.ts.md
@@ -1,6 +1,6 @@
 ---
 title: Traversable.ts
-nav_order: 85
+nav_order: 87
 parent: Modules
 ---
 
diff --git a/docs/modules/TraversableWithIndex.ts.md b/docs/modules/TraversableWithIndex.ts.md
index e1e052dc..fc1670bc 100644
--- a/docs/modules/TraversableWithIndex.ts.md
+++ b/docs/modules/TraversableWithIndex.ts.md
@@ -1,6 +1,6 @@
 ---
 title: TraversableWithIndex.ts
-nav_order: 86
+nav_order: 88
 parent: Modules
 ---
 
diff --git a/docs/modules/Tree.ts.md b/docs/modules/Tree.ts.md
index afcf8e17..ed96da55 100644
--- a/docs/modules/Tree.ts.md
+++ b/docs/modules/Tree.ts.md
@@ -1,6 +1,6 @@
 ---
 title: Tree.ts
-nav_order: 87
+nav_order: 89
 parent: Modules
 ---
 
diff --git a/docs/modules/Tuple.ts.md b/docs/modules/Tuple.ts.md
index 5998b656..2e3af69b 100644
--- a/docs/modules/Tuple.ts.md
+++ b/docs/modules/Tuple.ts.md
@@ -1,6 +1,6 @@
 ---
 title: Tuple.ts
-nav_order: 88
+nav_order: 90
 parent: Modules
 ---
 
diff --git a/docs/modules/Unfoldable.ts.md b/docs/modules/Unfoldable.ts.md
index eccb28bd..f8558543 100644
--- a/docs/modules/Unfoldable.ts.md
+++ b/docs/modules/Unfoldable.ts.md
@@ -1,6 +1,6 @@
 ---
 title: Unfoldable.ts
-nav_order: 89
+nav_order: 91
 parent: Modules
 ---
 
diff --git a/docs/modules/ValidationT.ts.md b/docs/modules/ValidationT.ts.md
index 18d6566f..655c0991 100644
--- a/docs/modules/ValidationT.ts.md
+++ b/docs/modules/ValidationT.ts.md
@@ -1,6 +1,6 @@
 ---
 title: ValidationT.ts
-nav_order: 90
+nav_order: 92
 parent: Modules
 ---
 
diff --git a/docs/modules/Witherable.ts.md b/docs/modules/Witherable.ts.md
index 232cc28b..b925040c 100644
--- a/docs/modules/Witherable.ts.md
+++ b/docs/modules/Witherable.ts.md
@@ -1,6 +1,6 @@
 ---
 title: Witherable.ts
-nav_order: 91
+nav_order: 93
 parent: Modules
 ---
 
diff --git a/docs/modules/Writer.ts.md b/docs/modules/Writer.ts.md
index 5e9d102f..b48f449e 100644
--- a/docs/modules/Writer.ts.md
+++ b/docs/modules/Writer.ts.md
@@ -1,6 +1,6 @@
 ---
 title: Writer.ts
-nav_order: 92
+nav_order: 94
 parent: Modules
 ---
 
diff --git a/docs/modules/WriterT.ts.md b/docs/modules/WriterT.ts.md
index c51f86b2..879d3425 100644
--- a/docs/modules/WriterT.ts.md
+++ b/docs/modules/WriterT.ts.md
@@ -1,6 +1,6 @@
 ---
 title: WriterT.ts
-nav_order: 93
+nav_order: 95
 parent: Modules
 ---
 
diff --git a/docs/modules/function.ts.md b/docs/modules/function.ts.md
index 290c6d3b..fd953571 100644
--- a/docs/modules/function.ts.md
+++ b/docs/modules/function.ts.md
@@ -32,6 +32,8 @@ Added in v2.0.0
 - [increment (function)](#increment-function)
 - [not (function)](#not-function)
 - [tuple (function)](#tuple-function)
+- [tupled (function)](#tupled-function)
+- [untupled (function)](#untupled-function)
 
 ---
 
@@ -338,3 +340,37 @@ export function tuple<T extends Array<any>>(...t: T): T { ... }
 ```
 
 Added in v2.0.0
+
+# tupled (function)
+
+Creates a tupled version of this function: instead of `n` arguments, it accepts a single tuple argument.
+
+**Signature**
+
+```ts
+export function tupled<A extends Array<unknown>, B>(f: (...a: A) => B): (a: A) => B { ... }
+```
+
+**Example**
+
+```ts
+import { tupled } from 'fp-ts/lib/function'
+
+const add = tupled((x: number, y: number): number => x + y)
+
+assert.strictEqual(add([1, 2]), 3)
+```
+
+Added in v2.4.0
+
+# untupled (function)
+
+Inverse function of `tupled`
+
+**Signature**
+
+```ts
+export function untupled<A extends Array<unknown>, B>(f: (a: A) => B): (...a: A) => B { ... }
+```
+
+Added in v2.4.0
diff --git a/docs/modules/index.ts.md b/docs/modules/index.ts.md
index 20e08a34..5a061f5e 100644
--- a/docs/modules/index.ts.md
+++ b/docs/modules/index.ts.md
@@ -93,7 +93,9 @@ Added in v2.0.0
 - [strong (export)](#strong-export)
 - [task (export)](#task-export)
 - [taskEither (export)](#taskeither-export)
+- [taskThese (export)](#taskthese-export)
 - [these (export)](#these-export)
+- [theseT (export)](#theset-export)
 - [traced (export)](#traced-export)
 - [traversable (export)](#traversable-export)
 - [traversableWithIndex (export)](#traversablewithindex-export)
@@ -917,6 +919,16 @@ typeof taskEither
 
 Added in v2.0.0
 
+# taskThese (export)
+
+**Signature**
+
+```ts
+typeof taskThese
+```
+
+Added in v2.4.0
+
 # these (export)
 
 **Signature**
@@ -927,6 +939,16 @@ typeof these
 
 Added in v2.0.0
 
+# theseT (export)
+
+**Signature**
+
+```ts
+typeof theseT
+```
+
+Added in v2.4.0
+
 # traced (export)
 
 **Signature**
diff --git a/dtslint/ts3.5/index.ts b/dtslint/ts3.5/index.ts
index 2f15288d..b00a155e 100644
--- a/dtslint/ts3.5/index.ts
+++ b/dtslint/ts3.5/index.ts
@@ -177,20 +177,14 @@ interface fromNullableTest2 {
 }
 declare const fromNullableTest3: <Key extends keyof fromNullableTest2>(key: Key) => fromNullableTest2[Key]
 // $ExpectType Option<number>
-Fu.flow(
-  fromNullableTest3,
-  O.fromNullable
-)('foo')
+Fu.flow(fromNullableTest3, O.fromNullable)('foo')
 
 //
 // Either
 //
 
 // $ExpectType Either<string, number>
-Fu.flow(
-  fromNullableTest3,
-  E.fromNullable('error')
-)('foo')
+Fu.flow(fromNullableTest3, E.fromNullable('error'))('foo')
 
 //
 // HKT
@@ -375,10 +369,13 @@ Fu.tuple(1, 'a') // $ExpectType [number, string]
 Fu.tuple(1, 'a', true) // $ExpectType [number, string, boolean]
 
 // $ExpectType <A>(head: A, tail: A[]) => Option<A>
-Fu.flow(
-  A.cons,
-  A.head
-)
+Fu.flow(A.cons, A.head)
+
+// tupled
+Fu.tupled(A.insertAt) // $ExpectType <A>(a: [number, A]) => (as: A[]) => Option<A[]>
+
+// untupled
+Fu.untupled(Fu.tupled(A.insertAt)) // $ExpectType <A>(i: number, a: A) => (as: A[]) => Option<A[]>
 
 //
 // Filterable overloads
diff --git a/src/TaskThese.ts b/src/TaskThese.ts
new file mode 100644
index 00000000..ea5564e8
--- /dev/null
+++ b/src/TaskThese.ts
@@ -0,0 +1,152 @@
+/**
+ * @since 2.4.0
+ */
+import { Bifunctor2 } from './Bifunctor'
+import { Functor2 } from './Functor'
+import { IO } from './IO'
+import { IOEither } from './IOEither'
+import { Monad2C } from './Monad'
+import { MonadTask2C } from './MonadTask'
+import { pipeable } from './pipeable'
+import { Semigroup } from './Semigroup'
+import { getSemigroup as getTaskSemigroup, Task, task } from './Task'
+import * as TH from './These'
+import { getTheseM } from './TheseT'
+
+import These = TH.These
+
+const T = getTheseM(task)
+
+declare module './HKT' {
+  interface URItoKind2<E, A> {
+    TaskThese: TaskThese<E, A>
+  }
+}
+
+/**
+ * @since 2.4.0
+ */
+export const URI = 'TaskThese'
+
+/**
+ * @since 2.4.0
+ */
+export type URI = typeof URI
+
+/**
+ * @since 2.4.0
+ */
+export interface TaskThese<E, A> extends Task<These<E, A>> {}
+
+/**
+ * @since 2.4.0
+ */
+export const left: <E = never, A = never>(e: E) => TaskThese<E, A> = T.left
+
+/**
+ * @since 2.4.0
+ */
+export const right: <E = never, A = never>(a: A) => TaskThese<E, A> = T.right
+
+/**
+ * @since 2.4.0
+ */
+export const both: <E, A>(e: E, a: A) => TaskThese<E, A> = T.both
+
+/**
+ * @since 2.4.0
+ */
+export function rightIO<E = never, A = never>(ma: IO<A>): TaskThese<E, A> {
+  return rightTask(task.fromIO(ma))
+}
+
+/**
+ * @since 2.4.0
+ */
+export function leftIO<E = never, A = never>(me: IO<E>): TaskThese<E, A> {
+  return leftTask(task.fromIO(me))
+}
+
+/**
+ * @since 2.4.0
+ */
+export const leftTask: <E = never, A = never>(me: Task<E>) => TaskThese<E, A> = T.leftM
+
+/**
+ * @since 2.4.0
+ */
+export const rightTask: <E = never, A = never>(ma: Task<A>) => TaskThese<E, A> = T.rightM
+
+/**
+ * @since 2.4.0
+ */
+export const fromIOEither: <E, A>(fa: IOEither<E, A>) => TaskThese<E, A> = task.fromIO
+
+/**
+ * @since 2.4.0
+ */
+export function fold<E, A, B>(
+  onLeft: (e: E) => Task<B>,
+  onRight: (a: A) => Task<B>,
+  onBoth: (e: E, a: A) => Task<B>
+): (fa: TaskThese<E, A>) => Task<B> {
+  return fa => T.fold(fa, onLeft, onRight, onBoth)
+}
+
+/**
+ * @since 2.4.0
+ */
+export const swap: <E, A>(fa: TaskThese<E, A>) => TaskThese<A, E> = T.swap
+
+/**
+ * @since 2.4.0
+ */
+export function getSemigroup<E, A>(SE: Semigroup<E>, SA: Semigroup<A>): Semigroup<TaskThese<E, A>> {
+  return getTaskSemigroup(TH.getSemigroup<E, A>(SE, SA))
+}
+
+/**
+ * @since 2.4.0
+ */
+export function getMonad<E>(S: Semigroup<E>): Monad2C<URI, E> & MonadTask2C<URI, E> {
+  return {
+    URI,
+    ...T.getMonad(S),
+    fromIO: rightIO,
+    fromTask: rightTask
+  }
+}
+
+/**
+ * @since 2.4.0
+ */
+export function toTuple<E, A>(e: E, a: A): (fa: TaskThese<E, A>) => Task<[E, A]> {
+  return fa => T.toTuple(fa, e, a)
+}
+
+/**
+ * @since 2.4.0
+ */
+export const taskThese: Functor2<URI> & Bifunctor2<URI> = {
+  URI,
+  map: T.map,
+  bimap: T.bimap,
+  mapLeft: T.mapLeft
+}
+
+const { bimap, map, mapLeft } = pipeable(taskThese)
+
+export {
+  /**
+   * @since 2.4.0
+   */
+  bimap,
+  /**
+   * @since 2.4.0
+   */
+  map,
+  /**
+   * @since 2.4.0
+   */
+  mapLeft
+}
diff --git a/src/These.ts b/src/These.ts
index 2a46a335..9f7afb38 100644
--- a/src/These.ts
+++ b/src/These.ts
@@ -28,10 +28,10 @@ import { Functor2 } from './Functor'
 import { HKT } from './HKT'
 import { Monad2C } from './Monad'
 import { isNone, none, Option, some } from './Option'
+import { pipeable } from './pipeable'
 import { Semigroup } from './Semigroup'
 import { Show } from './Show'
 import { Traversable2 } from './Traversable'
-import { pipeable } from './pipeable'
 
 declare module './HKT' {
   interface URItoKind2<E, A> {
@@ -104,6 +104,11 @@ export function fold<E, A, B>(
   }
 }
 
+/**
+ * @since 2.4.0
+ */
+export const swap: <E, A>(fa: These<E, A>) => These<A, E> = fold(right, left, (e, a) => both(a, e))
+
 /**
  * @since 2.0.0
  */
@@ -133,15 +138,15 @@ export function getEq<E, A>(EE: Eq<E>, EA: Eq<A>): Eq<These<E, A>> {
 /**
  * @since 2.0.0
  */
-export function getSemigroup<E, A>(SL: Semigroup<E>, SA: Semigroup<A>): Semigroup<These<E, A>> {
+export function getSemigroup<E, A>(SE: Semigroup<E>, SA: Semigroup<A>): Semigroup<These<E, A>> {
   return {
     concat: (x, y) =>
       isLeft(x)
         ? isLeft(y)
-          ? left(SL.concat(x.left, y.left))
+          ? left(SE.concat(x.left, y.left))
           : isRight(y)
           ? both(x.left, y.right)
-          : both(SL.concat(x.left, y.left), y.right)
+          : both(SE.concat(x.left, y.left), y.right)
         : isRight(x)
         ? isLeft(y)
           ? both(y.left, x.right)
@@ -149,10 +154,10 @@ export function getSemigroup<E, A>(SL: Semigroup<E>, SA: Semigroup<A>): Semigrou
           ? right(SA.concat(x.right, y.right))
           : both(y.left, SA.concat(x.right, y.right))
         : isLeft(y)
-        ? both(SL.concat(x.left, y.left), x.right)
+        ? both(SE.concat(x.left, y.left), x.right)
         : isRight(y)
         ? both(x.left, SA.concat(x.right, y.right))
-        : both(SL.concat(x.left, y.left), SA.concat(x.right, y.right))
+        : both(SE.concat(x.left, y.left), SA.concat(x.right, y.right))
   }
 }
 
@@ -186,7 +191,6 @@ export function getMonad<E>(S: Semigroup<E>): Monad2C<URI, E> {
 }
 
 /**
- *
  * @example
  * import { toTuple, left, right, both } from 'fp-ts/lib/These'
  *
@@ -201,7 +205,7 @@ export function toTuple<E, A>(e: E, a: A): (fa: These<E, A>) => [E, A] {
 }
 
 /**
- * Returns an `L` value if possible
+ * Returns an `E` value if possible
  *
  * @example
  * import { getLeft, left, right, both } from 'fp-ts/lib/These'
@@ -271,8 +275,8 @@ export function isBoth<E, A>(fa: These<E, A>): fa is Both<E, A> {
  *
  * @since 2.0.0
  */
-export function leftOrBoth<E>(defaultLeft: E): <A>(ma: Option<A>) => These<E, A> {
-  return ma => (isNone(ma) ? left(defaultLeft) : both(defaultLeft, ma.value))
+export function leftOrBoth<E>(e: E): <A>(ma: Option<A>) => These<E, A> {
+  return ma => (isNone(ma) ? left(e) : both(e, ma.value))
 }
 
 /**
@@ -285,12 +289,12 @@ export function leftOrBoth<E>(defaultLeft: E): <A>(ma: Option<A>) => These<E, A>
  *
  * @since 2.0.0
  */
-export function rightOrBoth<A>(defaultRight: A): <E>(me: Option<E>) => These<E, A> {
-  return me => (isNone(me) ? right(defaultRight) : both(me.value, defaultRight))
+export function rightOrBoth<A>(a: A): <E>(me: Option<E>) => These<E, A> {
+  return me => (isNone(me) ? right(a) : both(me.value, a))
 }
 
 /**
- * Returns the `L` value if and only if the value is constructed with `Left`
+ * Returns the `E` value if and only if the value is constructed with `Left`
  *
  * @example
  * import { getLeftOnly, left, right, both } from 'fp-ts/lib/These'
@@ -348,8 +352,6 @@ export function fromOptions<E, A>(fe: Option<E>, fa: Option<A>): Option<These<E,
     : some(both(fe.value, fa.value))
 }
 
-const identity = <A>(a: A): A => a
-
 /**
  * @since 2.0.0
  */
@@ -358,7 +360,7 @@ export const these: Functor2<URI> & Bifunctor2<URI> & Foldable2<URI> & Traversab
   map: (fa, f) => (isLeft(fa) ? fa : isRight(fa) ? right(f(fa.right)) : both(fa.left, f(fa.right))),
   bimap: (fea, f, g) =>
     isLeft(fea) ? left(f(fea.left)) : isRight(fea) ? right(g(fea.right)) : both(f(fea.left), g(fea.right)),
-  mapLeft: (fea, f) => these.bimap(fea, f, identity),
+  mapLeft: (fea, f) => (isLeft(fea) ? left(f(fea.left)) : isBoth(fea) ? both(f(fea.left), fea.right) : fea),
   reduce: (fa, b, f) => (isLeft(fa) ? b : isRight(fa) ? f(b, fa.right) : f(b, fa.right)),
   foldMap: M => (fa, f) => (isLeft(fa) ? M.empty : isRight(fa) ? f(fa.right) : f(fa.right)),
   reduceRight: (fa, b, f) => (isLeft(fa) ? b : isRight(fa) ? f(fa.right, b) : f(fa.right, b)),
diff --git a/src/TheseT.ts b/src/TheseT.ts
new file mode 100644
index 00000000..a57eb420
--- /dev/null
+++ b/src/TheseT.ts
@@ -0,0 +1,175 @@
+/**
+ * @since 2.4.0
+ */
+import { HKT, Kind, Kind2, URIS, URIS2 } from './HKT'
+import { Monad, Monad1, Monad2 } from './Monad'
+import { Semigroup } from './Semigroup'
+import { bimap, both, fold, left, map, mapLeft, right, swap, These, toTuple } from './These'
+
+/**
+ * @since 2.4.0
+ */
+export interface TheseT<M, E, A> extends HKT<M, These<E, A>> {}
+
+/**
+ * @since 2.4.0
+ */
+export interface TheseM<M> {
+  readonly map: <E, A, B>(fa: TheseT<M, E, A>, f: (a: A) => B) => TheseT<M, E, B>
+  readonly bimap: <E, A, N, B>(fa: TheseT<M, E, A>, f: (e: E) => N, g: (a: A) => B) => TheseT<M, N, B>
+  readonly mapLeft: <E, A, N>(fa: TheseT<M, E, A>, f: (e: E) => N) => TheseT<M, N, A>
+  readonly fold: <E, A, R>(
+    fa: TheseT<M, E, A>,
+    onLeft: (e: E) => HKT<M, R>,
+    onRight: (a: A) => HKT<M, R>,
+    onBoth: (e: E, a: A) => HKT<M, R>
+  ) => HKT<M, R>
+  readonly swap: <E, A>(fa: TheseT<M, E, A>) => TheseT<M, A, E>
+  readonly rightM: <E, A>(ma: HKT<M, A>) => TheseT<M, E, A>
+  readonly leftM: <E, A>(me: HKT<M, E>) => TheseT<M, E, A>
+  readonly left: <E, A>(e: E) => TheseT<M, E, A>
+  readonly right: <E, A>(a: A) => TheseT<M, E, A>
+  readonly both: <E, A>(e: E, a: A) => TheseT<M, E, A>
+  readonly toTuple: <E, A>(fa: TheseT<M, E, A>, e: E, a: A) => HKT<M, [E, A]>
+  readonly getMonad: <E>(
+    S: Semigroup<E>
+  ) => {
+    readonly _E: E
+    readonly map: <A, B>(ma: TheseT<M, E, A>, f: (a: A) => B) => TheseT<M, E, B>
+    readonly of: <A>(a: A) => TheseT<M, E, A>
+    readonly ap: <A, B>(mab: TheseT<M, E, (a: A) => B>, ma: TheseT<M, E, A>) => TheseT<M, E, B>
+    readonly chain: <A, B>(ma: TheseT<M, E, A>, f: (a: A) => TheseT<M, E, B>) => TheseT<M, E, B>
+  }
+}
+
+/**
+ * @since 2.4.0
+ */
+export type TheseT1<M extends URIS, E, A> = Kind<M, These<E, A>>
+
+/**
+ * @since 2.4.0
+ */
+export interface TheseM1<M extends URIS> {
+  readonly map: <E, A, B>(fa: TheseT1<M, E, A>, f: (a: A) => B) => TheseT1<M, E, B>
+  readonly bimap: <E, A, N, B>(fa: TheseT1<M, E, A>, f: (e: E) => N, g: (a: A) => B) => TheseT1<M, N, B>
+  readonly mapLeft: <E, A, N>(fa: TheseT1<M, E, A>, f: (e: E) => N) => TheseT1<M, N, A>
+  readonly fold: <E, A, R>(
+    fa: TheseT1<M, E, A>,
+    onLeft: (e: E) => Kind<M, R>,
+    onRight: (a: A) => Kind<M, R>,
+    onBoth: (e: E, a: A) => Kind<M, R>
+  ) => Kind<M, R>
+  readonly swap: <E, A>(fa: TheseT1<M, E, A>) => TheseT1<M, A, E>
+  readonly rightM: <E, A>(ma: Kind<M, A>) => TheseT1<M, E, A>
+  readonly leftM: <E, A>(me: Kind<M, E>) => TheseT1<M, E, A>
+  readonly left: <E, A>(e: E) => TheseT1<M, E, A>
+  readonly right: <E, A>(a: A) => TheseT1<M, E, A>
+  readonly both: <E, A>(e: E, a: A) => TheseT1<M, E, A>
+  readonly toTuple: <E, A>(fa: TheseT1<M, E, A>, e: E, a: A) => Kind<M, [E, A]>
+  readonly getMonad: <E>(
+    S: Semigroup<E>
+  ) => {
+    readonly _E: E
+    readonly map: <A, B>(ma: TheseT1<M, E, A>, f: (a: A) => B) => TheseT1<M, E, B>
+    readonly of: <A>(a: A) => TheseT1<M, E, A>
+    readonly ap: <A, B>(mab: TheseT1<M, E, (a: A) => B>, ma: TheseT1<M, E, A>) => TheseT1<M, E, B>
+    readonly chain: <A, B>(ma: TheseT1<M, E, A>, f: (a: A) => TheseT1<M, E, B>) => TheseT1<M, E, B>
+  }
+}
+
+/**
+ * @since 2.4.0
+ */
+export type TheseT2<M extends URIS2, R, E, A> = Kind2<M, R, These<E, A>>
+
+/**
+ * @since 2.4.0
+ */
+export interface TheseM2<M extends URIS2> {
+  readonly map: <R, E, A, B>(fa: TheseT2<M, R, E, A>, f: (a: A) => B) => TheseT2<M, R, E, B>
+  readonly bimap: <R, E, A, N, B>(fa: TheseT2<M, R, E, A>, f: (e: E) => N, g: (a: A) => B) => TheseT2<M, R, N, B>
+  readonly mapLeft: <R, E, A, N>(fa: TheseT2<M, R, E, A>, f: (e: E) => N) => TheseT2<M, R, N, A>
+  readonly fold: <R, E, A, B>(
+    fa: TheseT2<M, R, E, A>,
+    onLeft: (e: E) => Kind2<M, R, B>,
+    onRight: (a: A) => Kind2<M, R, B>,
+    onBoth: (e: E, a: A) => Kind2<M, R, B>
+  ) => Kind2<M, R, B>
+  readonly swap: <R, E, A>(fa: TheseT2<M, R, E, A>) => TheseT2<M, R, A, E>
+  readonly rightM: <R, E, A>(ma: Kind2<M, R, A>) => TheseT2<M, R, E, A>
+  readonly leftM: <R, E, A>(me: Kind2<M, R, E>) => TheseT2<M, R, E, A>
+  readonly left: <R, E, A>(e: E) => TheseT2<M, R, E, A>
+  readonly right: <R, E, A>(a: A) => TheseT2<M, R, E, A>
+  readonly both: <R, E, A>(e: E, a: A) => TheseT2<M, R, E, A>
+  readonly toTuple: <R, E, A>(fa: TheseT2<M, R, E, A>, e: E, a: A) => Kind2<M, R, [E, A]>
+  readonly getMonad: <E>(
+    S: Semigroup<E>
+  ) => {
+    readonly _E: E
+    readonly map: <R, A, B>(ma: TheseT2<M, R, E, A>, f: (a: A) => B) => TheseT2<M, R, E, B>
+    readonly of: <R, A>(a: A) => TheseT2<M, R, E, A>
+    readonly ap: <R, A, B>(mab: TheseT2<M, R, E, (a: A) => B>, ma: TheseT2<M, R, E, A>) => TheseT2<M, R, E, B>
+    readonly chain: <R, A, B>(ma: TheseT2<M, R, E, A>, f: (a: A) => TheseT2<M, R, E, B>) => TheseT2<M, R, E, B>
+  }
+}
+
+/**
+ * @since 2.4.0
+ */
+export function getTheseM<M extends URIS2>(M: Monad2<M>): TheseM2<M>
+export function getTheseM<M extends URIS>(M: Monad1<M>): TheseM1<M>
+export function getTheseM<M>(M: Monad<M>): TheseM<M>
+export function getTheseM<M>(M: Monad<M>): TheseM<M> {
+  function mapT<E, A, B>(fa: TheseT<M, E, A>, f: (a: A) => B): TheseT<M, E, B> {
+    return M.map(fa, map(f))
+  }
+
+  function of<E, A>(a: A): TheseT<M, E, A> {
+    return M.of(right(a))
+  }
+
+  function leftT<E = never, A = never>(e: E): TheseT<M, E, A> {
+    return M.of(left(e))
+  }
+
+  return {
+    map: mapT,
+    bimap: (fa, f, g) => M.map(fa, bimap(f, g)),
+    mapLeft: (fa, f) => M.map(fa, mapLeft(f)),
+    fold: (fa, onLeft, onRight, onBoth) => M.chain(fa, fold(onLeft, onRight, onBoth)),
+    swap: fa => M.map(fa, swap),
+    rightM: ma => M.map(ma, right),
+    leftM: me => M.map(me, left),
+    left: leftT,
+    right: of,
+    both: (e, a) => M.of(both(e, a)),
+    toTuple: (fa, e, a) => M.map(fa, toTuple(e, a)),
+    getMonad: <E>(E: Semigroup<E>) => {
+      function chain<A, B>(fa: TheseT<M, E, A>, f: (a: A) => TheseT<M, E, B>): TheseT<M, E, B> {
+        return M.chain(
+          fa,
+          fold(leftT, f, (e1, a) =>
+            M.map(
+              f(a),
+              fold(
+                e2 => left(E.concat(e1, e2)),
+                right,
+                (e2, b) => both(E.concat(e1, e2), b)
+              )
+            )
+          )
+        )
+      }
+
+      return {
+        _E: undefined as any,
+        map: mapT,
+        of,
+        ap: <A, B>(mab: TheseT<M, E, (a: A) => B>, ma: TheseT<M, E, A>): TheseT<M, E, B> =>
+          chain(mab, f => mapT(ma, f)),
+        chain
+      }
+    }
+  }
+}
diff --git a/src/function.ts b/src/function.ts
index b4737351..3391c343 100644
--- a/src/function.ts
+++ b/src/function.ts
@@ -273,3 +273,28 @@ export function decrement(n: number): number {
 export function absurd<A>(_: never): A {
   throw new Error('Called `absurd` function which should be uncallable')
 }
+
+/**
+ * Creates a tupled version of this function: instead of `n` arguments, it accepts a single tuple argument.
+ *
+ * @example
+ * import { tupled } from 'fp-ts/lib/function'
+ *
+ * const add = tupled((x: number, y: number): number => x + y)
+ *
+ * assert.strictEqual(add([1, 2]), 3)
+ *
+ * @since 2.4.0
+ */
+export function tupled<A extends Array<unknown>, B>(f: (...a: A) => B): (a: A) => B {
+  return a => f(...a)
+}
+
+/**
+ * Inverse function of `tupled`
+ *
+ * @since 2.4.0
+ */
+export function untupled<A extends Array<unknown>, B>(f: (a: A) => B): (...a: A) => B {
+  return (...a) => f(a)
+}
diff --git a/src/index.ts b/src/index.ts
index 9e4d4598..f443c813 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -83,7 +83,9 @@ import * as store from './Store'
 import * as strong from './Strong'
 import * as task from './Task'
 import * as taskEither from './TaskEither'
+import * as taskThese from './TaskThese'
 import * as these from './These'
+import * as theseT from './TheseT'
 import * as traced from './Traced'
 import * as traversable from './Traversable'
 import * as traversableWithIndex from './TraversableWithIndex'
@@ -419,10 +421,18 @@ export {
    * @since 2.0.0
    */
   taskEither,
+  /**
+   * @since 2.4.0
+   */
+  taskThese,
   /**
    * @since 2.0.0
    */
   these,
+  /**
+   * @since 2.4.0
+   */
+  theseT,
   /**
    * @since 2.0.0
    */
diff --git a/tutorials/fp-ts-to-the-max-I.ts b/tutorials/fp-ts-to-the-max-I.ts
index 33d71bf8..ce1240f4 100644
--- a/tutorials/fp-ts-to-the-max-I.ts
+++ b/tutorials/fp-ts-to-the-max-I.ts
@@ -1,14 +1,18 @@
 import { createInterface } from 'readline'
+import { sequenceS } from '../src/Apply'
 import { log } from '../src/Console'
-import { fold, none, Option, some } from '../src/Option'
+import { flow } from '../src/function'
+import * as O from '../src/Option'
+import { pipe } from '../src/pipeable'
 import { randomInt } from '../src/Random'
-import { Task, task } from '../src/Task'
+import * as T from '../src/Task'
 
 //
 // helpers
 //
 
-const getStrLn: Task<string> = () =>
+// read from standard input
+const getStrLn: T.Task<string> = () =>
   new Promise(resolve => {
     const rl = createInterface({
       input: process.stdin,
@@ -20,60 +24,76 @@ const getStrLn: Task<string> = () =>
     })
   })
 
-const putStrLn = (message: string): Task<void> => task.fromIO(log(message))
+// write to standard output
+const putStrLn = flow(log, T.fromIO)
 
-const random = task.fromIO(randomInt(1, 5))
+// ask something and get the answer
+function ask(question: string): T.Task<string> {
+  return pipe(
+    putStrLn(question),
+    T.chain(() => getStrLn)
+  )
+}
+
+// get a random int between 1 and 5
+const random = T.fromIO(randomInt(1, 5))
 
-const parse = (s: string): Option<number> => {
+// parse a string to an integer
+function parse(s: string): O.Option<number> {
   const i = +s
-  return isNaN(i) || i % 1 !== 0 ? none : some(i)
+  return isNaN(i) || i % 1 !== 0 ? O.none : O.some(i)
 }
 
 //
 // game
 //
 
-const checkContinue = (name: string): Task<boolean> => {
-  const put = putStrLn(`Do you want to continue, ${name}?`)
-  const get = task.chain(put, () => getStrLn)
-  const answer = task.chain(get, answer => {
-    switch (answer.toLowerCase()) {
-      case 'y':
-        return task.of(true)
-      case 'n':
-        return task.of(false)
-      default:
-        return checkContinue(name)
-    }
-  })
-  return answer
+function shouldContinue(name: string): T.Task<boolean> {
+  return pipe(
+    ask(`Do you want to continue, ${name} (y/n)?`),
+    T.chain(answer => {
+      switch (answer.toLowerCase()) {
+        case 'y':
+          return T.of(true)
+        case 'n':
+          return T.of(false)
+        default:
+          return shouldContinue(name)
+      }
+    })
+  )
 }
 
-const parseFailureMessage = putStrLn('You did not enter an integer!')
+// run `n` tasks in parallel
+const ado = sequenceS(T.task)
 
-const gameLoop = (name: string): Task<void> =>
-  task.chain(random, secret => {
-    const game = task.chain(putStrLn(`Dear ${name}, please guess a number from 1 to 5`), () =>
-      task.chain(getStrLn, guess =>
-        fold(
-          () => parseFailureMessage,
+function gameLoop(name: string): T.Task<void> {
+  return pipe(
+    ado({
+      secret: random,
+      guess: ask(`Dear ${name}, please guess a number from 1 to 5`)
+    }),
+    T.chain(({ secret, guess }) =>
+      pipe(
+        parse(guess),
+        O.fold(
+          () => putStrLn('You did not enter an integer!'),
           x =>
             x === secret
               ? putStrLn(`You guessed right, ${name}!`)
               : putStrLn(`You guessed wrong, ${name}! The number was: ${secret}`)
-        )(parse(guess))
+        )
       )
-    )
-    const doContinue = task.chain(game, () => checkContinue(name))
-    return task.chain(doContinue, shouldContinue => (shouldContinue ? gameLoop(name) : task.of(undefined)))
-  })
-
-const nameMessage = putStrLn('What is your name?')
-
-const askName = task.chain(nameMessage, () => getStrLn)
+    ),
+    T.chain(() => shouldContinue(name)),
+    T.chain(b => (b ? gameLoop(name) : T.of(undefined)))
+  )
+}
 
-const main: Task<void> = task.chain(askName, name =>
-  task.chain(putStrLn(`Hello, ${name} welcome to the game!`), () => gameLoop(name))
+const main: T.Task<void> = pipe(
+  ask('What is your name?'),
+  T.chainFirst(name => putStrLn(`Hello, ${name} welcome to the game!`)),
+  T.chain(gameLoop)
 )
 
 // tslint:disable-next-line: no-floating-promises
diff --git a/tutorials/fp-ts-to-the-max-II.ts b/tutorials/fp-ts-to-the-max-II.ts
index b1d7ec5a..77b2f161 100644
--- a/tutorials/fp-ts-to-the-max-II.ts
+++ b/tutorials/fp-ts-to-the-max-II.ts
@@ -1,34 +1,14 @@
 import { log } from '../src/Console'
 import { Kind, URIS } from '../src/HKT'
-import { none, Option, some, fold } from '../src/Option'
+import * as O from '../src/Option'
 import { randomInt } from '../src/Random'
 import * as T from '../src/Task'
 import { createInterface } from 'readline'
 import { State, state } from '../src/State'
 import { Monad1 } from '../src/Monad'
-
-//
-// helpers
-//
-
-const getStrLn: T.Task<string> = () =>
-  new Promise(resolve => {
-    const rl = createInterface({
-      input: process.stdin,
-      output: process.stdout
-    })
-    rl.question('> ', answer => {
-      rl.close()
-      resolve(answer)
-    })
-  })
-
-const putStrLn = (message: string): T.Task<void> => T.task.fromIO(log(message))
-
-const parse = (s: string): Option<number> => {
-  const i = +s
-  return isNaN(i) || i % 1 !== 0 ? none : some(i)
-}
+import { flow } from '../src/function'
+import { pipe, pipeable } from '../src/pipeable'
+import { sequenceS } from '../src/Apply'
 
 //
 // type classes
@@ -55,65 +35,108 @@ interface Main<F extends URIS> extends Program<F>, Console<F>, Random<F> {}
 
 const programTask: Program<T.URI> = {
   ...T.task,
-  finish: T.task.of
+  finish: T.of
 }
 
+// read from standard input
+const getStrLn: T.Task<string> = () =>
+  new Promise(resolve => {
+    const rl = createInterface({
+      input: process.stdin,
+      output: process.stdout
+    })
+    rl.question('> ', answer => {
+      rl.close()
+      resolve(answer)
+    })
+  })
+
+// write to standard output
+const putStrLn = flow(log, T.fromIO)
+
 const consoleTask: Console<T.URI> = {
   putStrLn,
   getStrLn
 }
 
 const randomTask: Random<T.URI> = {
-  nextInt: upper => T.task.fromIO(randomInt(1, upper))
+  nextInt: upper => T.fromIO(randomInt(1, upper))
 }
 
 //
 // game
 //
 
-const checkContinue = <F extends URIS>(F: Program<F> & Console<F>) => (name: string): Kind<F, boolean> => {
-  const put = F.putStrLn(`Do you want to continue, ${name}?`)
-  const get = F.chain(put, () => F.getStrLn)
-  const answer = F.chain(get, answer => {
-    switch (answer.toLowerCase()) {
-      case 'y':
-        return F.of(true)
-      case 'n':
-        return F.of(false)
-      default:
-        return checkContinue(F)(name)
-    }
-  })
-  return answer
+// parse a string to an integer
+function parse(s: string): O.Option<number> {
+  const i = +s
+  return isNaN(i) || i % 1 !== 0 ? O.none : O.some(i)
 }
 
-const gameLoop = <F extends URIS>(F: Main<F>) => (name: string): Kind<F, void> => {
-  const parseFailureMessage = F.putStrLn('You did not enter an integer!')
-
-  return F.chain(F.nextInt(5), secret => {
-    const game = F.chain(F.putStrLn(`Dear ${name}, please guess a number from 1 to 5`), () =>
-      F.chain(F.getStrLn, guess =>
-        fold(
-          () => parseFailureMessage,
-          x =>
-            x === secret
-              ? F.putStrLn(`You guessed right, ${name}!`)
-              : F.putStrLn(`You guessed wrong, ${name}! The number was: ${secret}`)
-        )(parse(guess))
-      )
+function main<F extends URIS>(F: Main<F>): Kind<F, void> {
+  // run `n` tasks in parallel
+  const ado = sequenceS(F)
+
+  const { chain, chainFirst } = pipeable(F)
+
+  // ask something and get the answer
+  const ask = (question: string): Kind<F, string> =>
+    pipe(
+      F.putStrLn(question),
+      chain(() => F.getStrLn)
     )
-    const doContinue = F.chain(game, () => checkContinue(F)(name))
-    return F.chain(doContinue, shouldContinue => (shouldContinue ? gameLoop(F)(name) : F.of<void>(undefined)))
-  })
-}
 
-const main = <F extends URIS>(F: Main<F>): Kind<F, void> => {
-  const nameMessage = F.putStrLn('What is your name?')
-  const askName = F.chain(nameMessage, () => F.getStrLn)
-  return F.chain(askName, name => F.chain(F.putStrLn(`Hello, ${name} welcome to the game!`), () => gameLoop(F)(name)))
+  const shouldContinue = (name: string): Kind<F, boolean> => {
+    return pipe(
+      ask(`Do you want to continue, ${name} (y/n)?`),
+      chain(answer => {
+        switch (answer.toLowerCase()) {
+          case 'y':
+            return F.of<boolean>(true)
+          case 'n':
+            return F.of<boolean>(false)
+          default:
+            return shouldContinue(name)
+        }
+      })
+    )
+  }
+
+  const gameLoop = (name: string): Kind<F, void> => {
+    return pipe(
+      ado({
+        secret: F.nextInt(5),
+        guess: ask(`Dear ${name}, please guess a number from 1 to 5`)
+      }),
+      chain(({ secret, guess }) =>
+        pipe(
+          parse(guess),
+          O.fold(
+            () => F.putStrLn('You did not enter an integer!'),
+            x =>
+              x === secret
+                ? F.putStrLn(`You guessed right, ${name}!`)
+                : F.putStrLn(`You guessed wrong, ${name}! The number was: ${secret}`)
+          )
+        )
+      ),
+      chain(() => shouldContinue(name)),
+      chain(b => (b ? gameLoop(name) : F.of<void>(undefined)))
+    )
+  }
+
+  return pipe(
+    ask('What is your name?'),
+    chainFirst(name => F.putStrLn(`Hello, ${name} welcome to the game!`)),
+    chain(gameLoop)
+  )
 }
 
-export const mainTask = main({ ...programTask, ...consoleTask, ...randomTask })
+export const mainTask = main({
+  ...programTask,
+  ...consoleTask,
+  ...randomTask
+})
 
 // tslint:disable-next-line: no-floating-promises
 // mainTask.run()
@@ -171,7 +194,12 @@ const randomTest: Random<URI> = {
   }
 }
 
-const mainTestTask = main({ ...programTest, ...consoleTest, ...randomTest })
+const mainTestTask = main({
+  ...programTest,
+  ...consoleTest,
+  ...randomTest
+})
+
 const testExample = new TestData(['Giulio', '1', 'n'], [], [1])
 
 import * as assert from 'assert'
@@ -185,7 +213,7 @@ assert.deepStrictEqual(mainTestTask(testExample), [
       'Hello, Giulio welcome to the game!',
       'Dear Giulio, please guess a number from 1 to 5',
       'You guessed right, Giulio!',
-      'Do you want to continue, Giulio?'
+      'Do you want to continue, Giulio (y/n)?'
     ],
     []
   )


Test patch:
diff --git a/test/TaskThese.ts b/test/TaskThese.ts
new file mode 100644
index 00000000..48c2495e
--- /dev/null
+++ b/test/TaskThese.ts
@@ -0,0 +1,122 @@
+import * as assert from 'assert'
+import * as IO from '../src/IO'
+import { monoidString, monoidSum } from '../src/Monoid'
+import { pipe } from '../src/pipeable'
+import * as T from '../src/Task'
+import * as TT from '../src/TaskThese'
+import * as TH from '../src/These'
+
+describe('TaskThese', () => {
+  it('right', async () => {
+    const x = await TT.right(1)()
+    assert.deepStrictEqual(x, TH.right(1))
+  })
+
+  it('left', async () => {
+    const x = await TT.left('a')()
+    assert.deepStrictEqual(x, TH.left('a'))
+  })
+
+  it('both', async () => {
+    const x = await TT.both('a', 1)()
+    assert.deepStrictEqual(x, TH.both('a', 1))
+  })
+
+  it('rightIO', async () => {
+    const x = await TT.rightIO(IO.of(1))()
+    assert.deepStrictEqual(x, TH.right(1))
+  })
+
+  it('leftIO', async () => {
+    const x = await TT.leftIO(IO.of('a'))()
+    assert.deepStrictEqual(x, TH.left('a'))
+  })
+
+  it('rightTask', async () => {
+    const x = await TT.rightTask(T.of(1))()
+    assert.deepStrictEqual(x, TH.right(1))
+  })
+
+  it('leftTask', async () => {
+    const x = await TT.leftTask(T.of('a'))()
+    assert.deepStrictEqual(x, TH.left('a'))
+  })
+
+  it('fold', async () => {
+    const f = TT.fold(
+      e => T.of(`left ${e}`),
+      a => T.of(`right ${a}`),
+      (e, a) => T.of(`both ${e} ${a}`)
+    )
+    assert.deepStrictEqual(await pipe(TT.right(1), f)(), 'right 1')
+    assert.deepStrictEqual(await pipe(TT.left('a'), f)(), 'left a')
+    assert.deepStrictEqual(await pipe(TT.both('a', 1), f)(), 'both a 1')
+  })
+
+  it('swap', async () => {
+    assert.deepStrictEqual(await TT.swap(TT.right(1))(), TH.left(1))
+    assert.deepStrictEqual(await TT.swap(TT.left('a'))(), TH.right('a'))
+    assert.deepStrictEqual(await TT.swap(TT.both('a', 1))(), TH.both(1, 'a'))
+  })
+
+  it('toTuple', async () => {
+    const f = TT.toTuple('b', 2)
+    assert.deepStrictEqual(await f(TT.right(1))(), ['b', 1])
+    assert.deepStrictEqual(await f(TT.left('a'))(), ['a', 2])
+    assert.deepStrictEqual(await f(TT.both('a', 1))(), ['a', 1])
+  })
+
+  it('bimap', async () => {
+    const f = TT.bimap(
+      (e: string) => e + e,
+      (a: number) => a + 1
+    )
+    assert.deepStrictEqual(await pipe(TT.right(1), f)(), TH.right(2))
+    assert.deepStrictEqual(await pipe(TT.left('a'), f)(), TH.left('aa'))
+    assert.deepStrictEqual(await pipe(TT.both('a', 1), f)(), TH.both('aa', 2))
+  })
+
+  it('mapLeft', async () => {
+    const f = TT.mapLeft((e: string) => e + e)
+    assert.deepStrictEqual(await pipe(TT.right(1), f)(), TH.right(1))
+    assert.deepStrictEqual(await pipe(TT.left('a'), f)(), TH.left('aa'))
+    assert.deepStrictEqual(await pipe(TT.both('a', 1), f)(), TH.both('aa', 1))
+  })
+
+  it('getSemigroup', async () => {
+    const S = TT.getSemigroup(monoidString, monoidSum)
+    assert.deepStrictEqual(await S.concat(TT.right(1), TT.right(2))(), TH.right(3))
+    assert.deepStrictEqual(await S.concat(TT.right(1), TT.left('a'))(), TH.both('a', 1))
+    assert.deepStrictEqual(await S.concat(TT.left('a'), TT.left('b'))(), TH.left('ab'))
+    assert.deepStrictEqual(await S.concat(TT.right(1), TT.both('a', 2))(), TH.both('a', 3))
+    assert.deepStrictEqual(await S.concat(TT.left('a'), TT.both('b', 2))(), TH.both('ab', 2))
+    assert.deepStrictEqual(await S.concat(TT.both('a', 1), TT.both('b', 2))(), TH.both('ab', 3))
+  })
+
+  describe('getMonad', () => {
+    const M = TT.getMonad(monoidString)
+    it('map', async () => {
+      const f = (n: number): number => n * 2
+      assert.deepStrictEqual(await M.map(TT.right(1), f)(), TH.right(2))
+      assert.deepStrictEqual(await M.map(TT.left('a'), f)(), TH.left('a'))
+      assert.deepStrictEqual(await M.map(TT.both('a', 1), f)(), TH.both('a', 2))
+    })
+
+    it('map', async () => {
+      const f = (n: number): number => n * 2
+      assert.deepStrictEqual(await M.ap(TT.right(f), TT.right(1))(), TH.right(2))
+    })
+
+    it('chain', async () => {
+      const f = (n: number) => (n > 2 ? TT.both(`c`, n + 1) : n > 1 ? TT.right(n * 2) : TT.left(`b`))
+      assert.deepStrictEqual(await M.chain(TT.right(1), f)(), TH.left('b'))
+      assert.deepStrictEqual(await M.chain(TT.right(2), f)(), TH.right(4))
+
+      assert.deepStrictEqual(await M.chain(TT.left('a'), f)(), TH.left('a'))
+
+      assert.deepStrictEqual(await M.chain(TT.both('a', 1), f)(), TH.left('ab'))
+      assert.deepStrictEqual(await M.chain(TT.both('a', 2), f)(), TH.right(4))
+      assert.deepStrictEqual(await M.chain(TT.both('a', 3), f)(), TH.both('ac', 4))
+    })
+  })
+})
diff --git a/test/These.ts b/test/These.ts
index e6b8c60a..287815a0 100644
--- a/test/These.ts
+++ b/test/These.ts
@@ -237,4 +237,10 @@ describe('These', () => {
     assert.deepStrictEqual(S.show(_.right('a')), `right("a")`)
     assert.deepStrictEqual(S.show(_.both('a', 'b')), `both("a", "b")`)
   })
+
+  it('swap', () => {
+    assert.deepStrictEqual(_.swap(_.left('a')), _.right('a'))
+    assert.deepStrictEqual(_.swap(_.right('a')), _.left('a'))
+    assert.deepStrictEqual(_.swap(_.both('a', 1)), _.both(1, 'a'))
+  })
 })
diff --git a/test/function.ts b/test/function.ts
index 45c24e79..5d5263df 100644
--- a/test/function.ts
+++ b/test/function.ts
@@ -12,7 +12,9 @@ import {
   not,
   unsafeCoerce,
   absurd,
-  flow
+  flow,
+  tupled,
+  untupled
 } from '../src/function'
 
 const f = (n: number) => n + 1
@@ -78,4 +80,22 @@ describe('function', () => {
     assert.deepStrictEqual(flow(f, g, f, g, f, g, f, g)(2), 62)
     assert.deepStrictEqual(flow(f, g, f, g, f, g, f, g, f)(2), 63)
   })
+
+  it('tupled', () => {
+    const f1 = (a: number): number => a * 2
+    const f2 = (a: number, b: number): number => a + b
+    const u1 = tupled(f1)
+    const u2 = tupled(f2)
+    assert.deepStrictEqual(u1([1]), 2)
+    assert.deepStrictEqual(u2([1, 2]), 3)
+  })
+
+  it('untupled', () => {
+    const f1 = (a: [number]): number => a[0] * 2
+    const f2 = (a: [number, number]): number => a[0] + a[1]
+    const u1 = untupled(f1)
+    const u2 = untupled(f2)
+    assert.deepStrictEqual(u1(1), 2)
+    assert.deepStrictEqual(u2(1, 2), 3)
+  })
 })