Repository: gcanti/fp-ts
Pull request #557 (2018-09-06 16:23:15)

New Feature: add increment and decrement functions


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index d084f6e0..891b07b5 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -14,6 +14,11 @@
 **Note**: Gaps between patch versions are faulty/broken releases. **Note**: A feature tagged as Experimental is in a
 high state of flux, you're at risk of it changing without notice.
 
+# 1.8.1
+
+- **Bug Fix**
+  - add module augmentation to `Free`, https://github.com/gcanti/fp-ts/pull/559 (@gcanti)
+
 # 1.8.0
 
 - **New Feature**
diff --git a/README.md b/README.md
index 1f2be552..1b679828 100644
--- a/README.md
+++ b/README.md
@@ -57,6 +57,7 @@ The stable version is tested against TypeScript 3.0.1, but should run with TypeS
 # Documentation
 
 - [API Reference](docs/index.md)
+- [`fp-ts` for `PureScript`ers / `Haskell`ers](fp-ts-for-purescripters.md)
 
 ## Internals
 
diff --git a/docs/function.md b/docs/function.md
index a4f28927..f4bc2874 100644
--- a/docs/function.md
+++ b/docs/function.md
@@ -105,6 +105,23 @@ _Description_
 
 A thunk that returns always `false`
 
+### constIdentity
+
+_function_
+
+_since 1.5.0_
+
+_Signature_
+
+```ts
+(): (<A>(a: A) => A)
+```
+
+_Description_
+
+A thunk that returns always the `identity` function.
+For use with `applySecond` methods.
+
 ### constNull
 
 _function_
@@ -177,6 +194,18 @@ _Signature_
 curry(f: Function)
 ```
 
+### decrement
+
+_function_
+
+_since 1.9.0_
+
+_Signature_
+
+```ts
+(n: number): number
+```
+
 ### flip
 
 _function_
@@ -205,6 +234,18 @@ _Signature_
 <A>(a: A): A
 ```
 
+### increment
+
+_function_
+
+_since 1.9.0_
+
+_Signature_
+
+```ts
+(n: number): number
+```
+
 ### not
 
 _function_
diff --git a/examples/mtl.ts b/examples/mtl.ts
index 01d92402..5bfaeab6 100644
--- a/examples/mtl.ts
+++ b/examples/mtl.ts
@@ -3,7 +3,7 @@ import { flatten } from '../src/Chain'
 import { HKT, Type, Type3, URIS, URIS3 } from '../src/HKT'
 import { IO, URI as IOURI, io } from '../src/IO'
 import { Monad, Monad1, Monad3 } from '../src/Monad'
-import { Task, URI as TaskURI, task } from '../src/Task'
+import { URI as TaskURI, task, delay } from '../src/Task'
 import { URI as ReaderTaskEitherURI, right, readerTaskEither } from '../src/ReaderTaskEither'
 
 // Adapted from https://tech.iheart.com/why-fp-its-the-composition-f585d17b01d3
@@ -72,29 +72,18 @@ true
 */
 
 const now = Date.now()
-const delay = <A>(a: A) => (n: number): Task<A> =>
-  new Task(
-    () =>
-      new Promise(resolve => {
-        setTimeout(() => {
-          // tslint:disable-next-line:no-console
-          console.log(`${a} after ${(Date.now() - now) / 1000}`)
-          resolve(a)
-        }, n)
-      })
-  )
 
 const monadUserTask: MonadUser1<TaskURI> = {
-  validateUser: token => delay(`string(${token})`)(1000),
-  facebookToken: uid => delay(`FBToken(${uid})`)(500)
+  validateUser: token => delay(1000, `string(${token})`),
+  facebookToken: uid => delay(500, `FBToken(${uid})`)
 }
 
 const monadFBTask: MonadFB1<TaskURI> = {
-  findPost: url => delay(`FBPost(${url})`)(2000),
+  findPost: url => delay(2000, `FBPost(${url})`),
   sendLike: token => (post: string) => {
     // tslint:disable-next-line:no-console
     console.log(`sending like with fbToken "${token}" and post "${post}"`)
-    return delay(true)(1000)
+    return delay(1000, true)
   }
 }
 
