Repository: gcanti/fp-ts
Pull request #450 (2018-05-22 08:08:26)

Refactor ReaderTaskEither
- change `run`
- add `applyFirst`, `applySecond` methods
- remove `fromTask` in favour of `right`
- add `fromIO`, `fromLeft`, `fromIOEither` lifting functions


Patch:
diff --git a/src/ReaderTaskEither.ts b/src/ReaderTaskEither.ts
index 701c68b8..c757e273 100644
--- a/src/ReaderTaskEither.ts
+++ b/src/ReaderTaskEither.ts
@@ -1,4 +1,7 @@
 import { Either } from './Either'
+import { constant, constIdentity } from './function'
+import { IO } from './IO'
+import { IOEither } from './IOEither'
 import { Monad3 } from './Monad'
 import { Reader } from './Reader'
 import * as readerT from './ReaderT'
@@ -21,25 +24,29 @@ export type URI = typeof URI
 /**
  * @data
  * @constructor ReaderTaskEither
- * @since 1.0.0
+ * @since 1.6.0
  */
 export class ReaderTaskEither<E, L, A> {
   readonly _A!: A
   readonly _L!: L
   readonly _U!: E
   readonly _URI!: URI
-  constructor(readonly run: (e: E) => TaskEither<L, A>) {}
+  constructor(readonly value: (e: E) => TaskEither<L, A>) {}
+  /** Runs the inner `TaskEither` */
+  run(e: E): Promise<Either<L, A>> {
+    return this.value(e).run()
+  }
   /**
    * @since 1.6.0
    */
   map<B>(f: (a: A) => B): ReaderTaskEither<E, L, B> {
-    return new ReaderTaskEither(readerTTaskEither.map(f, this.run))
+    return new ReaderTaskEither(readerTTaskEither.map(f, this.value))
   }
   /**
    * @since 1.6.0
    */
   ap<B>(fab: ReaderTaskEither<E, L, (a: A) => B>): ReaderTaskEither<E, L, B> {
-    return new ReaderTaskEither(readerTTaskEither.ap(fab.run, this.run))
+    return new ReaderTaskEither(readerTTaskEither.ap(fab.value, this.value))
   }
   /**
    * @since 1.6.0
@@ -47,11 +54,25 @@ export class ReaderTaskEither<E, L, A> {
   ap_<B, C>(this: ReaderTaskEither<E, L, (b: B) => C>, fb: ReaderTaskEither<E, L, B>): ReaderTaskEither<E, L, C> {
     return fb.ap(this)
   }
+  /**
+   * Combine two effectful actions, keeping only the result of the first
+   * @since 1.6.0
+   */
+  applyFirst<B>(fb: ReaderTaskEither<E, L, B>): ReaderTaskEither<E, L, A> {
+    return fb.ap(this.map(constant))
+  }
+  /**
+   * Combine two effectful actions, keeping only the result of the second
+   * @since 1.6.0
+   */
+  applySecond<B>(fb: ReaderTaskEither<E, L, B>): ReaderTaskEither<E, L, B> {
+    return fb.ap(this.map(constIdentity as () => (b: B) => B))
+  }
   /**
    * @since 1.6.0
    */
   chain<B>(f: (a: A) => ReaderTaskEither<E, L, B>): ReaderTaskEither<E, L, B> {
-    return new ReaderTaskEither(readerTTaskEither.chain(a => f(a).run, this.run))
+    return new ReaderTaskEither(readerTTaskEither.chain(a => f(a).value, this.value))
   }
 }
 
