Repository: gcanti/fp-ts
Pull request #442 (2018-05-18 07:57:35)

 New Feature: add TaskEither.fromIOEither


Patch:
diff --git a/src/IOEither.ts b/src/IOEither.ts
index 874a9280..65680d3d 100644
--- a/src/IOEither.ts
+++ b/src/IOEither.ts
@@ -172,7 +172,7 @@ export const tryCatch = <A>(f: Lazy<A>, onerror: (reason: {}) => Error = toError
 }
 
 /**
- * @function
+ * @instance
  * @since 1.6.0
  */
 export const ioEither: Monad2<URI> & Bifunctor2<URI> & Alt2<URI> = {
diff --git a/src/NonEmptyArray.ts b/src/NonEmptyArray.ts
index 0f9ebc64..f538a02e 100644
--- a/src/NonEmptyArray.ts
+++ b/src/NonEmptyArray.ts
@@ -1,5 +1,5 @@
 import { Applicative } from './Applicative'
-import { array, last } from './Array'
+import { array, last, sort } from './Array'
 import { Comonad1 } from './Comonad'
 import { Foldable1 } from './Foldable'
 import { HKT } from './HKT'
@@ -21,6 +21,7 @@ export const URI = 'NonEmptyArray'
 export type URI = typeof URI
 
 /**
+ * Data structure which represents non-empty arrays
  * @data
  * @constructor NonEmptyArray
  * @since 1.0.0
@@ -29,39 +30,152 @@ export class NonEmptyArray<A> {
   readonly _A!: A
   readonly _URI!: URI
   constructor(readonly head: A, readonly tail: Array<A>) {}
+
+  /**
+   * Converts this {@link NonEmptyArray} to plain {@link Array}
+   * @since 1.0.0
+   * @example
+   * assert.deepEqual(new NonEmptyArray(1, [2, 3]), [1, 2, 3])
+   * @returns {Array<A>} foo
+   */
   toArray(): Array<A> {
     return uncurriedConcat([this.head], this.tail)
   }
+
+  /**
+   * Concatenates this {@link NonEmptyArray} and passed {@link Array}
+   * @since 1.0.0
+   * @param {Array<A>} as - {@link Array}
+   * @example
+   * assert.deepEqual(new NonEmptyArray(1, []).concatArray([2]), new NonEmptyArray(1, [2]))
+   * @returns {NonEmptyArray<A>}
+   */
   concatArray(as: Array<A>): NonEmptyArray<A> {
     return new NonEmptyArray(this.head, uncurriedConcat(this.tail, as))
   }
+
+  /**
+   * Instance-bound implementation of {@link Functor}
+   * @since 1.0.0
+   * @param {(a: A) => B} f
+   * @example
+   * const double = (n: number): number => n * 2
+   * assert.deepEqual(new NonEmptyArray(1, [2]).map(double), new NonEmptyArray(2, [4]))
+   * @returns {NonEmptyArray<B>}
+   */
   map<B>(f: (a: A) => B): NonEmptyArray<B> {
     return new NonEmptyArray(f(this.head), this.tail.map(f))
   }
+
+  /**
+   * Instance-bound implementation of {@link Apply}
+   * @since 1.0.0
+   * @param {NonEmptyArray<(a: A) => B>} fab
+   * @example
+   * const x = new NonEmptyArray(1, [2])
+   * const double = (n: number) => n * 2
+   * assert.deepEqual(x.ap(new NonEmptyArray(double, [double])).toArray(), [2, 4, 2, 4])
+   * @returns {NonEmptyArray<B>}
+   */
   ap<B>(fab: NonEmptyArray<(a: A) => B>): NonEmptyArray<B> {
     return fab.chain(f => this.map(f)) // <= derived
   }
+
+  /**
+   * Same as {@link ap} but works on {@link NonEmptyArray} of functions and accepts {@link NonEmptyArray} of values instead
+   * @since 1.0.0
+   * @this {NonEmptyArray<(b: B) => C>}
+   * @param {NonEmptyArray<B>} fb
+   * @example
+   * const x = new NonEmptyArray(1, [2])
+   * const double = (n: number) => n * 2
+   * assert.deepEqual(new NonEmptyArray(double, [double]).ap_(x).toArray(), [2, 4, 2, 4])
+   * @returns {NonEmptyArray<C>}
+   */
   ap_<B, C>(this: NonEmptyArray<(b: B) => C>, fb: NonEmptyArray<B>): NonEmptyArray<C> {
     return fb.ap(this)
   }
+
+  /**
+   * Instance-bound implementation of {@link Chain}
+   * @since 1.0.0
+   * @param {(a: A) => NonEmptyArray<B>} f
+   * @example
+   * const x = new NonEmptyArray(1, [2])
+   * const f = (a: number) => new NonEmptyArray(a, [4])
+   * assert.deepEqual(x.chain(f).toArray(), [1, 4, 2, 4])
+   * @returns {NonEmptyArray<B>}
+   */
   chain<B>(f: (a: A) => NonEmptyArray<B>): NonEmptyArray<B> {
     return f(this.head).concatArray(array.chain(this.tail, a => f(a).toArray()))
   }
+
+  /**
+   * Instance-bound implementation of {@link Semigroup}
+   * @since 1.0.0
+   * @param {NonEmptyArray<A>} y
+   * @example
+   * const x = new NonEmptyArray(1, [2])
+   * const y = new NonEmptyArray(3, [4])
+   * assert.deepEqual(x.concat(y).toArray(), [1, 2, 3, 4])
+   * @returns {NonEmptyArray<A>}
+   */
   concat(y: NonEmptyArray<A>): NonEmptyArray<A> {
     return this.concatArray(y.toArray())
   }
+
+  /**
+   * Instance-bound implementation of {@link Foldable}
+   * @since 1.0.0
+   * @param {B} b
+   * @param {(b: B, a: A) => B} f
+   * @example
+   * const x = new NonEmptyArray('a', ['b'])
+   * assert.strictEqual(x.reduce('', (b, a) => b + a), 'ab')
+   * @returns {B}
+   */
   reduce<B>(b: B, f: (b: B, a: A) => B): B {
     return array.reduce(this.toArray(), b, f)
   }
+
+  /**
+   * Instance-bound implementation of {@link Extend}
+   * @since 1.0.0
+   * @param {(fa: NonEmptyArray<A>) => B} f
+   * @example
+   * const sum = (as: NonEmptyArray<number>) => fold(monoidSum)(as.toArray())
+   * assert.deepEqual(new NonEmptyArray(1, [2, 3, 4]).extend(sum), new NonEmptyArray(10, [9, 7, 4]))
+   * @returns {NonEmptyArray<B>}
+   */
   extend<B>(f: (fa: NonEmptyArray<A>) => B): NonEmptyArray<B> {
     return unsafeFromArray(array.extend(this.toArray(), as => f(unsafeFromArray(as))))
   }
+
+  /**
+   * Instance-bound implementation of {@link Comonad}
+   * @since 1.0.0
+   * @example
+   * assert.strictEqual(new NonEmptyArray(1, [2, 3]).extract(), 1)
+   * @returns {A}
+   */
   extract(): A {
     return this.head
   }
+
+  /**
+   * Same as {@link toString}
+   * @since 1.0.0
+   * @returns {string}
+   */
   inspect(): string {
     return this.toString()
   }
+
+  /**
+   * Return stringified representation of this {@link NonEmptyArray}
+   * @since 1.0.0
+   * @returns {string}
+   */
   toString(): string {
     return `new NonEmptyArray(${toString(this.head)}, ${toString(this.tail)})`
   }
@@ -101,6 +215,20 @@ export class NonEmptyArray<A> {
   last(): A {
     return last(this.tail).getOrElse(this.head)
   }
+
+  /**
+   * Sorts this {@link NonEmptyArray} using specified {@link Ord} instance
+   * @since 1.6.0
+   * @param {Ord<A>} ord - {@link Ord} instance
+   * @example
+   * const result = new NonEmptyArray(3, [2, 1]).sort(ordNumber)
+   * const expected = new NonEmptyArray(1, [2, 3])
+   * assert.deepEqual(result, expected)
+   * @returns {NonEmptyArray<A>}
+   */
+  sort(ord: Ord<A>): NonEmptyArray<A> {
+    return unsafeFromArray(sort(ord)(this.toArray()))
+  }
 }
 
 const unsafeFromArray = <A>(as: Array<A>): NonEmptyArray<A> => {
@@ -108,8 +236,11 @@ const unsafeFromArray = <A>(as: Array<A>): NonEmptyArray<A> => {
 }
 
 /**
+ * Builds {@link NonEmptyArray} from {@link Array} returning {@link none} or {@link some} depending on amount of values in passed array
  * @function
  * @since 1.0.0
+ * @param {Array<A>} as
+ * @returns {Option<NonEmptyArray<A>>}
  */
 export const fromArray = <A>(as: Array<A>): Option<NonEmptyArray<A>> => {
   return as.length > 0 ? some(unsafeFromArray(as)) : none
@@ -136,8 +267,10 @@ const concat = <A>(fx: NonEmptyArray<A>, fy: NonEmptyArray<A>): NonEmptyArray<A>
 }
 
 /**
+ * Builds {@link Semigroup} instance for {@link NonEmptyArray} of specified type arument
  * @function
  * @since 1.0.0
+ * @returns {Semigroup<NonEmptyArray<A>>}
  */
 export const getSemigroup = <A = never>(): Semigroup<NonEmptyArray<A>> => {
   return { concat }
diff --git a/src/TaskEither.ts b/src/TaskEither.ts
index 9c6dc2f6..2a2d412f 100644
--- a/src/TaskEither.ts
+++ b/src/TaskEither.ts
@@ -6,6 +6,7 @@ import { IO } from './IO'
 import { Monad2 } from './Monad'
 import { Task, fromIO as taskFromIO, task, tryCatch as taskTryCatch } from './Task'
 import { Lazy, constIdentity } from './function'
+import { IOEither } from './IOEither'
 
 declare module './HKT' {
   interface URI2HKT2<L, A> {
@@ -147,6 +148,14 @@ export const fromLeft = <L, A>(l: L): TaskEither<L, A> => {
   return fromEither(eitherLeft(l))
 }
 
+/**
+ * @function
+ * @since 1.6.0
+ */
+export const fromIOEither = <L, A>(fa: IOEither<L, A>): TaskEither<L, A> => {
+  return new TaskEither(taskFromIO(fa.value))
+}
+
 /**
  * @function
  * @since 1.0.0
diff --git a/src/index.ts b/src/index.ts
index 6d746e7b..09565804 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -66,16 +66,18 @@ import * as invariant from './Invariant'
 export { invariant }
 import * as io from './IO'
 export { io }
-import * as ixIo from './IxIO'
-export { ixIo }
+import * as ioEither from './IOEither'
+export { ioEither }
+import * as ixIO from './IxIO'
+export { ixIO }
 import * as ixMonad from './IxMonad'
 export { ixMonad }
-import * as joinSemiLattice from './JoinSemilattice'
-export { joinSemiLattice }
+import * as joinSemilattice from './JoinSemilattice'
+export { joinSemilattice }
 import * as lattice from './Lattice'
 export { lattice }
-import * as meetSemiLattice from './MeetSemilattice'
-export { meetSemiLattice }
+import * as meetSemilattice from './MeetSemilattice'
+export { meetSemilattice }
 import * as monad from './Monad'
 export { monad }
 import * as monoid from './Monoid'


Test patch:
diff --git a/test/Identity.ts b/test/Identity.ts
index d73f30f0..d3ecbfed 100644
--- a/test/Identity.ts
+++ b/test/Identity.ts
@@ -1,7 +1,95 @@
 import * as assert from 'assert'
-import { Identity } from '../src/Identity'
+import { Identity, identity, getSetoid } from '../src/Identity'
+import { setoidNumber } from '../src/Setoid'
+import { right, left, either } from '../src/Either'
+import { sequence } from '../src/Traversable'
 
 describe('Identity', () => {
+  it('map', () => {
+    const double = (n: number): number => n * 2
+    const x = new Identity(1)
+    const expected = new Identity(2)
+    assert.deepEqual(x.map(double), expected)
+    assert.deepEqual(identity.map(x, double), expected)
+  })
+
+  it('ap', () => {
+    const double = (n: number): number => n * 2
+    const fab = identity.of(double)
+    const fa = new Identity(1)
+    const expected = new Identity(2)
+    assert.deepEqual(fa.ap(fab), expected)
+    assert.deepEqual(fab.ap_(fa), expected)
+    assert.deepEqual(identity.ap(fab, fa), expected)
+  })
+
+  it('chain', () => {
+    const f = (n: number) => identity.of(n * 2)
+    const x = new Identity(1)
+    const expected = new Identity(2)
+    assert.deepEqual(x.chain(f), expected)
+    assert.deepEqual(identity.chain(x, f), expected)
+  })
+
+  it('reduce', () => {
+    const x = new Identity('b')
+    const expected = 'ab'
+    assert.deepEqual(x.reduce('a', (b, a) => b + a), expected)
+    assert.deepEqual(identity.reduce(x, 'a', (b, a) => b + a), expected)
+  })
+
+  it('alt', () => {
+    const x = new Identity(1)
+    const y = new Identity(2)
+    assert.strictEqual(x.alt(y), x)
+    assert.strictEqual(identity.alt(x, y), x)
+  })
+
+  it('extract', () => {
+    const x = new Identity(1)
+    assert.strictEqual(x.extract(), 1)
+    assert.strictEqual(identity.extract(x), 1)
+  })
+
+  it('extend', () => {
+    const f = (fa: Identity<string>): number => fa.value.length
+    const x = new Identity('foo')
+    const expected = new Identity(3)
+    assert.deepEqual(x.extend(f), expected)
+    assert.deepEqual(identity.extend(x, f), expected)
+  })
+
+  it('fold', () => {
+    const len = (a: string): number => a.length
+    const x = new Identity('foo')
+    assert.strictEqual(x.fold(len), 3)
+  })
+
+  it('toString', () => {
+    const x = new Identity(1)
+    assert.strictEqual(x.toString(), 'new Identity(1)')
+    assert.strictEqual(x.inspect(), 'new Identity(1)')
+  })
+
+  it('getSetoid', () => {
+    const S = getSetoid(setoidNumber)
+    assert.strictEqual(S.equals(new Identity(1), new Identity(1)), true)
+    assert.strictEqual(S.equals(new Identity(1), new Identity(2)), false)
+    assert.strictEqual(S.equals(new Identity(2), new Identity(1)), false)
+  })
+
+  it('ChainRec', () => {
+    const x = identity.chainRec<number, number>(0, a => new Identity(a < 10 ? left(a + 1) : right(a)))
+    const expected = new Identity(10)
+    assert.deepEqual(x, expected)
+  })
+
+  it('traverse', () => {
+    const x = sequence(either, identity)(new Identity(right(1)))
+    const expected = right(new Identity(1))
+    assert.deepEqual(x, expected)
+  })
+
   it('orElse', () => {
     assert.deepEqual(new Identity(123).orElse(() => new Identity(456)), new Identity(123))
   })
diff --git a/test/NonEmptyArray.ts b/test/NonEmptyArray.ts
index 7d54311a..5ff079a8 100644
--- a/test/NonEmptyArray.ts
+++ b/test/NonEmptyArray.ts
@@ -98,4 +98,8 @@ describe('NonEmptyArray', () => {
     assert.deepEqual(new NonEmptyArray(1, [2, 3]).last(), 3)
     assert.deepEqual(new NonEmptyArray(1, []).last(), 1)
   })
+
+  it('sort', () => {
+    assert.deepEqual(new NonEmptyArray(3, [2, 1]).sort(ordNumber), new NonEmptyArray(1, [2, 3]))
+  })
 })
diff --git a/test/TaskEither.ts b/test/TaskEither.ts
index a354b1af..2203f9d3 100644
--- a/test/TaskEither.ts
+++ b/test/TaskEither.ts
@@ -2,7 +2,18 @@ import * as assert from 'assert'
 import { left as eitherLeft, right as eitherRight } from '../src/Either'
 import { IO } from '../src/IO'
 import { Task, task } from '../src/Task'
-import { TaskEither, fromIO, fromLeft, left, right, taskEither, taskify, tryCatch } from '../src/TaskEither'
+import {
+  TaskEither,
+  fromIO,
+  fromLeft,
+  left,
+  right,
+  taskEither,
+  taskify,
+  tryCatch,
+  fromIOEither
+} from '../src/TaskEither'
+import { IOEither } from '../src/IOEither'
 
 describe('TaskEither', () => {
   it('ap', () => {
@@ -159,4 +170,13 @@ describe('TaskEither', () => {
       assert.deepEqual(e4, e5)
     })
   })
+
+  it('fromIOEither', () => {
+    const x1 = fromIOEither(new IOEither(new IO(() => eitherRight(1))))
+    const x2 = fromIOEither(new IOEither(new IO(() => eitherLeft('foo'))))
+    return Promise.all([x1.run(), x2.run()]).then(([e1, e2]) => {
+      assert.deepEqual(e1, eitherRight(1))
+      assert.deepEqual(e2, eitherLeft('foo'))
+    })
+  })
 })
diff --git a/test/index.ts b/test/index.ts
new file mode 100644
index 00000000..f19e1370
--- /dev/null
+++ b/test/index.ts
@@ -0,0 +1,35 @@
+import * as assert from 'assert'
+import { getModuleNames } from '../docs/api/fs'
+
+const getExportName = (name: string): string => {
+  if (name === 'HKT' || name === 'IO') {
+    return name.toLowerCase()
+  }
+  if (name === 'IOEither') {
+    return 'ioEither'
+  }
+  if (name === 'TaskEither') {
+    return 'taskEither'
+  }
+  if (name === 'StrMap') {
+    return 'strmap'
+  }
+  return name.substring(0, 1).toLowerCase() + name.substring(1)
+}
+
+describe('index', () => {
+  it('check exported modules', () => {
+    const moduleNames = getModuleNames()
+    const fp = require('../src')
+    moduleNames.forEach(name => {
+      if (name !== 'index') {
+        const exportName = getExportName(name)
+        assert.strictEqual(
+          fp[exportName] !== undefined,
+          true,
+          `The "${name}" module is not exported in src/index.ts as ${exportName}`
+        )
+      }
+    })
+  })
+})