diff --git a/fp-ts-for-purescripters.md b/fp-ts-for-purescripters.md
new file mode 100644
index 00000000..db1245ca
--- /dev/null
+++ b/fp-ts-for-purescripters.md
@@ -0,0 +1,221 @@
+---
+title: `fp-ts` for `PureScript`ers / `Haskell`ers
+---
+
+# Do notation
+
+PureScript
+
+```purescript
+do
+  print "foo"
+  print "bar"
+  x <- readLine
+  print x
+```
+
+TypeScript
+
+```ts
+// print: (s: string) => Task<void>
+// readLine: Task<string>
+
+print('foo')
+  .chain(_ => print('bar'))
+  .chain(_ => readLine)
+  .chain(print)
+```
+
+# Data
+
+PureScript
+
+```purescript
+--   ↓-- type
+data Foo = Bar String | Baz Boolean
+--         ↑------------↑-- constructors
+```
+
+TypeScript
+
+```ts
+interface Bar { type: 'Bar'; value: string }
+interface Baz { type: 'Baz'; value: boolean }
+// type
+type Foo = Bar | Baz
+// constructors
+const Bar = (value: string): Foo => ({ type: 'Bar', value })
+const Baz = (value: boolean): Foo => ({ type: 'Baz', value })
+```
+
+# Polymorphic data
+
+PureScript
+
+```purescript
+data Option a = None | Some a
+```
+
+TypeScript
+
+```ts
+declare module 'fp-ts/lib/HKT' {
+  interface URI2HKT<A> {
+    Option: Option<A>
+  }
+}
+
+const URI = 'Option'
+
+type URI = typeof URI
+
+class None<A> {
+  static value: Option<never> = new None()
+  readonly _tag: 'None' = 'None'
+  readonly _A!: A
+  readonly _URI!: URI
+  private constructor() {}
+}
+
+class Some<A> {
+  readonly _tag: 'Some' = 'Some'
+  readonly _A!: A
+  readonly _URI!: URI
+  constructor(readonly value: A) {}
+}
+
+type Option<A> = None<A> | Some<A>
+
+const none = None.value
+
+const some = <A>(a: A): Option<A> => new Some(a)
+```
+
+# Pattern matching
+
+PureScript
+
+```purescript
+maybe :: forall a b. b -> (a -> b) -> Option a -> b
+maybe b _ None = b
+maybe _ f (Some a) = f a
+```
+
+TypeScript
+
+```ts
+const maybe = <A, B>(whenNone: B, whenSome: (a: A) => B, fa: Option<A>): B => {
+  switch (fa.type) {
+    case 'None' :
+      return whenNone
+    case 'Some' :
+      return whenSome(fa.value)
+  }
+}
+```
+
+# Type classes
+
+PureScript
+
+```purescript
+class Functor f where
+  map :: forall a b. (a -> b) -> f a -> f b
+```
+
+TypeScript
+
+```ts
+export interface Functor<F> {
+  readonly URI: F
+  readonly map: <A, B>(fa: HKT<F, A>, f: (a: A) => B) => HKT<F, B>
+}
+
+export interface Functor1<F extends URIS> {
+  readonly URI: F
+  readonly map: <A, B>(fa: Type<F, A>, f: (a: A) => B) => Type<F, B>
+}
+
+export interface Functor2<F extends URIS2> {
+  readonly URI: F
+  readonly map: <L, A, B>(fa: Type2<F, L, A>, f: (a: A) => B) => Type2<F, L, B>
+}
+
+export interface Functor3<F extends URIS3> {
+  readonly URI: F
+  readonly map: <U, L, A, B>(fa: Type3<F, U, L, A>, f: (a: A) => B) => Type3<F, U, L, B>
+}
+
+export interface Functor2C<F extends URIS2, L> {
+  readonly URI: F
+  readonly _L: L
+  readonly map: <A, B>(fa: Type2<F, L, A>, f: (a: A) => B) => Type2<F, L, B>
+}
+
+export interface Functor3C<F extends URIS3, U, L> {
+  readonly URI: F
+  readonly _L: L
+  readonly _U: U
+  readonly map: <A, B>(fa: Type3<F, U, L, A>, f: (a: A) => B) => Type3<F, U, L, B>
+}
+```
+
+# Instances
+
+PureScript
+
+```purescript
+instance functorOption :: Functor Option where
+  map fn (Some x) = Some (fn x)
+  map _  _        = None
+```
+
+TypeScript
+
+```ts
+const functorOption: Functor1<'Option'> = {
+  map: (fa, f) => maybe(none, a => some(f(fa.value)), fa)
+}
+```
+
+# Type constraints
+
+PureScript
+
+```purescript
+instance semigroupOption :: Semigroup a => Semigroup (Option a) where
+  append None y = y
+  append x None = x
+  append (Some x) (Some y) = Some (x <> y)
+
+instance monoidOption :: Semigroup a => Monoid (Option a) where
+  mempty = None
+```
+
+TypeScript
+
+```ts
+//                    ↓ the constraint is implemented as an additional parameter
+const getMonoid = <A>(S: Semigroup<A>): Monoid<Option<A>> => {
+  return {
+    concat: (x, y) => {
+      if (x._tag === 'Some' && y._tag === 'Some') {
+        return some(S.concat(x.value, y.value))
+      } else if (x._tag === 'Some') {
+        return y
+      } else {
+        return x
+      }
+    },
+    empty: none
+  }
+}
+```
+
+# Newtypes
+
+See [newtype-ts](https://github.com/gcanti/newtype-ts)
+
+# Functional optics
+
+See [monocle-ts](https://github.com/gcanti/monocle-ts)
diff --git a/package-lock.json b/package-lock.json
index 915f5625..95b3a2e2 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,6 +1,6 @@
 {
   "name": "fp-ts",
-  "version": "1.8.0",
+  "version": "1.8.1",
   "lockfileVersion": 1,
   "requires": true,
   "dependencies": {
diff --git a/package.json b/package.json
index 461c1f7c..5623327f 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "fp-ts",
-  "version": "1.8.0",
+  "version": "1.8.1",
   "description": "Functional programming in TypeScript",
   "files": [
     "lib"
@@ -17,6 +17,7 @@
     "test": "npm run lint && npm run prettier && npm run typings && npm run jest",
     "clean": "rimraf lib/*",
     "build": "npm run clean && tsc",
+    "prepublish": "npm run build",
     "docs": "rimraf docs/*.md && ts-node docs-generator/main.ts"
   },
   "repository": {
diff --git a/src/Array.ts b/src/Array.ts
index 14ef6417..6dee20f5 100644
--- a/src/Array.ts
+++ b/src/Array.ts
@@ -23,7 +23,9 @@ import { Witherable1 } from './Witherable'
 
 declare global {
   interface Array<T> {
+    /** phantom property added by `fp-ts` */
     _URI: URI
+    /** phantom property added by `fp-ts` */
     _A: T
   }
 }
diff --git a/src/Foldable.ts b/src/Foldable.ts
index ed118c92..749dfda8 100644
--- a/src/Foldable.ts
+++ b/src/Foldable.ts
@@ -489,7 +489,8 @@ export function toArray<F>(F: Foldable<F>): <A>(fa: HKT<F, A>) => Array<A>
  * @since 1.0.0
  */
 export function toArray<F>(F: Foldable<F>): <A>(fa: HKT<F, A>) => Array<A> {
-  return fa => foldMap(F, unsafeMonoidArray)(fa, a => [a])
+  const foldMapF = foldMap(F, unsafeMonoidArray)
+  return fa => foldMapF(fa, a => [a])
 }
 
 /**
diff --git a/src/Free.ts b/src/Free.ts
index c88bd2b2..c19a0a54 100644
--- a/src/Free.ts
+++ b/src/Free.ts
@@ -9,6 +9,12 @@ export const URI = 'Free'
 
 export type URI = typeof URI
 
+declare module './HKT' {
+  interface URI2HKT2<L, A> {
+    Free: Free<L, A>
+  }
+}
+
 /**
  * @data
  * @constructor Pure
diff --git a/src/TaskEither.ts b/src/TaskEither.ts
index be34bc27..34a2f08c 100644
--- a/src/TaskEither.ts
+++ b/src/TaskEither.ts
@@ -1,12 +1,21 @@
 import { Alt2 } from './Alt'
 import { Bifunctor2 } from './Bifunctor'
-import { Either, fromPredicate as eitherFromPredicate, left as eitherLeft, right as eitherRight } from './Either'
+import {
+  Either,
+  fromPredicate as eitherFromPredicate,
+  left as eitherLeft,
+  right as eitherRight,
+  getSemigroup as eitherGetSemigroup,
+  getApplySemigroup as eitherGetApplySemigroup
+} from './Either'
 import * as eitherT from './EitherT'
 import { IO } from './IO'
 import { Monad2 } from './Monad'
-import { Task, fromIO as taskFromIO, task, tryCatch as taskTryCatch } from './Task'
+import { Task, fromIO as taskFromIO, task, tryCatch as taskTryCatch, getSemigroup as taskGetSemigroup } from './Task'
 import { Lazy, constIdentity, constant, Predicate } from './function'
 import { IOEither } from './IOEither'
+import { Semigroup } from './Semigroup'
+import { Monoid } from './Monoid'
 
 declare module './HKT' {
   interface URI2HKT2<L, A> {
@@ -174,6 +183,39 @@ export const fromPredicate = <L, A>(predicate: Predicate<A>, whenFalse: (a: A) =
   return a => fromEither(f(a))
 }
 
+/**
+ * @function
+ * @since 1.9.0
+ */
+export const getSemigroup = <L, A>(S: Semigroup<A>): Semigroup<TaskEither<L, A>> => {
+  const S2 = taskGetSemigroup(eitherGetSemigroup<L, A>(S))
+  return {
+    concat: (x, y) => new TaskEither<L, A>(S2.concat(x.value, y.value))
+  }
+}
+
+/**
+ * @function
+ * @since 1.9.0
+ */
+export const getApplySemigroup = <L, A>(S: Semigroup<A>): Semigroup<TaskEither<L, A>> => {
+  const S2 = taskGetSemigroup(eitherGetApplySemigroup<L, A>(S))
+  return {
+    concat: (x, y) => new TaskEither<L, A>(S2.concat(x.value, y.value))
+  }
+}
+
+/**
+ * @function
+ * @since 1.9.0
+ */
+export const getApplyMonoid = <L, A>(M: Monoid<A>): Monoid<TaskEither<L, A>> => {
+  return {
+    ...getApplySemigroup(M),
+    empty: of(M.empty)
+  }
+}
+
 /**
  * @function
  * @since 1.0.0
diff --git a/src/function.ts b/src/function.ts
index ed3f9a00..6c78a404 100644
--- a/src/function.ts
+++ b/src/function.ts
@@ -384,6 +384,25 @@ export const phantom: any = undefined
 /**
  * A thunk that returns always the `identity` function.
  * For use with `applySecond` methods.
+ * @function
  * @since 1.5.0
  */
-export const constIdentity = () => identity
+export const constIdentity = (): (<A>(a: A) => A) => {
+  return identity
+}
+
+/**
+ * @function
+ * @since 1.9.0
+ */
+export const increment = (n: number): number => {
+  return n + 1
+}
+
+/**
+ * @function
+ * @since 1.9.0
+ */
+export const decrement = (n: number): number => {
+  return n - 1
+}
diff --git a/tsconfig.json b/tsconfig.json
index 2e115475..d9afa656 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -1,7 +1,6 @@
 {
   "compilerOptions": {
     "outDir": "./lib",
-    "sourceMap": true,
     "declaration": true,
     "target": "es5",
     "module": "commonjs",


Test patch:
diff --git a/test/Foldable.ts b/test/Foldable.ts
index 2b2edd90..31f1c9bb 100644
--- a/test/Foldable.ts
+++ b/test/Foldable.ts
@@ -25,6 +25,7 @@ import { monoidString } from '../src/Monoid'
 import * as option from '../src/Option'
 import { ordNumber } from '../src/Ord'
 import { setoidNumber } from '../src/Setoid'
+import { StrMap, strmap } from '../src/StrMap'
 
 export const ArrayOptionURI = 'ArrayOption'
 
@@ -33,6 +34,9 @@ export type ArrayOptionURI = typeof ArrayOptionURI
 describe('Foldable', () => {
   it('toArray', () => {
     assert.deepEqual(toArray(array)([1, 2, 3]), [1, 2, 3])
+    assert.deepEqual(toArray(option.option)(option.some(1)), [1])
+    assert.deepEqual(toArray(option.option)(option.none), [])
+    assert.deepEqual(toArray(strmap)(new StrMap({ a: 1, b: 2 })), [1, 2])
   })
 
   it('foldMap', () => {
diff --git a/test/TaskEither.ts b/test/TaskEither.ts
index 42d4b6bc..fae64fa7 100644
--- a/test/TaskEither.ts
+++ b/test/TaskEither.ts
@@ -1,7 +1,7 @@
 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 { Task, task, delay } from '../src/Task'
 import {
   TaskEither,
   fromIO,
@@ -12,9 +12,13 @@ import {
   taskify,
   tryCatch,
   fromIOEither,
-  fromPredicate
+  fromPredicate,
+  getApplyMonoid,
+  getSemigroup
 } from '../src/TaskEither'
 import { IOEither } from '../src/IOEither'
+import { monoidString } from '../src/Monoid'
+import { semigroupSum } from '../src/Semigroup'
 
 describe('TaskEither', () => {
   it('ap', () => {
@@ -211,4 +215,47 @@ describe('TaskEither', () => {
       assert.deepEqual(e2, eitherLeft('Invalid number 1'))
     })
   })
+
+  it('getSemigroup', () => {
+    const S = getSemigroup<string, number>(semigroupSum)
+    return Promise.all([
+      S.concat(left(delay(10, 'a')), left(delay(10, 'b')))
+        .run()
+        .then(x => assert.deepEqual(x, eitherLeft('a'))),
+      S.concat(left(delay(10, 'a')), right(delay(10, 2)))
+        .run()
+        .then(x => assert.deepEqual(x, eitherRight(2))),
+      S.concat(right(delay(10, 1)), left(delay(10, 'b')))
+        .run()
+        .then(x => assert.deepEqual(x, eitherRight(1))),
+      S.concat(right(delay(10, 1)), right(delay(10, 2)))
+        .run()
+        .then(x => assert.deepEqual(x, eitherRight(3)))
+    ])
+  })
+
+  describe('getApplyMonoid', () => {
+    const M = getApplyMonoid(monoidString)
+
+    it('concat (right)', () => {
+      return M.concat(right(delay(10, 'a')), right(delay(10, 'b')))
+        .run()
+        .then(x => assert.deepEqual(x, eitherRight('ab')))
+    })
+    it('concat (left)', () => {
+      return M.concat(right(delay(10, 'a')), left(delay(10, 'b')))
+        .run()
+        .then(x => assert.deepEqual(x, eitherLeft('b')))
+    })
+    it('empty (right)', () => {
+      return M.concat(right(delay(10, 'a')), M.empty)
+        .run()
+        .then(x => assert.deepEqual(x, eitherRight('a')))
+    })
+    it('empty (left)', () => {
+      return M.concat(M.empty, right(delay(10, 'a')))
+        .run()
+        .then(x => assert.deepEqual(x, eitherRight('a')))
+    })
+  })
 })