@@ -100,7 +121,7 @@ export const asks = <E, L, A>(f: (e: E) => A): ReaderTaskEither<E, L, A> => {
  * @since 1.6.0
  */
 export const local = <E>(f: (e: E) => E) => <L, A>(fa: ReaderTaskEither<E, L, A>): ReaderTaskEither<E, L, A> => {
-  return new ReaderTaskEither(e => fa.run(f(e)))
+  return new ReaderTaskEither(e => fa.value(f(e)))
 }
 
 /**
@@ -119,14 +140,6 @@ export const left = <E, L, A>(fa: Task<L>): ReaderTaskEither<E, L, A> => {
   return new ReaderTaskEither(() => taskEither.left(fa))
 }
 
-/**
- * @function
- * @since 1.6.0
- */
-export const fromTask = <E, L, A>(fa: Task<A>): ReaderTaskEither<E, L, A> => {
-  return fromTaskEither(taskEither.right(fa))
-}
-
 /**
  * @function
  * @since 1.6.0
@@ -152,6 +165,30 @@ export const fromEither = <E, L, A>(fa: Either<L, A>): ReaderTaskEither<E, L, A>
   return fromTaskEither(taskEither.fromEither(fa))
 }
 
+/**
+ * @function
+ * @since 1.6.0
+ */
+export const fromIO = <E, L, A>(fa: IO<A>): ReaderTaskEither<E, L, A> => {
+  return fromTaskEither(taskEither.fromIO(fa))
+}
+
+/**
+ * @function
+ * @since 1.6.0
+ */
+export const fromLeft = <E, L, A>(l: L): ReaderTaskEither<E, L, A> => {
+  return fromTaskEither(taskEither.fromLeft(l))
+}
+
+/**
+ * @function
+ * @since 1.6.0
+ */
+export const fromIOEither = <E, L, A>(fa: IOEither<L, A>): ReaderTaskEither<E, L, A> => {
+  return fromTaskEither(taskEither.fromIOEither(fa))
+}
+
 /**
  * @function
  * @since 1.6.0
diff --git a/src/TaskEither.ts b/src/TaskEither.ts
index 2f554123..82f72560 100644
--- a/src/TaskEither.ts
+++ b/src/TaskEither.ts
@@ -34,7 +34,7 @@ export class TaskEither<L, A> {
   readonly _L!: L
   readonly _URI!: URI
   constructor(readonly value: Task<Either<L, A>>) {}
-  /** Runs the inner task */
+  /** Runs the inner `Task` */
   run(): Promise<Either<L, A>> {
     return this.value.run()
   }


Test patch:
diff --git a/test/ReaderTaskEither.ts b/test/ReaderTaskEither.ts
index 4f74c4a4..5c87e01a 100644
--- a/test/ReaderTaskEither.ts
+++ b/test/ReaderTaskEither.ts
@@ -4,43 +4,36 @@ import {
   fromTaskEither,
   left,
   right,
-  fromTask,
   fromEither,
   fromReader,
   tryCatch,
   ask,
   asks,
   local,
-  ReaderTaskEither
+  ReaderTaskEither,
+  fromLeft,
+  fromIO,
+  fromIOEither
 } from '../src/ReaderTaskEither'
 import { left as eitherLeft, right as eitherRight, either } from '../src/Either'
-import { fromLeft, taskEither } from '../src/TaskEither'
-import { task } from '../src/Task'
+import { taskEither } from '../src/TaskEither'
+import { task, Task } from '../src/Task'
 import { reader } from '../src/Reader'
+import { IO } from '../src/IO'
+import { IOEither } from '../src/IOEither'
 
 describe('ReaderTaskEither', () => {
   it('ap', () => {
     const double = (n: number): number => n * 2
     const fab = readerTaskEither.of(double)
     const fa = readerTaskEither.of(1)
-    return Promise.all([
-      fa
-        .ap(fab)
-        .run({})
-        .run(),
-      fab
-        .ap_(fa)
-        .run({})
-        .run(),
-      readerTaskEither
-        .ap(fab, fa)
-        .run({})
-        .run()
-    ]).then(([e1, e2, e3]) => {
-      assert.deepEqual(e1, eitherRight(2))
-      assert.deepEqual(e1, e2)
-      assert.deepEqual(e1, e3)
-    })
+    return Promise.all([fa.ap(fab).run({}), fab.ap_(fa).run({}), readerTaskEither.ap(fab, fa).run({})]).then(
+      ([e1, e2, e3]) => {
+        assert.deepEqual(e1, eitherRight(2))
+        assert.deepEqual(e1, e2)
+        assert.deepEqual(e1, e3)
+      }
+    )
   })
 
   it('map', () => {
@@ -48,7 +41,6 @@ describe('ReaderTaskEither', () => {
     return readerTaskEither
       .map(readerTaskEither.of(1), double)
       .run({})
-      .run()
       .then(e => {
         assert.deepEqual(e, eitherRight(2))
       })
@@ -57,19 +49,13 @@ describe('ReaderTaskEither', () => {
   it('chain', () => {
     const rte1 = readerTaskEither.chain(
       readerTaskEither.of<{}, string, string>('foo'),
-      a =>
-        a.length > 2
-          ? readerTaskEither.of<{}, string, number>(a.length)
-          : fromTaskEither(fromLeft<string, number>('foo'))
+      a => (a.length > 2 ? readerTaskEither.of<{}, string, number>(a.length) : fromLeft<{}, string, number>('foo'))
     )
     const rte2 = readerTaskEither.chain(
       readerTaskEither.of<{}, string, string>('a'),
-      a =>
-        a.length > 2
-          ? readerTaskEither.of<{}, string, number>(a.length)
-          : fromTaskEither(fromLeft<string, number>('foo'))
+      a => (a.length > 2 ? readerTaskEither.of<{}, string, number>(a.length) : fromLeft<{}, string, number>('foo'))
     )
-    return Promise.all([rte1.run({}).run(), rte2.run({}).run()]).then(([e1, e2]) => {
+    return Promise.all([rte1.run({}), rte2.run({})]).then(([e1, e2]) => {
       assert.deepEqual(e1, eitherRight(3))
       assert.deepEqual(e2, eitherLeft('foo'))
     })
@@ -79,26 +65,17 @@ describe('ReaderTaskEither', () => {
     return readerTaskEither
       .of(1)
       .run({})
-      .run()
       .then(e => assert.deepEqual(e, eitherRight(1)))
   })
 
   it('ask', () => {
     const x = ask<number, {}>()
-
-    return x
-      .run(1)
-      .run()
-      .then(e => assert.deepEqual(e, eitherRight(1)))
+    return x.run(1).then(e => assert.deepEqual(e, eitherRight(1)))
   })
 
   it('asks', () => {
     const x = asks((s: string) => s.length)
-
-    return x
-      .run('foo')
-      .run()
-      .then(e => assert.deepEqual(e, eitherRight(3)))
+    return x.run('foo').then(e => assert.deepEqual(e, eitherRight(3)))
   })
 
   it('local', () => {
@@ -107,14 +84,12 @@ describe('ReaderTaskEither', () => {
 
     return doubleLocal(new ReaderTaskEither(taskEither.of))
       .run(1)
-      .run()
       .then(e => assert.deepEqual(e, eitherRight(2)))
   })
 
   it('left', () => {
     return left(task.of(1))
       .run({})
-      .run()
       .then(e => {
         assert.deepEqual(e, eitherLeft(1))
       })
@@ -123,7 +98,6 @@ describe('ReaderTaskEither', () => {
   it('right', () => {
     return right(task.of(1))
       .run({})
-      .run()
       .then(e => {
         assert.deepEqual(e, eitherRight(1))
       })
@@ -131,50 +105,74 @@ describe('ReaderTaskEither', () => {
 
   it('fromEither', () => {
     const fa = fromEither(either.of(1))
-    return fa
-      .run({})
-      .run()
-      .then(e => {
-        assert.deepEqual(e, eitherRight(1))
-      })
+    return fa.run({}).then(e => {
+      assert.deepEqual(e, eitherRight(1))
+    })
   })
 
   it('fromReader', () => {
     const fa = fromReader(reader.of(1))
-    return fa
-      .run({})
-      .run()
-      .then(e => {
-        assert.deepEqual(e, eitherRight(1))
-      })
-  })
-
-  it('fromTask', () => {
-    const fa = fromTask(task.of(1))
-    return fa
-      .run({})
-      .run()
-      .then(e => {
-        assert.deepEqual(e, eitherRight(1))
-      })
+    return fa.run({}).then(e => {
+      assert.deepEqual(e, eitherRight(1))
+    })
   })
 
   it('fromTaskEither', () => {
     const fa = fromTaskEither(taskEither.of(1))
-    return fa
-      .run({})
-      .run()
-      .then(e => {
-        assert.deepEqual(e, eitherRight(1))
-      })
+    return fa.run({}).then(e => {
+      assert.deepEqual(e, eitherRight(1))
+    })
   })
 
   it('tryCatch', () => {
     const ok = tryCatch(() => Promise.resolve(1), () => 'error')
     const ko = tryCatch(() => Promise.reject(undefined), () => 'error')
-    return Promise.all([ok.run({}).run(), ko.run({}).run()]).then(([eok, eko]) => {
+    return Promise.all([ok.run({}), ko.run({})]).then(([eok, eko]) => {
       assert.deepEqual(eok, eitherRight(1))
       assert.deepEqual(eko, eitherLeft('error'))
     })
   })
+
+  it('fromIO', () => {
+    const io = new IO(() => 1)
+    const fa = fromIO(io)
+    return fa.run({}).then(e => {
+      assert.deepEqual(e, eitherRight(1))
+    })
+  })
+
+  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'))
+    })
+  })
+
+  it('applyFirst', () => {
+    const log: Array<string> = []
+    const append = (message: string): ReaderTaskEither<{}, string, number> =>
+      right(new Task(() => Promise.resolve(log.push(message))))
+    return append('a')
+      .applyFirst(append('b'))
+      .run({})
+      .then(e => {
+        assert.deepEqual(e, eitherRight(1))
+        assert.deepEqual(log, ['a', 'b'])
+      })
+  })
+
+  it('applySecond', () => {
+    const log: Array<string> = []
+    const append = (message: string): ReaderTaskEither<{}, string, number> =>
+      right(new Task(() => Promise.resolve(log.push(message))))
+    return append('a')
+      .applySecond(append('b'))
+      .run({})
+      .then(e => {
+        assert.deepEqual(e, eitherRight(2))
+        assert.deepEqual(log, ['a', 'b'])
+      })
+  })
 })