Repository: gcanti/fp-ts
Pull request #817 (2019-04-06 08:01:32)

New Feature: add Record.fromFoldableMap


Patch:
diff --git a/docs/modules/IOEither.ts.md b/docs/modules/IOEither.ts.md
index 500806e6..82b09472 100644
--- a/docs/modules/IOEither.ts.md
+++ b/docs/modules/IOEither.ts.md
@@ -32,7 +32,9 @@ error of type `L`. If you want to represent a synchronous computation that never
 - [fromEither (function)](#fromeither-function)
 - [fromLeft (function)](#fromleft-function)
 - [left (function)](#left-function)
+- [parseJSON (function)](#parsejson-function)
 - [right (function)](#right-function)
+- [stringifyJSON (function)](#stringifyjson-function)
 - [~~tryCatch~~ (function)](#trycatch-function)
 - [tryCatch2v (function)](#trycatch2v-function)
 
@@ -211,6 +213,31 @@ export const left = <L, A>(fa: IO<L>): IOEither<L, A> => ...
 
 Added in v1.6.0
 
+# parseJSON (function)
+
+Converts a JavaScript Object Notation (JSON) string into an object.
+
+**Signature**
+
+```ts
+export const parseJSON = <L>(s: string, onError: (reason: unknown) => L): IOEither<L, unknown> => ...
+```
+
+**Example**
+
+```ts
+import { parseJSON } from 'fp-ts/lib/IOEither'
+import { toError } from 'fp-ts/lib/Either'
+
+assert.deepStrictEqual(parseJSON('{"a":1}', toError).run().value, { a: 1 })
+assert.deepStrictEqual(
+  parseJSON('{"a":}', toError).run().value,
+  new SyntaxError('Unexpected token } in JSON at position 5')
+)
+```
+
+Added in v1.16.0
+
 # right (function)
 
 **Signature**
@@ -221,6 +248,33 @@ export const right = <L, A>(fa: IO<A>): IOEither<L, A> => ...
 
 Added in v1.6.0
 
+# stringifyJSON (function)
+
+Converts a JavaScript value to a JavaScript Object Notation (JSON) string.
+
+**Signature**
+
+```ts
+export const stringifyJSON = <L>(u: unknown, onError: (reason: unknown) => L): IOEither<L, string> => ...
+```
+
+**Example**
+
+```ts
+import { stringifyJSON } from 'fp-ts/lib/IOEither'
+import { toError } from 'fp-ts/lib/Either'
+
+assert.deepStrictEqual(stringifyJSON({ a: 1 }, toError).run().value, '{"a":1}')
+const circular: any = { ref: null }
+circular.ref = circular
+assert.deepStrictEqual(
+  stringifyJSON(circular, toError).run().value,
+  new TypeError('Converting circular structure to JSON')
+)
+```
+
+Added in v1.16.0
+
 # ~~tryCatch~~ (function)
 
 Use `tryCatch2v` instead
diff --git a/docs/modules/Magma.ts.md b/docs/modules/Magma.ts.md
new file mode 100644
index 00000000..741d4e69
--- /dev/null
+++ b/docs/modules/Magma.ts.md
@@ -0,0 +1,27 @@
+---
+title: Magma.ts
+nav_order: 52
+parent: Modules
+---
+
+---
+
+<h2 class="text-delta">Table of contents</h2>
+
+- [Magma (interface)](#magma-interface)
+
+---
+
+# Magma (interface)
+
+A `Magma` is a pair `(A, concat)` in which `A` is a non-empty set and `concat` is a binary operation on `A`
+
+**Signature**
+
+```ts
+export interface Magma<A> {
+  readonly concat: (x: A, y: A) => A
+}
+```
+
+Added in v1.16.0
diff --git a/docs/modules/Map.ts.md b/docs/modules/Map.ts.md
index c3ce8611..cf581e06 100644
--- a/docs/modules/Map.ts.md
+++ b/docs/modules/Map.ts.md
@@ -1,6 +1,6 @@
 ---
 title: Map.ts
-nav_order: 52
+nav_order: 53
 parent: Modules
 ---
 
@@ -106,19 +106,19 @@ specified function to combine values for duplicate keys.
 export function fromFoldable<K, F extends URIS3>(
   S: Setoid<K>,
   F: Foldable2v3<F>
-): <U, L, A>(ta: Type3<F, U, L, [K, A]>, f: (existing: A, a: A) => A) => Map<K, A>
+): <U, L, A>(ta: Type3<F, U, L, [K, A]>, onConflict: (existing: A, a: A) => A) => Map<K, A>
 export function fromFoldable<K, F extends URIS2>(
   S: Setoid<K>,
   F: Foldable2v2<F>
-): <L, A>(ta: Type2<F, L, [K, A]>, f: (existing: A, a: A) => A) => Map<K, A>
+): <L, A>(ta: Type2<F, L, [K, A]>, onConflict: (existing: A, a: A) => A) => Map<K, A>
 export function fromFoldable<K, F extends URIS>(
   S: Setoid<K>,
   F: Foldable2v1<F>
-): <A>(ta: Type<F, [K, A]>, f: (existing: A, a: A) => A) => Map<K, A>
+): <A>(ta: Type<F, [K, A]>, onConflict: (existing: A, a: A) => A) => Map<K, A>
 export function fromFoldable<K, F>(
   S: Setoid<K>,
   F: Foldable2v<F>
-): <A>(ta: HKT<F, [K, A]>, f: (existing: A, a: A) => A) => Map<K, A> { ... }
+): <A>(ta: HKT<F, [K, A]>, onConflict: (existing: A, a: A) => A) => Map<K, A> { ... }
 ```
 
 Added in v1.14.0
diff --git a/docs/modules/MeetSemilattice.ts.md b/docs/modules/MeetSemilattice.ts.md
index 70984da3..f81284ce 100644
--- a/docs/modules/MeetSemilattice.ts.md
+++ b/docs/modules/MeetSemilattice.ts.md
@@ -1,6 +1,6 @@
 ---
 title: MeetSemilattice.ts
-nav_order: 53
+nav_order: 54
 parent: Modules
 ---
 
diff --git a/docs/modules/Monad.ts.md b/docs/modules/Monad.ts.md
index 541e15af..da4662aa 100644
--- a/docs/modules/Monad.ts.md
+++ b/docs/modules/Monad.ts.md
@@ -1,6 +1,6 @@
 ---
 title: Monad.ts
-nav_order: 54
+nav_order: 55
 parent: Modules
 ---
 
diff --git a/docs/modules/MonadIO.ts.md b/docs/modules/MonadIO.ts.md
index b50fbc1a..3e0fad86 100644
--- a/docs/modules/MonadIO.ts.md
+++ b/docs/modules/MonadIO.ts.md
@@ -1,6 +1,6 @@
 ---
 title: MonadIO.ts
-nav_order: 55
+nav_order: 56
 parent: Modules
 ---
 
diff --git a/docs/modules/MonadTask.ts.md b/docs/modules/MonadTask.ts.md
index 1f484b4b..b19a23d4 100644
--- a/docs/modules/MonadTask.ts.md
+++ b/docs/modules/MonadTask.ts.md
@@ -1,6 +1,6 @@
 ---
 title: MonadTask.ts
-nav_order: 56
+nav_order: 57
 parent: Modules
 ---
 
diff --git a/docs/modules/MonadThrow.ts.md b/docs/modules/MonadThrow.ts.md
index b805e1e7..05168211 100644
--- a/docs/modules/MonadThrow.ts.md
+++ b/docs/modules/MonadThrow.ts.md
@@ -1,6 +1,6 @@
 ---
 title: MonadThrow.ts
-nav_order: 57
+nav_order: 58
 parent: Modules
 ---
 
diff --git a/docs/modules/Monoid.ts.md b/docs/modules/Monoid.ts.md
index e804caee..6596cce4 100644
--- a/docs/modules/Monoid.ts.md
+++ b/docs/modules/Monoid.ts.md
@@ -1,6 +1,6 @@
 ---
 title: Monoid.ts
-nav_order: 58
+nav_order: 59
 parent: Modules
 ---
 
diff --git a/docs/modules/Monoidal.ts.md b/docs/modules/Monoidal.ts.md
index 4bdcd4eb..bb5eaed9 100644
--- a/docs/modules/Monoidal.ts.md
+++ b/docs/modules/Monoidal.ts.md
@@ -1,6 +1,6 @@
 ---
 title: Monoidal.ts
-nav_order: 59
+nav_order: 60
 parent: Modules
 ---
 
diff --git a/docs/modules/NonEmptyArray.ts.md b/docs/modules/NonEmptyArray.ts.md
index c5ebb085..24134462 100644
--- a/docs/modules/NonEmptyArray.ts.md
+++ b/docs/modules/NonEmptyArray.ts.md
@@ -1,6 +1,6 @@
 ---
 title: NonEmptyArray.ts
-nav_order: 60
+nav_order: 61
 parent: Modules
 ---
 
diff --git a/docs/modules/NonEmptyArray2v.ts.md b/docs/modules/NonEmptyArray2v.ts.md
index 405b5bf9..88565701 100644
--- a/docs/modules/NonEmptyArray2v.ts.md
+++ b/docs/modules/NonEmptyArray2v.ts.md
@@ -1,6 +1,6 @@
 ---
 title: NonEmptyArray2v.ts
-nav_order: 61
+nav_order: 62
 parent: Modules
 ---
 
diff --git a/docs/modules/Option.ts.md b/docs/modules/Option.ts.md
index 2aa5bf30..3fa9a747 100644
--- a/docs/modules/Option.ts.md
+++ b/docs/modules/Option.ts.md
@@ -1,6 +1,6 @@
 ---
 title: Option.ts
-nav_order: 62
+nav_order: 63
 parent: Modules
 ---
 
diff --git a/docs/modules/OptionT.ts.md b/docs/modules/OptionT.ts.md
index 15986683..578be435 100644
--- a/docs/modules/OptionT.ts.md
+++ b/docs/modules/OptionT.ts.md
@@ -1,6 +1,6 @@
 ---
 title: OptionT.ts
-nav_order: 63
+nav_order: 64
 parent: Modules
 ---
 
diff --git a/docs/modules/Ord.ts.md b/docs/modules/Ord.ts.md
index 3c6d829e..5e6b6a3a 100644
--- a/docs/modules/Ord.ts.md
+++ b/docs/modules/Ord.ts.md
@@ -1,6 +1,6 @@
 ---
 title: Ord.ts
-nav_order: 64
+nav_order: 65
 parent: Modules
 ---
 
diff --git a/docs/modules/Ordering.ts.md b/docs/modules/Ordering.ts.md
index 82ad99f2..8c24f662 100644
--- a/docs/modules/Ordering.ts.md
+++ b/docs/modules/Ordering.ts.md
@@ -1,6 +1,6 @@
 ---
 title: Ordering.ts
-nav_order: 65
+nav_order: 66
 parent: Modules
 ---
 
diff --git a/docs/modules/Pair.ts.md b/docs/modules/Pair.ts.md
index daff2a38..e8510e45 100644
--- a/docs/modules/Pair.ts.md
+++ b/docs/modules/Pair.ts.md
@@ -1,6 +1,6 @@
 ---
 title: Pair.ts
-nav_order: 66
+nav_order: 67
 parent: Modules
 ---
 
diff --git a/docs/modules/Plus.ts.md b/docs/modules/Plus.ts.md
index 2d63188e..27bec439 100644
--- a/docs/modules/Plus.ts.md
+++ b/docs/modules/Plus.ts.md
@@ -1,6 +1,6 @@
 ---
 title: Plus.ts
-nav_order: 67
+nav_order: 68
 parent: Modules
 ---
 
diff --git a/docs/modules/Profunctor.ts.md b/docs/modules/Profunctor.ts.md
index d3ef7617..58a7d592 100644
--- a/docs/modules/Profunctor.ts.md
+++ b/docs/modules/Profunctor.ts.md
@@ -1,6 +1,6 @@
 ---
 title: Profunctor.ts
-nav_order: 68
+nav_order: 69
 parent: Modules
 ---
 
diff --git a/docs/modules/Random.ts.md b/docs/modules/Random.ts.md
index 639fa631..98ffc20b 100644
--- a/docs/modules/Random.ts.md
+++ b/docs/modules/Random.ts.md
@@ -1,6 +1,6 @@
 ---
 title: Random.ts
-nav_order: 69
+nav_order: 70
 parent: Modules
 ---
 
diff --git a/docs/modules/Reader.ts.md b/docs/modules/Reader.ts.md
index c2a2a909..7edc5a15 100644
--- a/docs/modules/Reader.ts.md
+++ b/docs/modules/Reader.ts.md
@@ -1,6 +1,6 @@
 ---
 title: Reader.ts
-nav_order: 70
+nav_order: 71
 parent: Modules
 ---
 
diff --git a/docs/modules/ReaderT.ts.md b/docs/modules/ReaderT.ts.md
index dd60f191..400dc42e 100644
--- a/docs/modules/ReaderT.ts.md
+++ b/docs/modules/ReaderT.ts.md
@@ -1,6 +1,6 @@
 ---
 title: ReaderT.ts
-nav_order: 71
+nav_order: 72
 parent: Modules
 ---
 
diff --git a/docs/modules/ReaderTaskEither.ts.md b/docs/modules/ReaderTaskEither.ts.md
index d7712cd2..59136c76 100644
--- a/docs/modules/ReaderTaskEither.ts.md
+++ b/docs/modules/ReaderTaskEither.ts.md
@@ -1,6 +1,6 @@
 ---
 title: ReaderTaskEither.ts
-nav_order: 72
+nav_order: 73
 parent: Modules
 ---
 
diff --git a/docs/modules/Record.ts.md b/docs/modules/Record.ts.md
index f25ddb01..d59a12b7 100644
--- a/docs/modules/Record.ts.md
+++ b/docs/modules/Record.ts.md
@@ -1,6 +1,6 @@
 ---
 title: Record.ts
-nav_order: 73
+nav_order: 74
 parent: Modules
 ---
 
@@ -25,6 +25,7 @@ parent: Modules
 - [foldr (function)](#foldr-function)
 - [foldrWithKey (function)](#foldrwithkey-function)
 - [fromFoldable (function)](#fromfoldable-function)
+- [fromFoldableMap (function)](#fromfoldablemap-function)
 - [getMonoid (function)](#getmonoid-function)
 - [getSetoid (function)](#getsetoid-function)
 - [insert (function)](#insert-function)
@@ -240,7 +241,7 @@ Added in v1.12.0
 
 # fromFoldable (function)
 
-Create a dictionary from a foldable collection of key/value pairs, using the
+Create a record from a foldable collection of key/value pairs, using the
 specified function to combine values for duplicate keys.
 
 **Signature**
@@ -248,21 +249,80 @@ specified function to combine values for duplicate keys.
 ```ts
 export function fromFoldable<F extends URIS3>(
   F: Foldable3<F>
-): <K extends string, U, L, A>(ta: Type3<F, U, L, [K, A]>, f: (existing: A, a: A) => A) => Record<K, A>
+): <K extends string, U, L, A>(ta: Type3<F, U, L, [K, A]>, onConflict: (existing: A, a: A) => A) => Record<K, A>
 export function fromFoldable<F extends URIS2>(
   F: Foldable2<F>
-): <K extends string, L, A>(ta: Type2<F, L, [K, A]>, f: (existing: A, a: A) => A) => Record<K, A>
+): <K extends string, L, A>(ta: Type2<F, L, [K, A]>, onConflict: (existing: A, a: A) => A) => Record<K, A>
 export function fromFoldable<F extends URIS>(
   F: Foldable1<F>
-): <K extends string, A>(ta: Type<F, [K, A]>, f: (existing: A, a: A) => A) => Record<K, A>
+): <K extends string, A>(ta: Type<F, [K, A]>, onConflict: (existing: A, a: A) => A) => Record<K, A>
 export function fromFoldable<F>(
   // tslint:disable-next-line: deprecation
   F: Foldable<F>
-): <K extends string, A>(ta: HKT<F, [K, A]>, f: (existing: A, a: A) => A) => Record<K, A> { ... }
+): <K extends string, A>(ta: HKT<F, [K, A]>, onConflict: (existing: A, a: A) => A) => Record<K, A> { ... }
 ```
 
 Added in v1.10.0
 
+# fromFoldableMap (function)
+
+Create a record from a foldable collection using the specified functions to
+
+- map to key/value pairs
+- combine values for duplicate keys.
+
+**Signature**
+
+```ts
+export function fromFoldableMap<F extends URIS3, B>(
+  M: Magma<B>,
+  F: Foldable3<F>
+): <U, L, A, K extends string>(ta: Type3<F, U, L, A>, f: (a: A) => [K, B]) => Record<K, B>
+export function fromFoldableMap<F extends URIS2, B>(
+  M: Magma<B>,
+  F: Foldable2<F>
+): <L, A, K extends string>(ta: Type2<F, L, A>, f: (a: A) => [K, B]) => Record<K, B>
+export function fromFoldableMap<F extends URIS, B>(
+  M: Magma<B>,
+  F: Foldable1<F>
+): <A, K extends string>(ta: Type<F, A>, f: (a: A) => [K, B]) => Record<K, B>
+export function fromFoldableMap<F, B>(
+  M: Magma<B>,
+  // tslint:disable-next-line: deprecation
+  F: Foldable<F>
+): <A, K extends string>(ta: HKT<F, A>, f: (a: A) => [K, B]) => Record<K, B> { ... }
+```
+
+**Example**
+
+```ts
+import { getLastSemigroup } from 'fp-ts/lib/Semigroup'
+import { array, zip } from 'fp-ts/lib/Array'
+import { identity } from 'fp-ts/lib/function'
+import { fromFoldableMap } from 'fp-ts/lib/Record'
+
+// like lodash `zipObject` or ramda `zipObj`
+export const zipObject = <K extends string, A>(keys: Array<K>, values: Array<A>): Record<K, A> =>
+  fromFoldableMap(getLastSemigroup<A>(), array)(zip(keys, values), identity)
+
+assert.deepStrictEqual(zipObject(['a', 'b'], [1, 2, 3]), { a: 1, b: 2 })
+
+// build a record from a field
+interface User {
+  id: string
+  name: string
+}
+
+const users: Array<User> = [{ id: 'id1', name: 'name1' }, { id: 'id2', name: 'name2' }, { id: 'id1', name: 'name3' }]
+
+assert.deepStrictEqual(fromFoldableMap(getLastSemigroup<User>(), array)(users, user => [user.id, user]), {
+  id1: { id: 'id1', name: 'name3' },
+  id2: { id: 'id2', name: 'name2' }
+})
+```
+
+Added in v1.16.0
+
 # getMonoid (function)
 
 Returns a `Semigroup` instance for records given a `Semigroup` instance for their values
@@ -336,7 +396,7 @@ Added in v1.14.0
 
 # lookup (function)
 
-Lookup the value for a key in a dictionary
+Lookup the value for a key in a record
 
 **Signature**
 
@@ -554,7 +614,7 @@ Added in v1.10.0
 
 # singleton (function)
 
-Create a dictionary with one key/value pair
+Create a record with one key/value pair
 
 **Signature**
 
diff --git a/docs/modules/Ring.ts.md b/docs/modules/Ring.ts.md
index baca8bbf..5028d52f 100644
--- a/docs/modules/Ring.ts.md
+++ b/docs/modules/Ring.ts.md
@@ -1,6 +1,6 @@
 ---
 title: Ring.ts
-nav_order: 74
+nav_order: 75
 parent: Modules
 ---
 
diff --git a/docs/modules/Semigroup.ts.md b/docs/modules/Semigroup.ts.md
index 8702cf65..a93e4ee3 100644
--- a/docs/modules/Semigroup.ts.md
+++ b/docs/modules/Semigroup.ts.md
@@ -1,6 +1,6 @@
 ---
 title: Semigroup.ts
-nav_order: 75
+nav_order: 76
 parent: Modules
 ---
 
@@ -38,12 +38,14 @@ See [Getting started with fp-ts: Semigroup](https://dev.to/gcanti/getting-starte
 
 # Semigroup (interface)
 
+A `Semigroup` is a `Magma` where `concat` is associative, that is:
+
+Associativiy: `concat(concat(x, y), z) = concat(x, concat(y, z))`
+
 **Signature**
 
 ```ts
-export interface Semigroup<A> {
-  readonly concat: (x: A, y: A) => A
-}
+export interface Semigroup<A> extends Magma<A> {}
 ```
 
 Added in v1.0.0
diff --git a/docs/modules/Semigroupoid.ts.md b/docs/modules/Semigroupoid.ts.md
index fd90dc76..ad3669fe 100644
--- a/docs/modules/Semigroupoid.ts.md
+++ b/docs/modules/Semigroupoid.ts.md
@@ -1,6 +1,6 @@
 ---
 title: Semigroupoid.ts
-nav_order: 76
+nav_order: 77
 parent: Modules
 ---
 
diff --git a/docs/modules/Semiring.ts.md b/docs/modules/Semiring.ts.md
index 0aa9fbf1..1ec59b5d 100644
--- a/docs/modules/Semiring.ts.md
+++ b/docs/modules/Semiring.ts.md
@@ -1,6 +1,6 @@
 ---
 title: Semiring.ts
-nav_order: 77
+nav_order: 78
 parent: Modules
 ---
 
diff --git a/docs/modules/Set.ts.md b/docs/modules/Set.ts.md
index 3800c2a3..0329c7c4 100644
--- a/docs/modules/Set.ts.md
+++ b/docs/modules/Set.ts.md
@@ -1,6 +1,6 @@
 ---
 title: Set.ts
-nav_order: 78
+nav_order: 79
 parent: Modules
 ---
 
diff --git a/docs/modules/Setoid.ts.md b/docs/modules/Setoid.ts.md
index 01ca9902..83ea9bf6 100644
--- a/docs/modules/Setoid.ts.md
+++ b/docs/modules/Setoid.ts.md
@@ -1,6 +1,6 @@
 ---
 title: Setoid.ts
-nav_order: 79
+nav_order: 80
 parent: Modules
 ---
 
diff --git a/docs/modules/State.ts.md b/docs/modules/State.ts.md
index fa2183a4..3634aec4 100644
--- a/docs/modules/State.ts.md
+++ b/docs/modules/State.ts.md
@@ -1,6 +1,6 @@
 ---
 title: State.ts
-nav_order: 80
+nav_order: 81
 parent: Modules
 ---
 
diff --git a/docs/modules/StateT.ts.md b/docs/modules/StateT.ts.md
index 3615ce15..60b5fccb 100644
--- a/docs/modules/StateT.ts.md
+++ b/docs/modules/StateT.ts.md
@@ -1,6 +1,6 @@
 ---
 title: StateT.ts
-nav_order: 81
+nav_order: 82
 parent: Modules
 ---
 
diff --git a/docs/modules/Store.ts.md b/docs/modules/Store.ts.md
index 69253bdc..038fa6a6 100644
--- a/docs/modules/Store.ts.md
+++ b/docs/modules/Store.ts.md
@@ -1,6 +1,6 @@
 ---
 title: Store.ts
-nav_order: 82
+nav_order: 83
 parent: Modules
 ---
 
diff --git a/docs/modules/StrMap.ts.md b/docs/modules/StrMap.ts.md
index f2a2b69c..cadb2796 100644
--- a/docs/modules/StrMap.ts.md
+++ b/docs/modules/StrMap.ts.md
@@ -1,6 +1,6 @@
 ---
 title: StrMap.ts
-nav_order: 83
+nav_order: 84
 parent: Modules
 ---
 
@@ -344,14 +344,17 @@ specified function to combine values for duplicate keys.
 ```ts
 export function fromFoldable<F extends URIS3>(
   F: Foldable3<F>
-): <U, L, A>(ta: Type3<F, U, L, [string, A]>, f: (existing: A, a: A) => A) => StrMap<A>
+): <U, L, A>(ta: Type3<F, U, L, [string, A]>, onConflict: (existing: A, a: A) => A) => StrMap<A>
 export function fromFoldable<F extends URIS2>(
   F: Foldable2<F>
-): <L, A>(ta: Type2<F, L, [string, A]>, f: (existing: A, a: A) => A) => StrMap<A>
+): <L, A>(ta: Type2<F, L, [string, A]>, onConflict: (existing: A, a: A) => A) => StrMap<A>
 export function fromFoldable<F extends URIS>(
   F: Foldable1<F>
-): <A>(ta: Type<F, [string, A]>, f: (existing: A, a: A) => A) => StrMap<A>
-export function fromFoldable<F>(F: Foldable<F>): <A>(ta: HKT<F, [string, A]>, f: (existing: A, a: A) => A) => StrMap<A> { ... }
+): <A>(ta: Type<F, [string, A]>, onConflict: (existing: A, a: A) => A) => StrMap<A>
+export function fromFoldable<F>(
+  // tslint:disable-next-line: deprecation
+  F: Foldable<F>
+): <A>(ta: HKT<F, [string, A]>, onConflict: (existing: A, a: A) => A) => StrMap<A> { ... }
 ```
 
 Added in v1.0.0
diff --git a/docs/modules/Strong.ts.md b/docs/modules/Strong.ts.md
index 88c4ed28..d3bd7b61 100644
--- a/docs/modules/Strong.ts.md
+++ b/docs/modules/Strong.ts.md
@@ -1,6 +1,6 @@
 ---
 title: Strong.ts
-nav_order: 84
+nav_order: 85
 parent: Modules
 ---
 
diff --git a/docs/modules/Task.ts.md b/docs/modules/Task.ts.md
index 7d4e21f7..0e2a7b56 100644
--- a/docs/modules/Task.ts.md
+++ b/docs/modules/Task.ts.md
@@ -1,6 +1,6 @@
 ---
 title: Task.ts
-nav_order: 85
+nav_order: 86
 parent: Modules
 ---
 
diff --git a/docs/modules/TaskEither.ts.md b/docs/modules/TaskEither.ts.md
index 6cef9d01..b3f3d7e4 100644
--- a/docs/modules/TaskEither.ts.md
+++ b/docs/modules/TaskEither.ts.md
@@ -1,6 +1,6 @@
 ---
 title: TaskEither.ts
-nav_order: 86
+nav_order: 87
 parent: Modules
 ---
 
diff --git a/docs/modules/These.ts.md b/docs/modules/These.ts.md
index b1cf4ace..e6fce664 100644
--- a/docs/modules/These.ts.md
+++ b/docs/modules/These.ts.md
@@ -1,6 +1,6 @@
 ---
 title: These.ts
-nav_order: 87
+nav_order: 88
 parent: Modules
 ---
 
diff --git a/docs/modules/Trace.ts.md b/docs/modules/Trace.ts.md
index 4bea3769..b64cd23a 100644
--- a/docs/modules/Trace.ts.md
+++ b/docs/modules/Trace.ts.md
@@ -1,6 +1,6 @@
 ---
 title: Trace.ts
-nav_order: 88
+nav_order: 89
 parent: Modules
 ---
 
diff --git a/docs/modules/Traced.ts.md b/docs/modules/Traced.ts.md
index 5ea1e18d..614096f2 100644
--- a/docs/modules/Traced.ts.md
+++ b/docs/modules/Traced.ts.md
@@ -1,6 +1,6 @@
 ---
 title: Traced.ts
-nav_order: 89
+nav_order: 90
 parent: Modules
 ---
 
diff --git a/docs/modules/Traversable.ts.md b/docs/modules/Traversable.ts.md
index 78cf0090..f5f2b78e 100644
--- a/docs/modules/Traversable.ts.md
+++ b/docs/modules/Traversable.ts.md
@@ -1,6 +1,6 @@
 ---
 title: Traversable.ts
-nav_order: 90
+nav_order: 91
 parent: Modules
 ---
 
diff --git a/docs/modules/Traversable2v.ts.md b/docs/modules/Traversable2v.ts.md
index 29fc4e49..8019f156 100644
--- a/docs/modules/Traversable2v.ts.md
+++ b/docs/modules/Traversable2v.ts.md
@@ -1,6 +1,6 @@
 ---
 title: Traversable2v.ts
-nav_order: 91
+nav_order: 92
 parent: Modules
 ---
 
diff --git a/docs/modules/TraversableWithIndex.ts.md b/docs/modules/TraversableWithIndex.ts.md
index 0e4205cf..cc975892 100644
--- a/docs/modules/TraversableWithIndex.ts.md
+++ b/docs/modules/TraversableWithIndex.ts.md
@@ -1,6 +1,6 @@
 ---
 title: TraversableWithIndex.ts
-nav_order: 92
+nav_order: 93
 parent: Modules
 ---
 
diff --git a/docs/modules/Tree.ts.md b/docs/modules/Tree.ts.md
index 1fa1dd64..63a835c6 100644
--- a/docs/modules/Tree.ts.md
+++ b/docs/modules/Tree.ts.md
@@ -1,6 +1,6 @@
 ---
 title: Tree.ts
-nav_order: 93
+nav_order: 94
 parent: Modules
 ---
 
diff --git a/docs/modules/Tuple.ts.md b/docs/modules/Tuple.ts.md
index defdf7a0..e3b9149b 100644
--- a/docs/modules/Tuple.ts.md
+++ b/docs/modules/Tuple.ts.md
@@ -1,6 +1,6 @@
 ---
 title: Tuple.ts
-nav_order: 94
+nav_order: 95
 parent: Modules
 ---
 
diff --git a/docs/modules/Unfoldable.ts.md b/docs/modules/Unfoldable.ts.md
index b21a1a2e..378daf32 100644
--- a/docs/modules/Unfoldable.ts.md
+++ b/docs/modules/Unfoldable.ts.md
@@ -1,6 +1,6 @@
 ---
 title: Unfoldable.ts
-nav_order: 95
+nav_order: 96
 parent: Modules
 ---
 
diff --git a/docs/modules/Validation.ts.md b/docs/modules/Validation.ts.md
index e5acfaea..555eb030 100644
--- a/docs/modules/Validation.ts.md
+++ b/docs/modules/Validation.ts.md
@@ -1,6 +1,6 @@
 ---
 title: Validation.ts
-nav_order: 96
+nav_order: 97
 parent: Modules
 ---
 
diff --git a/docs/modules/Witherable.ts.md b/docs/modules/Witherable.ts.md
index 39c110d5..bbca0272 100644
--- a/docs/modules/Witherable.ts.md
+++ b/docs/modules/Witherable.ts.md
@@ -1,6 +1,6 @@
 ---
 title: Witherable.ts
-nav_order: 97
+nav_order: 98
 parent: Modules
 ---
 
diff --git a/docs/modules/Writer.ts.md b/docs/modules/Writer.ts.md
index 9dd32e87..602287c3 100644
--- a/docs/modules/Writer.ts.md
+++ b/docs/modules/Writer.ts.md
@@ -1,6 +1,6 @@
 ---
 title: Writer.ts
-nav_order: 98
+nav_order: 99
 parent: Modules
 ---
 
diff --git a/docs/modules/Zipper.ts.md b/docs/modules/Zipper.ts.md
index d0c1d42d..9641c252 100644
--- a/docs/modules/Zipper.ts.md
+++ b/docs/modules/Zipper.ts.md
@@ -1,6 +1,6 @@
 ---
 title: Zipper.ts
-nav_order: 99
+nav_order: 100
 parent: Modules
 ---
 
diff --git a/docs/modules/index.ts.md b/docs/modules/index.ts.md
index 2485d807..bbb49e76 100644
--- a/docs/modules/index.ts.md
+++ b/docs/modules/index.ts.md
@@ -58,6 +58,7 @@ parent: Modules
 - [ixMonad (export)](#ixmonad-export)
 - [joinSemilattice (export)](#joinsemilattice-export)
 - [lattice (export)](#lattice-export)
+- [magma (export)](#magma-export)
 - [map (export)](#map-export)
 - [meetSemilattice (export)](#meetsemilattice-export)
 - [monad (export)](#monad-export)
@@ -509,6 +510,14 @@ export { joinSemilattice }
 export { lattice }
 ```
 
