Repository: gcanti/fp-ts
Pull request #858 (2019-05-06 15:37:47)

Experimental: add fluent module


Patch:
diff --git a/perf/Fluent.perf.ts b/perf/fluent.ts
similarity index 81%
rename from perf/Fluent.perf.ts
rename to perf/fluent.ts
index 5de2b055..603f4a50 100644
--- a/perf/Fluent.perf.ts
+++ b/perf/fluent.ts
@@ -1,7 +1,15 @@
 import * as Benchmark from 'benchmark'
-import * as F from './Fluent.def'
+import * as F from '../src/fluent'
 import { option, some, none } from '../src/Option'
 
+/*
+
+fluent x 43,375,050 ops/sec ±0.48% (89 runs sampled)
+static dictionary x 57,296,775 ops/sec ±0.38% (88 runs sampled)
+static dictionary (direct) x 62,190,037 ops/sec ±0.32% (89 runs sampled)
+
+*/
+
 const suite = new Benchmark.Suite()
 
 const fluent = F.fluent(option)
diff --git a/perf/Fluent.def.ts b/src/fluent.ts
similarity index 52%
rename from perf/Fluent.def.ts
rename to src/fluent.ts
index e3db1f4c..dc272a56 100644
--- a/perf/Fluent.def.ts
+++ b/src/fluent.ts
@@ -1,48 +1,31 @@
-import { Alt, Alt1, Alt2, Alt2C } from '../src/Alt'
-import { Apply, Apply1, Apply2, Apply2C } from '../src/Apply'
-import { Chain, Chain1, Chain2, Chain2C } from '../src/Chain'
-import { Separated, Compactable, Compactable1, Compactable2, Compactable2C } from '../src/Compactable'
-import { Either } from '../src/Either'
-import { Filterable, Filterable1, Filterable2, Filterable2C } from '../src/Filterable'
-import { Foldable, Foldable1, Foldable2, Foldable2C } from '../src/Foldable'
-import { Predicate, identity } from '../src/function'
-import { Functor, Functor1, Functor2, Functor2C } from '../src/Functor'
-import { HKT, Type, Type2, URIS, URIS2, HKT2 } from '../src/HKT'
-import { Monoid } from '../src/Monoid'
-import { Option } from '../src/Option'
-import { Magma } from '../src/Magma'
-import { Eq } from '../src/Eq'
-import { Bifunctor, Bifunctor2 } from '../src/Bifunctor'
-import { Ord } from '../src/Ord'
-import { Ordering } from '../src/Ordering'
-import { Profunctor, Profunctor2 } from '../src/Profunctor'
-import { Show } from '../src/Show'
-import { FunctorWithIndex, FunctorWithIndex1, FunctorWithIndex2, FunctorWithIndex2C } from '../src/FunctorWithIndex'
-import {
-  FoldableWithIndex,
-  FoldableWithIndex1,
-  FoldableWithIndex2,
-  FoldableWithIndex2C
-} from '../src/FoldableWithIndex'
+import { Alt, Alt1, Alt2, Alt2C } from './Alt'
+import { Apply, Apply1, Apply2, Apply2C } from './Apply'
+import { Bifunctor, Bifunctor2, Bifunctor2C } from './Bifunctor'
+import { Chain, Chain1, Chain2, Chain2C } from './Chain'
+import { Compactable, Compactable1, Compactable2, Compactable2C, Separated } from './Compactable'
+import { Either } from './Either'
+import { Extend, Extend1, Extend2, Extend2C } from './Extend'
+import { Filterable, Filterable1, Filterable2, Filterable2C } from './Filterable'
 import {
   FilterableWithIndex,
   FilterableWithIndex1,
   FilterableWithIndex2,
   FilterableWithIndex2C
-} from '../src/FilterableWithIndex'
-import { Extend, Extend2, Extend1, Extend2C } from '../src/Extend'
-
-// TODO remove when fp-ts@1.17.2 lends
-interface Bifunctor2C<F extends URIS2, L> {
-  readonly URI: F
-  readonly _L: L
-  readonly bimap: <A, M, B>(fla: Type2<F, L, A>, f: (l: L) => M, g: (a: A) => B) => Type2<F, M, B>
-}
-
-// TODO remove when fp-ts@1.17.2 lends
-interface Profunctor2C<F extends URIS2, L> extends Functor2C<F, L> {
-  readonly promap: <A, C, D>(flc: Type2<F, L, C>, f: (a: A) => L, g: (c: C) => D) => Type2<F, A, D>
-}
+} from './FilterableWithIndex'
+import { Foldable, Foldable1, Foldable2, Foldable2C } from './Foldable'
+import { FoldableWithIndex, FoldableWithIndex1, FoldableWithIndex2, FoldableWithIndex2C } from './FoldableWithIndex'
+import { identity, Predicate, Refinement } from './function'
+import { Functor, Functor1, Functor2, Functor2C } from './Functor'
+import { FunctorWithIndex, FunctorWithIndex1, FunctorWithIndex2, FunctorWithIndex2C } from './FunctorWithIndex'
+import { HKT, HKT2, Type, Type2, URIS, URIS2 } from './HKT'
+import { Magma } from './Magma'
+import { Monoid } from './Monoid'
+import { Option } from './Option'
+import { Ord } from './Ord'
+import { Ordering } from './Ordering'
+import { Profunctor, Profunctor2, Profunctor2C } from './Profunctor'
+import { Eq } from './Eq'
+import { Show } from './Show'
 
 interface Fluent2C<F extends URIS2, I, L, A> {
   readonly I: I
@@ -78,12 +61,20 @@ interface Fluent2C<F extends URIS2, I, L, A> {
     b: B,
     f: (i: Ix, a: A, b: B) => B
   ): B
-  alt(this: Fluent2C<F, Alt2C<F, L>, L, A>, that: Type2<F, L, A>): Fluent2C<F, I, L, A>
+  alt(this: Fluent2C<F, Alt2C<F, L>, L, A>, that: () => Type2<F, L, A>): Fluent2C<F, I, L, A>
   compact<A>(this: Fluent2<F, Compactable2C<F, L>, L, Option<A>>): Fluent2C<F, I, L, A>
   separate<A, B>(this: Fluent2C<F, Compactable2C<F, L>, L, Either<A, B>>): Separated<Type2<F, L, A>, Type2<F, L, B>>
-  filter(this: Fluent2C<F, Filterable2C<F, L>, L, A>, p: Predicate<A>): Fluent2C<F, I, L, A>
+  filter<B extends A>(this: Fluent2C<F, Filterable2C<F, L>, L, A>, refinement: Refinement<A, B>): Fluent2C<F, I, L, B>
+  filter(this: Fluent2C<F, Filterable2C<F, L>, L, A>, predicate: Predicate<A>): Fluent2C<F, I, L, A>
   filterMap<B>(this: Fluent2C<F, Filterable2C<F, L>, L, A>, f: (a: A) => Option<B>): Fluent2C<F, I, L, B>
-  partition(this: Fluent2C<F, Filterable2C<F, L>, L, A>, p: Predicate<A>): Separated<Type2<F, L, A>, Type2<F, L, A>>
+  partition<B extends A>(
+    this: Fluent2C<F, Filterable2C<F, L>, L, A>,
+    refinement: Refinement<A, B>
+  ): Separated<Type2<F, L, A>, Type2<F, L, B>>
+  partition(
+    this: Fluent2C<F, Filterable2C<F, L>, L, A>,
+    predicate: Predicate<A>
+  ): Separated<Type2<F, L, A>, Type2<F, L, A>>
   partitionMap<RL, RR>(
     this: Fluent2<F, Filterable2C<F, L>, L, A>,
     f: (a: A) => Either<RL, RR>
@@ -131,12 +122,17 @@ interface Fluent2<F extends URIS2, I, L, A> {
   reduceWithIndex<Ix, B>(this: Fluent2<F, FoldableWithIndex2<F, Ix>, L, A>, b: B, f: (i: Ix, b: B, a: A) => B): B
   foldMapWithIndex<Ix, M>(this: Fluent2<F, FoldableWithIndex2<F, Ix>, L, A>, M: Monoid<M>): (i: Ix, f: (a: A) => M) => M
   reduceRightWithIndex<Ix, B>(this: Fluent2<F, FoldableWithIndex2<F, Ix>, L, A>, b: B, f: (i: Ix, a: A, b: B) => B): B
-  alt(this: Fluent2<F, Alt2<F>, L, A>, that: Type2<F, L, A>): Fluent2<F, I, L, A>
+  alt(this: Fluent2<F, Alt2<F>, L, A>, that: () => Type2<F, L, A>): Fluent2<F, I, L, A>
   compact<A>(this: Fluent2<F, Compactable2<F>, L, Option<A>>): Fluent2<F, I, L, A>
   separate<A, B>(this: Fluent2<F, Compactable2<F>, L, Either<A, B>>): Separated<Type2<F, L, A>, Type2<F, L, B>>
-  filter(this: Fluent2<F, Filterable2<F>, L, A>, p: Predicate<A>): Fluent2<F, I, L, A>
+  filter<B extends A>(this: Fluent2<F, Filterable2<F>, L, A>, refinement: Refinement<A, B>): Fluent2<F, I, L, B>
+  filter(this: Fluent2<F, Filterable2<F>, L, A>, predicate: Predicate<A>): Fluent2<F, I, L, A>
   filterMap<B>(this: Fluent2<F, Filterable2<F>, L, A>, f: (a: A) => Option<B>): Fluent2<F, I, L, B>
-  partition(this: Fluent2<F, Filterable2<F>, L, A>, p: Predicate<A>): Separated<Type2<F, L, A>, Type2<F, L, A>>
+  partition<B extends A>(
+    this: Fluent2<F, Filterable2<F>, L, A>,
+    refinement: Refinement<A, B>
+  ): Separated<Type2<F, L, A>, Type2<F, L, B>>
+  partition(this: Fluent2<F, Filterable2<F>, L, A>, predicate: Predicate<A>): Separated<Type2<F, L, A>, Type2<F, L, A>>
   partitionMap<RL, RR>(
     this: Fluent2<F, Filterable2<F>, L, A>,
     f: (a: A) => Either<RL, RR>
@@ -182,12 +178,17 @@ interface Fluent1<F extends URIS, I, A> {
   reduceWithIndex<Ix, B>(this: Fluent1<F, FoldableWithIndex1<F, Ix>, A>, b: B, f: (i: Ix, b: B, a: A) => B): B
   foldMapWithIndex<Ix, M>(this: Fluent1<F, FoldableWithIndex1<F, Ix>, A>, M: Monoid<M>): (f: (i: Ix, a: A) => M) => M
   reduceRightWithIndex<Ix, B>(this: Fluent1<F, FoldableWithIndex1<F, Ix>, A>, b: B, f: (i: Ix, a: A, b: B) => B): B
-  alt(this: Fluent1<F, Alt1<F>, A>, that: Type<F, A>): Fluent1<F, I, A>
+  alt(this: Fluent1<F, Alt1<F>, A>, that: () => Type<F, A>): Fluent1<F, I, A>
   compact<A>(this: Fluent1<F, Compactable1<F>, Option<A>>): Fluent1<F, I, A>
   separate<A, B>(this: Fluent1<F, Compactable1<F>, Either<A, B>>): Separated<Type<F, A>, Type<F, B>>
-  filter(this: Fluent1<F, Filterable1<F>, A>, p: Predicate<A>): Fluent1<F, I, A>
+  filter<B extends A>(this: Fluent1<F, Filterable1<F>, A>, refinement: Refinement<A, B>): Fluent1<F, I, B>
+  filter(this: Fluent1<F, Filterable1<F>, A>, predicate: Predicate<A>): Fluent1<F, I, A>
   filterMap<B>(this: Fluent1<F, Filterable1<F>, A>, f: (a: A) => Option<B>): Fluent1<F, I, B>
-  partition(this: Fluent1<F, Filterable1<F>, A>, p: Predicate<A>): Separated<Type<F, A>, Type<F, A>>
+  partition<B extends A>(
+    this: Fluent1<F, Filterable1<F>, A>,
+    refinement: Refinement<A, B>
+  ): Separated<Type<F, A>, Type<F, B>>
+  partition(this: Fluent1<F, Filterable1<F>, A>, predicate: Predicate<A>): Separated<Type<F, A>, Type<F, A>>
   partitionMap<RL, RR>(
     this: Fluent1<F, Filterable1<F>, A>,
     f: (a: A) => Either<RL, RR>
@@ -207,159 +208,221 @@ interface Fluent1<F extends URIS, I, A> {
   ): Separated<Type<F, RL>, Type<F, RR>>
 }
 
-class FluentHKT<F, I, A> {
+interface FluentHKT2<F, I, L, A> {
+  readonly I: I
+  readonly value: HKT2<F, L, A>
+  show(this: FluentHKT2<F, Show<HKT2<F, L, A>>, L, A>): string
+  equals(this: FluentHKT2<F, Eq<HKT2<F, L, A>>, L, A>, that: HKT2<F, L, A>): boolean
+  compare(this: FluentHKT2<F, Ord<HKT2<F, L, A>>, L, A>, that: HKT2<F, L, A>): Ordering
+  concat(this: FluentHKT2<F, Magma<HKT2<F, L, A>>, L, A>, that: HKT2<F, L, A>): HKT2<F, L, A>
+  map<B>(this: FluentHKT2<F, Functor<F>, L, A>, f: (a: A) => B): FluentHKT2<F, I, L, B>
+  mapWithIndex<Ix, B>(this: FluentHKT2<F, FunctorWithIndex<F, Ix>, L, A>, f: (i: Ix, a: A) => B): FluentHKT2<F, I, L, B>
+  bimap<L, M, B>(this: FluentHKT2<F, Bifunctor<F>, L, A>, f: (l: L) => M, g: (a: A) => B): FluentHKT2<F, I, M, B>
+  mapLeft<L, M>(this: FluentHKT2<F, Bifunctor<F>, L, A>, f: (l: L) => M): FluentHKT2<F, I, M, A>
+  ap<B>(this: FluentHKT2<F, Apply<F>, L, A>, fab: HKT2<F, L, (a: A) => B>): FluentHKT2<F, I, L, B>
+  apFirst<B>(this: FluentHKT2<F, Apply<F>, L, A>, that: HKT2<F, L, B>): FluentHKT2<F, I, L, A>
+  apSecond<B>(this: FluentHKT2<F, Apply<F>, L, A>, that: HKT2<F, L, B>): FluentHKT2<F, I, L, B>
+  chain<B>(this: FluentHKT2<F, Chain<F>, L, A>, f: (a: A) => HKT2<F, L, B>): FluentHKT2<F, I, L, B>
+  flatten<A>(this: FluentHKT2<F, Chain<F>, L, HKT2<F, L, A>>): FluentHKT2<F, I, L, A>
+  extend<B>(this: FluentHKT2<F, Extend<F>, L, A>, f: (fa: HKT2<F, L, A>) => B): FluentHKT2<F, I, L, B>
+  duplicate(this: FluentHKT2<F, Extend<F>, L, A>): FluentHKT2<F, I, L, HKT2<F, L, A>>
+  reduce<B>(this: FluentHKT2<F, Foldable<F>, L, A>, b: B, f: (b: B, a: A) => B): B
+  foldMap<M>(this: FluentHKT2<F, Foldable<F>, L, A>, M: Monoid<M>): (f: (a: A) => M) => M
+  reduceRight<B>(this: FluentHKT2<F, Foldable<F>, L, A>, b: B, f: (a: A, b: B) => B): B
+  reduceWithIndex<Ix, B>(this: FluentHKT2<F, FoldableWithIndex<F, Ix>, L, A>, b: B, f: (i: Ix, b: B, a: A) => B): B
+  foldMapWithIndex<Ix, M>(
+    this: FluentHKT2<F, FoldableWithIndex<F, Ix>, L, A>,
+    M: Monoid<M>
+  ): (i: Ix, f: (a: A) => M) => M
+  reduceRightWithIndex<Ix, B>(this: FluentHKT2<F, FoldableWithIndex<F, Ix>, L, A>, b: B, f: (i: Ix, a: A, b: B) => B): B
+  alt(this: FluentHKT2<F, Alt<F>, L, A>, that: () => HKT2<F, L, A>): FluentHKT2<F, I, L, A>
+  compact<A>(this: FluentHKT2<F, Compactable<F>, L, Option<A>>): FluentHKT2<F, I, L, A>
+  separate<A, B>(this: FluentHKT2<F, Compactable<F>, L, Either<A, B>>): Separated<HKT2<F, L, A>, HKT2<F, L, B>>
+  filter<B extends A>(this: FluentHKT2<F, Filterable<F>, L, A>, refinement: Refinement<A, B>): FluentHKT2<F, I, L, B>
+  filter(this: FluentHKT2<F, Filterable<F>, L, A>, predicate: Predicate<A>): FluentHKT2<F, I, L, A>
+  filterMap<B>(this: FluentHKT2<F, Filterable<F>, L, A>, f: (a: A) => Option<B>): FluentHKT2<F, I, L, B>
+  partition<B extends A>(
+    this: FluentHKT2<F, Filterable<F>, L, A>,
+    refinement: Refinement<A, B>
+  ): Separated<HKT2<F, L, A>, HKT2<F, L, B>>
+  partition(this: FluentHKT2<F, Filterable<F>, L, A>, predicate: Predicate<A>): Separated<HKT2<F, L, A>, HKT2<F, L, A>>
+  partitionMap<RL, RR>(
+    this: FluentHKT2<F, Filterable<F>, L, A>,
+    f: (a: A) => Either<RL, RR>
+  ): Separated<HKT2<F, L, RL>, HKT2<F, L, RR>>
+  filterWithIndex<Ix>(
+    this: FluentHKT2<F, FilterableWithIndex<F, Ix>, L, A>,
+    p: (i: Ix, a: A) => boolean
+  ): FluentHKT2<F, I, L, A>
+  filterMapWithIndex<Ix, B>(
+    this: FluentHKT2<F, FilterableWithIndex<F, Ix>, L, A>,
+    f: (i: Ix, a: A) => Option<B>
+  ): FluentHKT2<F, I, L, B>
+  partitionWithIndex<Ix>(
+    this: FluentHKT2<F, FilterableWithIndex<F, Ix>, L, A>,
+    p: (i: Ix, a: A) => boolean
+  ): Separated<HKT2<F, L, A>, HKT2<F, L, A>>
+  partitionMapWithIndex<Ix, RL, RR>(
+    this: FluentHKT2<F, FilterableWithIndex<F, Ix>, L, A>,
+    f: (i: Ix, a: A) => Either<RL, RR>
+  ): Separated<HKT2<F, L, RL>, HKT2<F, L, RR>>
+  promap<A, B, C, D>(this: FluentHKT2<F, Profunctor<F>, B, C>, f: (a: A) => B, g: (c: C) => D): FluentHKT2<F, I, A, D>
+}
+
+class Fluent<F, I, A> {
   constructor(readonly I: I, readonly value: HKT<F, A>) {}
-  show<I extends Show<HKT<F, A>>>(this: FluentHKT<F, I, A>): string {
+  show<I extends Show<HKT<F, A>>>(this: Fluent<F, I, A>): string {
     return this.I.show(this.value)
   }
-  equals<I extends Eq<HKT<F, A>>>(this: FluentHKT<F, I, A>, that: HKT<F, A>): boolean {
+  equals<I extends Eq<HKT<F, A>>>(this: Fluent<F, I, A>, that: HKT<F, A>): boolean {
     return this.I.equals(this.value, that)
   }
-  compare<I extends Ord<HKT<F, A>>>(this: FluentHKT<F, I, A>, that: HKT<F, A>): Ordering {
+  compare<I extends Ord<HKT<F, A>>>(this: Fluent<F, I, A>, that: HKT<F, A>): Ordering {
     return this.I.compare(this.value, that)
   }
-  concat<I extends Magma<HKT<F, A>>>(this: FluentHKT<F, I, A>, that: HKT<F, A>): HKT<F, A> {
+  concat<I extends Magma<HKT<F, A>>>(this: Fluent<F, I, A>, that: HKT<F, A>): HKT<F, A> {
     return this.I.concat(this.value, that)
   }
-  map<I extends Functor<F>, B>(this: FluentHKT<F, I, A>, f: (a: A) => B): FluentHKT<F, I, B> {
-    return new FluentHKT<F, I, B>(this.I, this.I.map(this.value, f))
+  map<I extends Functor<F>, B>(this: Fluent<F, I, A>, f: (a: A) => B): Fluent<F, I, B> {
+    return new Fluent<F, I, B>(this.I, this.I.map(this.value, f))
   }
   mapWithIndex<Ix, I extends FunctorWithIndex<F, Ix>, B>(
-    this: FluentHKT<F, I, A>,
+    this: Fluent<F, I, A>,
     f: (i: Ix, a: A) => B
-  ): FluentHKT<F, I, B> {
-    return new FluentHKT<F, I, B>(this.I, this.I.mapWithIndex(this.value, f))
+  ): Fluent<F, I, B> {
+    return new Fluent<F, I, B>(this.I, this.I.mapWithIndex(this.value, f))
   }
-  ap<I extends Apply<F>, B>(this: FluentHKT<F, I, A>, fab: HKT<F, (a: A) => B>): FluentHKT<F, I, B> {
-    return new FluentHKT<F, I, B>(this.I, this.I.ap(fab, this.value))
+  ap<I extends Apply<F>, B>(this: Fluent<F, I, A>, fab: HKT<F, (a: A) => B>): Fluent<F, I, B> {
+    return new Fluent<F, I, B>(this.I, this.I.ap(fab, this.value))
   }
-  apFirst<I extends Apply<F>, B>(this: FluentHKT<F, I, A>, that: HKT<F, B>): FluentHKT<F, I, A> {
-    return new FluentHKT<F, I, A>(this.I, this.I.ap(this.I.map(this.value, a => () => a), that))
+  apFirst<I extends Apply<F>, B>(this: Fluent<F, I, A>, that: HKT<F, B>): Fluent<F, I, A> {
+    return new Fluent<F, I, A>(this.I, this.I.ap(this.I.map(this.value, a => () => a), that))
   }
-  apSecond<I extends Apply<F>, B>(this: FluentHKT<F, I, A>, that: HKT<F, B>): FluentHKT<F, I, B> {
-    return new FluentHKT<F, I, B>(this.I, this.I.ap(this.I.map(this.value, () => (b: B) => b), that))
+  apSecond<I extends Apply<F>, B>(this: Fluent<F, I, A>, that: HKT<F, B>): Fluent<F, I, B> {
+    return new Fluent<F, I, B>(this.I, this.I.ap(this.I.map(this.value, () => (b: B) => b), that))
   }
-  chain<I extends Chain<F>, B>(this: FluentHKT<F, I, A>, f: (a: A) => HKT<F, B>): FluentHKT<F, I, B> {
-    return new FluentHKT<F, I, B>(this.I, this.I.chain(this.value, f))
+  chain<I extends Chain<F>, B>(this: Fluent<F, I, A>, f: (a: A) => HKT<F, B>): Fluent<F, I, B> {
+    return new Fluent<F, I, B>(this.I, this.I.chain(this.value, f))
   }
-  flatten<I extends Chain<F>, A>(this: FluentHKT<F, I, HKT<F, A>>): FluentHKT<F, I, A> {
-    return new FluentHKT<F, I, A>(this.I, this.I.chain(this.value, identity))
+  flatten<I extends Chain<F>, A>(this: Fluent<F, I, HKT<F, A>>): Fluent<F, I, A> {
+    return new Fluent<F, I, A>(this.I, this.I.chain(this.value, identity))
   }
-  extend<I extends Extend<F>, B>(this: FluentHKT<F, I, A>, f: (fa: HKT<F, A>) => B): FluentHKT<F, I, B> {
-    return new FluentHKT<F, I, B>(this.I, this.I.extend(this.value, f))
+  extend<I extends Extend<F>, B>(this: Fluent<F, I, A>, f: (fa: HKT<F, A>) => B): Fluent<F, I, B> {
+    return new Fluent<F, I, B>(this.I, this.I.extend(this.value, f))
   }
-  duplicate<I extends Extend<F>>(this: FluentHKT<F, I, A>): FluentHKT<F, I, HKT<F, A>> {
-    return new FluentHKT<F, I, HKT<F, A>>(this.I, this.I.extend(this.value, identity))
+  duplicate<I extends Extend<F>>(this: Fluent<F, I, A>): Fluent<F, I, HKT<F, A>> {
+    return new Fluent<F, I, HKT<F, A>>(this.I, this.I.extend(this.value, identity))
   }
-  reduce<I extends Foldable<F>, B>(this: FluentHKT<F, I, A>, b: B, f: (b: B, a: A) => B): B {
+  reduce<I extends Foldable<F>, B>(this: Fluent<F, I, A>, b: B, f: (b: B, a: A) => B): B {
     return this.I.reduce(this.value, b, f)
   }
-  foldMap<I extends Foldable<F>, M>(this: FluentHKT<F, I, A>, M: Monoid<M>): (f: (a: A) => M) => M {
+  foldMap<I extends Foldable<F>, M>(this: Fluent<F, I, A>, M: Monoid<M>): (f: (a: A) => M) => M {
     const foldMapM = this.I.foldMap(M)
     return f => foldMapM(this.value, f)
   }
-  reduceRight<I extends Foldable<F>, B>(this: FluentHKT<F, I, A>, b: B, f: (a: A, b: B) => B): B {
+  reduceRight<I extends Foldable<F>, B>(this: Fluent<F, I, A>, b: B, f: (a: A, b: B) => B): B {
     return this.I.reduceRight(this.value, b, f)
   }
   reduceWithIndex<Ix, I extends FoldableWithIndex<F, Ix>, B>(
-    this: FluentHKT<F, I, A>,
+    this: Fluent<F, I, A>,
     b: B,
     f: (i: Ix, b: B, a: A) => B
   ): B {
     return this.I.reduceWithIndex(this.value, b, f)
   }
   foldMapWithIndex<Ix, I extends FoldableWithIndex<F, Ix>, M>(
-    this: FluentHKT<F, I, A>,
+    this: Fluent<F, I, A>,
     M: Monoid<M>
   ): (f: (i: Ix, a: A) => M) => M {
     const foldMapWithIndexM = this.I.foldMapWithIndex(M)
     return f => foldMapWithIndexM(this.value, f)
   }
   reduceRightWithIndex<Ix, I extends FoldableWithIndex<F, Ix>, B>(
-    this: FluentHKT<F, I, A>,
+    this: Fluent<F, I, A>,
     b: B,
     f: (i: Ix, a: A, b: B) => B
   ): B {
     return this.I.reduceRightWithIndex(this.value, b, f)
   }
-  alt<I extends Alt<F>>(this: FluentHKT<F, I, A>, f: () => HKT<F, A>): FluentHKT<F, I, A> {
-    return new FluentHKT<F, I, A>(this.I, this.I.alt(this.value, f))
+  alt<I extends Alt<F>>(this: Fluent<F, I, A>, that: () => HKT<F, A>): Fluent<F, I, A> {
+    return new Fluent<F, I, A>(this.I, this.I.alt(this.value, that))
   }
-  compact<I extends Compactable<F>, A>(this: FluentHKT<F, I, Option<A>>): FluentHKT<F, I, A> {
-    return new FluentHKT<F, I, A>(this.I, this.I.compact(this.value))
+  compact<I extends Compactable<F>, A>(this: Fluent<F, I, Option<A>>): Fluent<F, I, A> {
+    return new Fluent<F, I, A>(this.I, this.I.compact(this.value))
   }
-  separate<I extends Compactable<F>, A, B>(this: FluentHKT<F, I, Either<A, B>>): Separated<HKT<F, A>, HKT<F, B>> {
+  separate<I extends Compactable<F>, A, B>(this: Fluent<F, I, Either<A, B>>): Separated<HKT<F, A>, HKT<F, B>> {
     return this.I.separate(this.value)
   }
-  filter<I extends Filterable<F>>(this: FluentHKT<F, I, A>, p: Predicate<A>): FluentHKT<F, I, A> {
-    return new FluentHKT<F, I, A>(this.I, this.I.filter(this.value, p))
+  filter<I extends Filterable<F>>(this: Fluent<F, I, A>, predicate: Predicate<A>): Fluent<F, I, A> {
+    return new Fluent<F, I, A>(this.I, this.I.filter(this.value, predicate))
   }
-  filterMap<I extends Filterable<F>, B>(this: FluentHKT<F, I, A>, f: (a: A) => Option<B>): FluentHKT<F, I, B> {
-    return new FluentHKT<F, I, B>(this.I, this.I.filterMap(this.value, f))
+  filterMap<I extends Filterable<F>, B>(this: Fluent<F, I, A>, f: (a: A) => Option<B>): Fluent<F, I, B> {
+    return new Fluent<F, I, B>(this.I, this.I.filterMap(this.value, f))
   }
-  partition<I extends Filterable<F>>(this: FluentHKT<F, I, A>, p: Predicate<A>): Separated<HKT<F, A>, HKT<F, A>> {
-    return this.I.partition(this.value, p)
+  partition<I extends Filterable<F>>(this: Fluent<F, I, A>, predicate: Predicate<A>): Separated<HKT<F, A>, HKT<F, A>> {
+    return this.I.partition(this.value, predicate)
   }
   partitionMap<I extends Filterable<F>, RL, RR>(
-    this: FluentHKT<F, I, A>,
+    this: Fluent<F, I, A>,
     f: (a: A) => Either<RL, RR>
   ): Separated<HKT<F, RL>, HKT<F, RR>> {
     return this.I.partitionMap(this.value, f)
   }
   filterWithIndex<Ix, I extends FilterableWithIndex<F, Ix>>(
-    this: FluentHKT<F, I, A>,
+    this: Fluent<F, I, A>,
     p: (i: Ix, a: A) => boolean
-  ): FluentHKT<F, I, A> {
-    return new FluentHKT<F, I, A>(this.I, this.I.filterWithIndex(this.value, p))
+  ): Fluent<F, I, A> {
+    return new Fluent<F, I, A>(this.I, this.I.filterWithIndex(this.value, p))
   }
   filterMapWithIndex<Ix, I extends FilterableWithIndex<F, Ix>, B>(
-    this: FluentHKT<F, I, A>,
+    this: Fluent<F, I, A>,
     f: (i: Ix, a: A) => Option<B>
-  ): FluentHKT<F, I, B> {
-    return new FluentHKT<F, I, B>(this.I, this.I.filterMapWithIndex(this.value, f))
+  ): Fluent<F, I, B> {
+    return new Fluent<F, I, B>(this.I, this.I.filterMapWithIndex(this.value, f))
   }
   partitionWithIndex<Ix, I extends FilterableWithIndex<F, Ix>>(
-    this: FluentHKT<F, I, A>,
+    this: Fluent<F, I, A>,
     p: (i: Ix, a: A) => boolean
   ): Separated<HKT<F, A>, HKT<F, A>> {
     return this.I.partitionWithIndex(this.value, p)
   }
   partitionMapWithIndex<Ix, I extends FilterableWithIndex<F, Ix>, RL, RR>(
-    this: FluentHKT<F, I, A>,
+    this: Fluent<F, I, A>,
     f: (i: Ix, a: A) => Either<RL, RR>
   ): Separated<HKT<F, RL>, HKT<F, RR>> {
     return this.I.partitionMapWithIndex(this.value, f)
   }
-}
-
-class FluentHKT2<F, I, L, A> extends FluentHKT<F, I, A> {
-  constructor(readonly I: I, readonly value: HKT2<F, L, A>) {
-    super(I, value)
-  }
-  bimap<I extends Bifunctor<F>, M, B>(
-    this: FluentHKT2<F, I, L, A>,
+  bimap<I extends Bifunctor<F>, L, M, B>(
+    this: { I: I; value: HKT2<F, L, A> },
     f: (l: L) => M,
     g: (a: A) => B
-  ): FluentHKT2<F, I, M, B> {
-    return new FluentHKT2<F, I, M, B>(this.I, this.I.bimap(this.value, f, g))
+  ): Fluent<F, I, B> {
+    return new Fluent<F, I, B>(this.I, this.I.bimap(this.value, f, g))
   }
-  mapLeft<I extends Bifunctor<F>, M>(this: FluentHKT2<F, I, L, A>, f: (l: L) => M): FluentHKT2<F, I, M, A> {
-    return this.bimap(f, identity)
+  mapLeft<I extends Bifunctor<F>, L, M>(this: { I: I; value: HKT2<F, L, A> }, f: (l: L) => M): Fluent<F, I, A> {
+    return new Fluent<F, I, A>(this.I, this.I.bimap(this.value, f, identity))
   }
   promap<I extends Profunctor<F>, A, B, C, D>(
-    this: FluentHKT2<F, I, B, C>,
+    this: { I: I; value: HKT2<F, B, C> },
     f: (a: A) => B,
     g: (c: C) => D
-  ): FluentHKT2<F, I, A, D> {
-    return new FluentHKT2<F, I, A, D>(this.I, this.I.promap(this.value, f, g))
+  ): Fluent<F, I, D> {
+    return new Fluent<F, I, D>(this.I, this.I.promap(this.value, f, g))
   }
 }
 
+/**
+ * @since 2.0.0
+ */
 export function fluent<F extends URIS2, I, L>(I: { URI: F; _L: L } & I): <A>(fa: Type2<F, L, A>) => Fluent2C<F, I, L, A>
 export function fluent<F extends URIS2, I>(I: { URI: F } & I): <L, A>(fa: Type2<F, L, A>) => Fluent2<F, I, L, A>
 export function fluent<F extends URIS, I>(I: { URI: F } & I): <A>(fa: Type<F, A>) => Fluent1<F, I, A>
-export function fluent<F, I>(I: { URI: F } & I): <L, A>(fa: HKT2<F, L, A>) => FluentHKT2<F, I, L, A>
-export function fluent<F, I>(I: { URI: F } & I): <A>(fa: HKT<F, A>) => FluentHKT<F, I, A>
-export function fluent<F, I>(I: { URI: F } & I): <L, A>(fa: HKT2<F, L, A>) => any {
-  return <L, A>(fa: HKT2<F, L, A>) => new FluentHKT2<F, I, L, A>(I, fa)
+export function fluent<F, I>(
+  I: { URI: F } & I
+): {
+  <L, A>(fa: HKT2<F, L, A>): FluentHKT2<F, I, L, A>
+  <A>(fa: HKT<F, A>): Fluent<F, I, A>
+}
+export function fluent<F, I>(I: { URI: F } & I): <A>(fa: HKT<F, A>) => any {
+  return fa => new Fluent(I, fa)
 }
diff --git a/src/index.ts b/src/index.ts
index 4ce10883..d5393520 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -58,6 +58,8 @@ import * as filterable from './Filterable'
 export { filterable }
 import * as filterableWithIndex from './FilterableWithIndex'
 export { filterableWithIndex }
+import * as fluent from './fluent'
+export { fluent }
 import * as foldable from './Foldable'
 export { foldable }
 import * as foldableWithIndex from './FoldableWithIndex'


Test patch:
diff --git a/test/fluent.ts b/test/fluent.ts
new file mode 100644
index 00000000..2971a1df
--- /dev/null
+++ b/test/fluent.ts
@@ -0,0 +1,256 @@
+import * as assert from 'assert'
+import { array } from '../src/Array'
+import { either, Either, left, right } from '../src/Either'
+import { eqNumber } from '../src/Eq'
+import * as F from '../src/fluent'
+import { IO, io } from '../src/IO'
+import { fold, monoidSum } from '../src/Monoid'
+import { fromNonEmptyArray, nonEmptyArray } from '../src/NonEmptyArray'
+import { getEq, getMonoid, getOrd, getShow, none, option, Option, some, URI } from '../src/Option'
+import { ordNumber } from '../src/Ord'
+import { reader } from '../src/Reader'
+import { semigroupSum } from '../src/Semigroup'
+import { showNumber } from '../src/Show'
+
+const fluent = F.fluent(option)
+
+describe('fluent', () => {
+  it('show', () => {
+    const S = getShow(showNumber)
+    const fluent = F.fluent({ ...S, URI })
+    assert.strictEqual(fluent(some(1)).show(), 'some(1)')
+    assert.strictEqual(fluent(none).show(), 'none')
+  })
+
+  it('equals', () => {
+    const S = getEq(eqNumber)
+    const fluent = F.fluent({ ...S, URI })
+    assert.strictEqual(fluent(some(1)).equals(some(1)), true)
+    assert.strictEqual(fluent(some(1)).equals(some(2)), false)
+    assert.strictEqual(fluent(some(1)).equals(none), false)
+    assert.strictEqual(fluent(none).equals(none), true)
+  })
+
+  it('compare', () => {
+    const O = getOrd(ordNumber)
+    const fluent = F.fluent({ ...O, URI })
+    assert.strictEqual(fluent(some(1)).compare(some(1)), 0)
+    assert.strictEqual(fluent(some(1)).compare(some(2)), -1)
+    assert.strictEqual(fluent(some(2)).compare(some(1)), 1)
+  })
+
+  it('concat', () => {
+    const M = getMonoid(semigroupSum)
+    const fluent = F.fluent({ ...M, URI })
+    assert.deepStrictEqual(fluent(some(1)).concat(some(2)), some(3))
+    assert.deepStrictEqual(fluent(none as Option<number>).concat(some(2)), some(2))
+    assert.deepStrictEqual(fluent(some(1)).concat(none), some(1))
+    assert.deepStrictEqual(fluent(none as Option<number>).concat(none), none)
+  })
+
+  it('map', () => {
+    const double = (n: number): number => n * 2
+    assert.deepStrictEqual(fluent(some(1)).map(double).value, option.map(some(1), double))
+    assert.deepStrictEqual(fluent(none).map(double).value, option.map(none, double))
+  })
+
+  it('mapWithIndex', () => {
+    const fluent = F.fluent(array)
+    assert.deepStrictEqual(fluent([1, 2, 3]).mapWithIndex((i, a) => i + a).value, [1, 3, 5])
+  })
+
+  it('bimap', () => {
+    const fluent = F.fluent(either)
+    const double = (n: number): number => n * 2
+    const len = (s: string): number => s.length
+    assert.deepStrictEqual(fluent(right(1) as Either<string, number>).bimap(len, double).value, right(2))
+    assert.deepStrictEqual(fluent(left('err')).bimap(len, double).value, left(3))
+  })
+
+  it('mapLeft', () => {
+    const fluent = F.fluent(either)
+    const len = (s: string): number => s.length
+    assert.deepStrictEqual(fluent(right(1) as Either<string, number>).mapLeft(len).value, right(1))
+    assert.deepStrictEqual(fluent(left('err')).mapLeft(len).value, left(3))
+  })
+
+  it('ap', () => {
+    const fab = some((s: string): number => s.length)
+    assert.deepStrictEqual(fluent(some('foo')).ap(fab).value, option.ap(fab, some('foo')))
+    assert.deepStrictEqual(fluent(none).ap(fab).value, option.ap(fab, none))
+  })
+
+  it('apFirst', () => {
+    const log: Array<string> = []
+    const append = (message: string): IO<number> => () => log.push(message)
+    const fluent = F.fluent(io)
+    assert.strictEqual(
+      fluent(append('a'))
+        .apFirst(append('b'))
+        .value(),
+      1
+    )
+    assert.deepStrictEqual(log, ['a', 'b'])
+  })
+
+  it('apSecond', () => {
+    const log: Array<string> = []
+    const append = (message: string): IO<number> => () => log.push(message)
+    const fluent = F.fluent(io)
+    assert.strictEqual(
+      fluent(append('a'))
+        .apSecond(append('b'))
+        .value(),
+      2
+    )
+    assert.deepStrictEqual(log, ['a', 'b'])
+  })
+
+  it('chain', () => {
+    const f = (s: string): Option<number> => (s.length > 2 ? some(s.length * 2) : none)
+    assert.deepStrictEqual(fluent(some('aaa')).chain(f).value, option.chain(some('aaa'), f))
+    assert.deepStrictEqual(fluent(some('aa')).chain(f).value, option.chain(some('aa'), f))
+    assert.deepStrictEqual(fluent(none).chain(f).value, option.chain(none, f))
+  })
+
+  it('flatten', () => {
+    assert.deepStrictEqual(fluent(some(some(1))).flatten().value, some(1))
+  })
+
+  it('extend', () => {
+    const fluent = F.fluent(nonEmptyArray)
+    const sum = fold(monoidSum)
+    assert.deepStrictEqual(fluent(fromNonEmptyArray([1, 2, 3, 4])).extend(sum).value, fromNonEmptyArray([10, 9, 7, 4]))
+  })
+
+  it('duplicate', () => {
+    assert.deepStrictEqual(fluent(some(1)).duplicate().value, some(some(1)))
+    assert.deepStrictEqual(fluent(none).duplicate().value, none)
+  })
+
+  it('reduce', () => {
+    assert.strictEqual(
+      fluent(some('aaa')).reduce(1, (acc, a) => acc + a.length),
+      option.reduce(some('aaa'), 1, (acc, a) => acc + a.length)
+    )
+  })
+
+  it('foldMap', () => {
+    assert.strictEqual(
+      fluent(some('aaa')).foldMap(monoidSum)(a => a.length),
+      option.foldMap(monoidSum)(some('aaa'), a => a.length)
+    )
+  })
+
+  it('foldr', () => {
+    assert.strictEqual(
+      fluent(some('aaa')).reduceRight(1, (a, acc) => acc + a.length),
+      option.reduceRight(some('aaa'), 1, (a, acc) => acc + a.length)
+    )
+  })
+
+  it('reduceWithIndex', () => {
+    const fluent = F.fluent(array)
+    assert.strictEqual(
+      fluent([1, 2, 3]).reduceWithIndex(1, (i, acc, a) => i + acc + a),
+      array.reduceWithIndex([1, 2, 3], 1, (i, acc, a) => i + acc + a)
+    )
+  })
+
+  it('foldMapWithIndex', () => {
+    const fluent = F.fluent(array)
+    assert.strictEqual(
+      fluent([1, 2, 3]).foldMapWithIndex(monoidSum)((i, a) => i + a),
+      array.foldMapWithIndex(monoidSum)([1, 2, 3], (i, a) => i + a)
+    )
+  })
+
+  it('foldrWithIndex', () => {
+    const fluent = F.fluent(array)
+    assert.strictEqual(
+      fluent([1, 2, 3]).reduceRightWithIndex(1, (i, a, acc) => i + acc + a),
+      array.reduceRightWithIndex([1, 2, 3], 1, (i, a, acc) => i + acc + a)
+    )
+  })
+
+  it('alt', () => {
+    assert.deepStrictEqual(fluent(some(1)).alt(() => some(2)).value, some(1))
+    assert.deepStrictEqual(fluent(some(1)).alt(() => none).value, some(1))
+    assert.deepStrictEqual(fluent(none as Option<number>).alt(() => some(2)).value, some(2))
+    assert.deepStrictEqual(fluent(none as Option<number>).alt(() => none).value, none)
+  })
+
+  it('compact', () => {
+    const fluent = F.fluent(array)
+    assert.deepStrictEqual(fluent([some(1), none, some(3)]).compact().value, [1, 3])
+  })
+
+  it('separate', () => {
+    const fluent = F.fluent(array)
+    assert.deepStrictEqual(fluent([right(1), left('err'), right(3)]).separate(), { left: ['err'], right: [1, 3] })
+  })
+
+  it('filter', () => {
+    const fluent = F.fluent(array)
+    assert.deepStrictEqual(fluent([1, 2, 3]).filter(n => n > 2).value, [3])
+  })
+
+  it('filterMap', () => {
+    const fluent = F.fluent(array)
+    assert.deepStrictEqual(fluent(['a', 'bb', 'ccc']).filterMap(s => (s.length > 2 ? some(s.length) : none)).value, [3])
+  })
+
+  it('partition', () => {
+    const fluent = F.fluent(array)
+    assert.deepStrictEqual(fluent([1, 2, 3]).partition(n => n > 2), { left: [1, 2], right: [3] })
+  })
+
+  it('partitionMap', () => {
+    const fluent = F.fluent(array)
+    assert.deepStrictEqual(
+      fluent(['a', 'bb', 'ccc']).partitionMap(s => (s.length > 2 ? right(s.length) : left(s.length < 2))),
+      { left: [true, false], right: [3] }
+    )
+  })
+
+  it('filterWithIndex', () => {
+    const fluent = F.fluent(array)
+    assert.deepStrictEqual(fluent(['a', 'bb', 'ccc']).filterWithIndex((i, s) => i + s.length > 2).value, ['bb', 'ccc'])
+  })
+
+  it('filterMapWithIndex', () => {
+    const fluent = F.fluent(array)
+    assert.deepStrictEqual(
+      fluent(['a', 'bb', 'ccc']).filterMapWithIndex((i, s) => (i + s.length > 2 ? some(s.length) : none)).value,
+      [2, 3]
+    )
+  })
+
+  it('partitionWithIndex', () => {
+    const fluent = F.fluent(array)
+    assert.deepStrictEqual(fluent(['a', 'bb', 'ccc']).partitionWithIndex((i, s) => i + s.length > 2), {
+      left: ['a'],
+      right: ['bb', 'ccc']
+    })
+  })
+
+  it('partitionMapWithIndex', () => {
+    const fluent = F.fluent(array)
+    assert.deepStrictEqual(
+      fluent(['a', 'bb', 'ccc']).partitionMapWithIndex((i, s) =>
+        i + s.length > 2 ? right(s.length) : left(s.length < 2)
+      ),
+      { left: [true], right: [2, 3] }
+    )
+  })
+
+  it('promap', () => {
+    const fluent = F.fluent(reader)
+    interface E {
+      count: number
+    }
+    const x = fluent((e: E) => e.count + 1).promap((s: string) => ({ count: s.length }), n => n > 2).value
+    assert.deepStrictEqual(x('aa'), true)
+    assert.deepStrictEqual(x('a'), false)
+  })
+})