Repository: gcanti/fp-ts
Pull request #535 (2018-07-23 15:01:04)

New Feature: add IORef module


Patch:
diff --git a/README.md b/README.md
index 362b1034..1f2be552 100644
--- a/README.md
+++ b/README.md
@@ -74,6 +74,7 @@ The stable version is tested against TypeScript 3.0.1, but should run with TypeS
 - StateT monad transformer
   - [StateIO](examples/StateIO.ts)
   - [StateTaskEither](examples/StateTaskEither.ts)
+- [Reader monad](examples/Reader.ts)
 - ReaderT monad transformer
   - [ReaderIO](examples/ReaderIO.ts)
 - Applicative composition
diff --git a/examples/Reader.ts b/examples/Reader.ts
new file mode 100644
index 00000000..7950960a
--- /dev/null
+++ b/examples/Reader.ts
@@ -0,0 +1,58 @@
+import { reader, ask } from '../src/reader'
+
+// So, what's the Reader monad?
+
+// Here's a shape of some data which will be useful later
+interface Config {
+  name: string
+  age: number
+}
+
+// Here's an arbitrary value we are going to start using in our functions
+const x = 1
+
+// Our Reader monad, note it has not been given the context at this point
+const read = reader.of(x)
+
+// Let's start doing calculations with it!
+const read2 = read
+  .map(x => x * 2) // multiply by 2!
+  .map(y => y + 10) // add 10! (why? why not!)
+  .chain(j => {
+    // Let's use something from the context!
+    // first we 'ask' for it
+    const conf = ask<Config>()
+    // It returns a new Reader monad with the context inside it
+    // we can't just grab the value, but we can map over it and
+    // thus return a new Reader monad
+    // because we are returning a new Monad note this function is chain
+    // instead of map
+    return conf.map(c => j + c.age)
+  })
+  .chain(age => {
+    // let's grab it again and use the name
+    const conf = ask<Config>()
+    // and combine the name with the age
+    return conf.map(c => `Hello ${c.name} you are now ${age} years old isn't that just lovely?`)
+  })
+
+// what is read2 then?
+console.log(read2)
+
+// -> Reader { run: [Function] }
+// a Reader monad!
+// but what can we do with it?
+
+// but what about if we have some context to put in it?
+const config: Config = {
+  name: 'horse',
+  age: 100
+}
+
+// let's run our Reader with the stuff in it...
+const output = read2.run(config)
+
+console.log(output)
+// -> "Hello horse you are now 112 years old isn't that just lovely?"
+
+// So what is the Reader monad? Dependency injection!
diff --git a/src/IORef.ts b/src/IORef.ts
new file mode 100644
index 00000000..05a733e9
--- /dev/null
+++ b/src/IORef.ts
@@ -0,0 +1,49 @@
+import { IO } from './IO'
+
+/**
+ * Mutable references in the `IO` monad
+ *
+ * @example
+ * import { newIORef } from 'fp-ts/lib/IORef'
+ *
+ * assert.strictEqual(
+ *   newIORef(1)
+ *     .chain(ref => ref.write(2).chain(() => ref.read))
+ *     .run(),
+ *   2
+ * )
+ *
+ * @data
+ * @constructor IORef
+ * @since 1.8.0
+ */
+export class IORef<A> {
+  read: IO<A>
+  constructor(private value: A) {
+    this.read = new IO(() => this.value)
+  }
+  /**
+   * @since 1.8.0
+   */
+  write(a: A): IO<void> {
+    return new IO(() => {
+      this.value = a
+    })
+  }
+  /**
+   * @since 1.8.0
+   */
+  modify(f: (a: A) => A): IO<void> {
+    return new IO(() => {
+      this.value = f(this.value)
+    })
+  }
+}
+
+/**
+ * @function
+ * @since 1.8.0
+ */
+export const newIORef = <A>(a: A): IO<IORef<A>> => {
+  return new IO(() => new IORef(a))
+}
diff --git a/src/index.ts b/src/index.ts
index bc295d7f..ebdce352 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -68,6 +68,8 @@ import * as io from './IO'
 export { io }
 import * as ioEither from './IOEither'
 export { ioEither }
+import * as ioRef from './IORef'
+export { ioRef }
 import * as ixIO from './IxIO'
 export { ixIO }
 import * as ixMonad from './IxMonad'


Test patch:
diff --git a/test/IORef.ts b/test/IORef.ts
new file mode 100644
index 00000000..50870bfd
--- /dev/null
+++ b/test/IORef.ts
@@ -0,0 +1,41 @@
+import * as assert from 'assert'
+import { IORef, newIORef } from '../src/IORef'
+
+describe('IORef', () => {
+  it('read', () => {
+    const ref = new IORef(1)
+    assert.strictEqual(ref.read.run(), 1)
+  })
+
+  it('write', () => {
+    const ref = new IORef(1)
+    assert.strictEqual(
+      ref
+        .write(2)
+        .chain(() => ref.read)
+        .run(),
+      2
+    )
+  })
+
+  it('modify', () => {
+    const double = (n: number): number => n * 2
+    const ref = new IORef(1)
+    assert.strictEqual(
+      ref
+        .modify(double)
+        .chain(() => ref.read)
+        .run(),
+      2
+    )
+  })
+
+  it('newIORef', () => {
+    assert.strictEqual(
+      newIORef(1)
+        .chain(ref => ref.read)
+        .run(),
+      1
+    )
+  })
+})
diff --git a/test/index.ts b/test/index.ts
index 522d5bb3..a686d82a 100644
--- a/test/index.ts
+++ b/test/index.ts
@@ -8,6 +8,9 @@ const getExportName = (name: string): string => {
   if (name === 'IOEither') {
     return 'ioEither'
   }
+  if (name === 'IORef') {
+    return 'ioRef'
+  }
   if (name === 'TaskEither') {
     return 'taskEither'
   }