+# magma (export)
+
+**Signature**
+
+```ts
+export { magma }
+```
+
 # map (export)
 
 **Signature**
diff --git a/src/IOEither.ts b/src/IOEither.ts
index 85f7ec0c..81c1de5c 100644
--- a/src/IOEither.ts
+++ b/src/IOEither.ts
@@ -151,6 +151,40 @@ export const tryCatch2v = <L, A>(f: Lazy<A>, onerror: (reason: unknown) => L): I
   return new IOEither(new IO(() => eitherTryCatch2v(f, onerror)))
 }
 
+/**
+ * Converts a JavaScript Object Notation (JSON) string into an object.
+ *
+ * @example
+ * import { parseJSON } from 'fp-ts/lib/IOEither'
+ * import { toError } from 'fp-ts/lib/Either'
+ *
+ * assert.deepStrictEqual(parseJSON('{"a":1}', toError).run().value, { a: 1 })
+ * assert.deepStrictEqual(parseJSON('{"a":}', toError).run().value, new SyntaxError('Unexpected token } in JSON at position 5'))
+ *
+ * @since 1.16.0
+ */
+export const parseJSON = <L>(s: string, onError: (reason: unknown) => L): IOEither<L, unknown> => {
+  return tryCatch2v(() => JSON.parse(s), onError)
+}
+
+/**
+ * Converts a JavaScript value to a JavaScript Object Notation (JSON) string.
+ *
+ * @example
+ * import { stringifyJSON } from 'fp-ts/lib/IOEither'
+ * import { toError } from 'fp-ts/lib/Either'
+ *
+ * assert.deepStrictEqual(stringifyJSON({ a: 1 }, toError).run().value, '{"a":1}')
+ * const circular: any = { ref: null }
+ * circular.ref = circular
+ * assert.deepStrictEqual(stringifyJSON(circular, toError).run().value, new TypeError('Converting circular structure to JSON'))
+ *
+ * @since 1.16.0
+ */
+export const stringifyJSON = <L>(u: unknown, onError: (reason: unknown) => L): IOEither<L, string> => {
+  return tryCatch2v(() => JSON.stringify(u), onError)
+}
+
 const throwError = fromLeft
 
 /**
diff --git a/src/Magma.ts b/src/Magma.ts
new file mode 100644
index 00000000..09a1a785
--- /dev/null
+++ b/src/Magma.ts
@@ -0,0 +1,7 @@
+/**
+ * A `Magma` is a pair `(A, concat)` in which `A` is a non-empty set and `concat` is a binary operation on `A`
+ * @since 1.16.0
+ */
+export interface Magma<A> {
+  readonly concat: (x: A, y: A) => A
+}
diff --git a/src/Map.ts b/src/Map.ts
index c4ed32b6..d557cacd 100644
--- a/src/Map.ts
+++ b/src/Map.ts
@@ -580,29 +580,29 @@ const filterWithIndex = <K, A>(fa: Map<K, A>, p: (k: K, a: A) => boolean): Map<K
 export function fromFoldable<K, F extends URIS3>(
   S: Setoid<K>,
   F: Foldable2v3<F>
-): <U, L, A>(ta: Type3<F, U, L, [K, A]>, f: (existing: A, a: A) => A) => Map<K, A>
+): <U, L, A>(ta: Type3<F, U, L, [K, A]>, onConflict: (existing: A, a: A) => A) => Map<K, A>
 export function fromFoldable<K, F extends URIS2>(
   S: Setoid<K>,
   F: Foldable2v2<F>
-): <L, A>(ta: Type2<F, L, [K, A]>, f: (existing: A, a: A) => A) => Map<K, A>
+): <L, A>(ta: Type2<F, L, [K, A]>, onConflict: (existing: A, a: A) => A) => Map<K, A>
 export function fromFoldable<K, F extends URIS>(
   S: Setoid<K>,
   F: Foldable2v1<F>
-): <A>(ta: Type<F, [K, A]>, f: (existing: A, a: A) => A) => Map<K, A>
+): <A>(ta: Type<F, [K, A]>, onConflict: (existing: A, a: A) => A) => Map<K, A>
 export function fromFoldable<K, F>(
   S: Setoid<K>,
   F: Foldable2v<F>
-): <A>(ta: HKT<F, [K, A]>, f: (existing: A, a: A) => A) => Map<K, A>
+): <A>(ta: HKT<F, [K, A]>, onConflict: (existing: A, a: A) => A) => Map<K, A>
 export function fromFoldable<K, F>(
   S: Setoid<K>,
   F: Foldable2v<F>
-): <A>(ta: HKT<F, [K, A]>, f: (existing: A, a: A) => A) => Map<K, A> {
-  return <A>(ta: HKT<F, [K, A]>, f: (existing: A, a: A) => A) => {
+): <A>(ta: HKT<F, [K, A]>, onConflict: (existing: A, a: A) => A) => Map<K, A> {
+  return <A>(ta: HKT<F, [K, A]>, onConflict: (existing: A, a: A) => A) => {
     const lookupWithKeyS = lookupWithKey(S)
     return F.reduce<[K, A], Map<K, A>>(ta, new Map<K, A>(), (b, [k, a]) => {
       const bOpt = lookupWithKeyS(k, b)
       if (bOpt.isSome()) {
-        b.set(bOpt.value[0], f(bOpt.value[1], a))
+        b.set(bOpt.value[0], onConflict(bOpt.value[1], a))
       } else {
         b.set(k, a)
       }
diff --git a/src/Record.ts b/src/Record.ts
index 94c05d0b..ecf87a16 100644
--- a/src/Record.ts
+++ b/src/Record.ts
@@ -4,11 +4,12 @@ import { Either } from './Either'
 import { Foldable, Foldable1, Foldable2, Foldable3 } from './Foldable'
 import { Predicate, Refinement } from './function'
 import { HKT, Type, Type2, Type3, URIS, URIS2, URIS3 } from './HKT'
+import { Magma } from './Magma'
 import { getDictionaryMonoid, Monoid } from './Monoid'
 import { none, Option, some as optionSome } from './Option'
-import { Setoid, fromEquals } from './Setoid'
-import { Unfoldable, Unfoldable1 } from './Unfoldable'
 import { Semigroup } from './Semigroup'
+import { fromEquals, Setoid } from './Setoid'
+import { Unfoldable, Unfoldable1 } from './Unfoldable'
 
 /**
  * Calculate the number of key/value pairs in a record
@@ -166,7 +167,7 @@ export function getMonoid<A>(S: Semigroup<A>): Monoid<Record<string, A>> {
 }
 
 /**
- * Lookup the value for a key in a dictionary
+ * Lookup the value for a key in a record
  * @since 1.10.0
  */
 export const lookup = <A>(key: string, fa: Record<string, A>): Option<A> => {
@@ -279,7 +280,7 @@ export function foldrWithKey<A, B>(fa: Record<string, A>, b: B, f: (k: string, a
 }
 
 /**
- * Create a dictionary with one key/value pair
+ * Create a record with one key/value pair
  *
  * @since 1.10.0
  */
@@ -624,28 +625,28 @@ export function filterWithKey<A>(fa: Record<string, A>, p: (key: string, a: A) =
 }
 
 /**
- * Create a dictionary from a foldable collection of key/value pairs, using the
+ * Create a record from a foldable collection of key/value pairs, using the
  * specified function to combine values for duplicate keys.
  *
  * @since 1.10.0
  */
 export function fromFoldable<F extends URIS3>(
   F: Foldable3<F>
-): <K extends string, U, L, A>(ta: Type3<F, U, L, [K, A]>, f: (existing: A, a: A) => A) => Record<K, A>
+): <K extends string, U, L, A>(ta: Type3<F, U, L, [K, A]>, onConflict: (existing: A, a: A) => A) => Record<K, A>
 export function fromFoldable<F extends URIS2>(
   F: Foldable2<F>
-): <K extends string, L, A>(ta: Type2<F, L, [K, A]>, f: (existing: A, a: A) => A) => Record<K, A>
+): <K extends string, L, A>(ta: Type2<F, L, [K, A]>, onConflict: (existing: A, a: A) => A) => Record<K, A>
 export function fromFoldable<F extends URIS>(
   F: Foldable1<F>
-): <K extends string, A>(ta: Type<F, [K, A]>, f: (existing: A, a: A) => A) => Record<K, A>
+): <K extends string, A>(ta: Type<F, [K, A]>, onConflict: (existing: A, a: A) => A) => Record<K, A>
 export function fromFoldable<F>(
   // tslint:disable-next-line: deprecation
   F: Foldable<F>
-): <K extends string, A>(ta: HKT<F, [K, A]>, f: (existing: A, a: A) => A) => Record<K, A>
+): <K extends string, A>(ta: HKT<F, [K, A]>, onConflict: (existing: A, a: A) => A) => Record<K, A>
 export function fromFoldable<F>(
   // tslint:disable-next-line: deprecation
   F: Foldable<F>
-): <A>(ta: HKT<F, [string, A]>, f: (existing: A, a: A) => A) => Record<string, A> {
+): <A>(ta: HKT<F, [string, A]>, onConflict: (existing: A, a: A) => A) => Record<string, A> {
   return <A>(ta: HKT<F, [string, A]>, f: (existing: A, a: A) => A) => {
     return F.reduce<[string, A], Record<string, A>>(ta, {}, (b, [k, a]) => {
       b[k] = b.hasOwnProperty(k) ? f(b[k], a) : a
@@ -654,6 +655,74 @@ export function fromFoldable<F>(
   }
 }
 
+/**
+ * Create a record from a foldable collection using the specified functions to
+ *
+ * - map to key/value pairs
+ * - combine values for duplicate keys.
+ *
+ * @example
+ * import { getLastSemigroup } from 'fp-ts/lib/Semigroup'
+ * import { array, zip } from 'fp-ts/lib/Array'
+ * import { identity } from 'fp-ts/lib/function'
+ * import { fromFoldableMap } from 'fp-ts/lib/Record'
+ *
+ * // like lodash `zipObject` or ramda `zipObj`
+ * export const zipObject = <K extends string, A>(keys: Array<K>, values: Array<A>): Record<K, A> =>
+ *   fromFoldableMap(getLastSemigroup<A>(), array)(zip(keys, values), identity)
+ *
+ * assert.deepStrictEqual(zipObject(['a', 'b'], [1, 2, 3]), { a: 1, b: 2 })
+ *
+ * // build a record from a field
+ * interface User {
+ *   id: string
+ *   name: string
+ * }
+ *
+ * const users: Array<User> = [
+ *   { id: 'id1', name: 'name1' },
+ *   { id: 'id2', name: 'name2' },
+ *   { id: 'id1', name: 'name3' }
+ * ]
+ *
+ * assert.deepStrictEqual(fromFoldableMap(getLastSemigroup<User>(), array)(users, user => [user.id, user]), {
+ *   id1: { id: 'id1', name: 'name3' },
+ *   id2: { id: 'id2', name: 'name2' }
+ * })
+ *
+ * @since 1.16.0
+ */
+export function fromFoldableMap<F extends URIS3, B>(
+  M: Magma<B>,
+  F: Foldable3<F>
+): <U, L, A, K extends string>(ta: Type3<F, U, L, A>, f: (a: A) => [K, B]) => Record<K, B>
+export function fromFoldableMap<F extends URIS2, B>(
+  M: Magma<B>,
+  F: Foldable2<F>
+): <L, A, K extends string>(ta: Type2<F, L, A>, f: (a: A) => [K, B]) => Record<K, B>
+export function fromFoldableMap<F extends URIS, B>(
+  M: Magma<B>,
+  F: Foldable1<F>
+): <A, K extends string>(ta: Type<F, A>, f: (a: A) => [K, B]) => Record<K, B>
+export function fromFoldableMap<F, B>(
+  M: Magma<B>,
+  // tslint:disable-next-line: deprecation
+  F: Foldable<F>
+): <A, K extends string>(ta: HKT<F, A>, f: (a: A) => [K, B]) => Record<K, B>
+export function fromFoldableMap<F, B>(
+  M: Magma<B>,
+  // tslint:disable-next-line: deprecation
+  F: Foldable<F>
+): <A>(ta: HKT<F, A>, f: (a: A) => [string, B]) => Record<string, B> {
+  return <A>(ta: HKT<F, A>, f: (a: A) => [string, B]) => {
+    return F.reduce<A, Record<string, B>>(ta, {}, (r, a) => {
+      const [k, b] = f(a)
+      r[k] = r.hasOwnProperty(k) ? M.concat(r[k], b) : b
+      return r
+    })
+  }
+}
+
 /**
  * @since 1.14.0
  */
diff --git a/src/Semigroup.ts b/src/Semigroup.ts
index 096c7542..0735e965 100644
--- a/src/Semigroup.ts
+++ b/src/Semigroup.ts
@@ -3,13 +3,16 @@
  */
 import { Ord, max, min } from './Ord'
 import { concat, identity } from './function'
+import { Magma } from './Magma'
 
 /**
+ * A `Semigroup` is a `Magma` where `concat` is associative, that is:
+ *
+ * Associativiy: `concat(concat(x, y), z) = concat(x, concat(y, z))`
+ *
  * @since 1.0.0
  */
-export interface Semigroup<A> {
-  readonly concat: (x: A, y: A) => A
-}
+export interface Semigroup<A> extends Magma<A> {}
 
 /**
  * @since 1.0.0
diff --git a/src/StrMap.ts b/src/StrMap.ts
index ea10b260..2233ef14 100644
--- a/src/StrMap.ts
+++ b/src/StrMap.ts
@@ -327,21 +327,23 @@ export const lookup = <A>(k: string, d: StrMap<A>): Option<A> => {
  */
 export function fromFoldable<F extends URIS3>(
   F: Foldable3<F>
-): <U, L, A>(ta: Type3<F, U, L, [string, A]>, f: (existing: A, a: A) => A) => StrMap<A>
+): <U, L, A>(ta: Type3<F, U, L, [string, A]>, onConflict: (existing: A, a: A) => A) => StrMap<A>
 export function fromFoldable<F extends URIS2>(
   F: Foldable2<F>
-): <L, A>(ta: Type2<F, L, [string, A]>, f: (existing: A, a: A) => A) => StrMap<A>
+): <L, A>(ta: Type2<F, L, [string, A]>, onConflict: (existing: A, a: A) => A) => StrMap<A>
 export function fromFoldable<F extends URIS>(
   F: Foldable1<F>
-): <A>(ta: Type<F, [string, A]>, f: (existing: A, a: A) => A) => StrMap<A>
-// tslint:disable-next-line: deprecation
-export function fromFoldable<F>(F: Foldable<F>): <A>(ta: HKT<F, [string, A]>, f: (existing: A, a: A) => A) => StrMap<A>
+): <A>(ta: Type<F, [string, A]>, onConflict: (existing: A, a: A) => A) => StrMap<A>
+export function fromFoldable<F>(
+  // tslint:disable-next-line: deprecation
+  F: Foldable<F>
+): <A>(ta: HKT<F, [string, A]>, onConflict: (existing: A, a: A) => A) => StrMap<A>
 export function fromFoldable<F>(
   // tslint:disable-next-line: deprecation
   F: Foldable<F>
-): <A>(ta: HKT<F, [string, A]>, f: (existing: A, a: A) => A) => StrMap<A> {
+): <A>(ta: HKT<F, [string, A]>, onConflict: (existing: A, a: A) => A) => StrMap<A> {
   const fromFoldableF = R.fromFoldable(F)
-  return (ta, f) => new StrMap(fromFoldableF(ta, f))
+  return (ta, onConflict) => new StrMap(fromFoldableF(ta, onConflict))
 }
 
 /**
diff --git a/src/index.ts b/src/index.ts
index c1e672cd..73c408bc 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -96,6 +96,8 @@ import * as joinSemilattice from './JoinSemilattice'
 export { joinSemilattice }
 import * as lattice from './Lattice'
 export { lattice }
+import * as magma from './Magma'
+export { magma }
 import * as map from './Map'
 export { map }
 import * as meetSemilattice from './MeetSemilattice'


Test patch:
diff --git a/test/IOEither.ts b/test/IOEither.ts
index db21c593..3e22f4f4 100644
--- a/test/IOEither.ts
+++ b/test/IOEither.ts
@@ -1,7 +1,7 @@
 import * as assert from 'assert'
-import { left as eitherLeft, right as eitherRight } from '../src/Either'
+import { left as eitherLeft, right as eitherRight, toError } from '../src/Either'
 import { IO, io } from '../src/IO'
-import { IOEither, fromLeft, left, right, ioEither, tryCatch } from '../src/IOEither'
+import { IOEither, fromLeft, left, right, ioEither, tryCatch, parseJSON, stringifyJSON } from '../src/IOEither'
 import { none, some } from '../src/Option'
 
 describe('IOEither', () => {
@@ -171,4 +171,28 @@ describe('IOEither', () => {
       assert.deepStrictEqual(ioEither.fromOption(some(1), 'error').run(), eitherRight(1))
     })
   })
+
+  it('parseJSON', () => {
+    assert.deepStrictEqual(parseJSON('{"a":1}', toError).run().value, { a: 1 })
+    assert.deepStrictEqual(
+      parseJSON('{"a":}', toError).run().value,
+      new SyntaxError('Unexpected token } in JSON at position 5')
+    )
+  })
+
+  it('stringifyJSON', () => {
+    assert.deepStrictEqual(stringifyJSON({ a: 1 }, toError).run().value, '{"a":1}')
+    const circular: any = { ref: null }
+    circular.ref = circular
+    assert.deepStrictEqual(
+      stringifyJSON(circular, toError).run().value,
+      new TypeError('Converting circular structure to JSON')
+    )
+    interface Person {
+      name: string
+      age: number
+    }
+    const person: Person = { name: 'Giulio', age: 45 }
+    assert.deepStrictEqual(stringifyJSON(person, toError).run().value, '{"name":"Giulio","age":45}')
+  })
 })
diff --git a/test/Record.ts b/test/Record.ts
index c7e388f9..f397af7e 100644
--- a/test/Record.ts
+++ b/test/Record.ts
@@ -1,11 +1,11 @@
 import * as assert from 'assert'
 import * as R from '../src/Record'
-import { semigroupSum } from '../src/Semigroup'
+import { semigroupSum, getLastSemigroup } from '../src/Semigroup'
 import { monoidString } from '../src/Monoid'
 import { identity } from '../src/function'
 import { option, some, none, Option } from '../src/Option'
 import { setoidNumber } from '../src/Setoid'
-import { array } from '../src/Array'
+import { array, zip } from '../src/Array'
 import { left, right } from '../src/Either'
 import * as I from '../src/Identity'
 
@@ -274,4 +274,27 @@ describe('Record', () => {
     // tslint:disable-next-line: deprecation
     assert.deepStrictEqual(R.filterWithIndex(x, f), expected)
   })
+
+  it('fromFoldableMap', () => {
+    const zipObject = <K extends string, A>(keys: Array<K>, values: Array<A>): Record<K, A> =>
+      R.fromFoldableMap(getLastSemigroup<A>(), array)(zip(keys, values), identity)
+
+    assert.deepStrictEqual(zipObject(['a', 'b'], [1, 2, 3]), { a: 1, b: 2 })
+
+    interface User {
+      id: string
+      name: string
+    }
+
+    const users: Array<User> = [
+      { id: 'id1', name: 'name1' },
+      { id: 'id2', name: 'name2' },
+      { id: 'id1', name: 'name3' }
+    ]
+
+    assert.deepStrictEqual(R.fromFoldableMap(getLastSemigroup<User>(), array)(users, user => [user.id, user]), {
+      id1: { id: 'id1', name: 'name3' },
+      id2: { id: 'id2', name: 'name2' }
+    })
+  })
 })