Repository: gcanti/fp-ts
Pull request #1234 (2020-06-09 09:21:14)

version 2.6.4


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 5ce393c5..7344174f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -14,6 +14,14 @@
 **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.
 
+# 2.6.4
+
+- **Bug Fix**
+  - `ReadonlyMap`
+    - `traverseWithIndex` should sort the keys (@gcanti)
+  - `ReadonlyRecord`
+    - `traverseWithIndex` should sort the keys (@gcanti)
+
 # 2.6.3
 
 - **Polish**
diff --git a/package.json b/package.json
index 3dec0c3f..b16f3641 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "fp-ts",
-  "version": "2.6.3",
+  "version": "2.6.4",
   "description": "Functional programming in TypeScript",
   "files": [
     "lib",
diff --git a/src/ReadonlyMap.ts b/src/ReadonlyMap.ts
index fbcb3c6e..56ebb1d9 100644
--- a/src/ReadonlyMap.ts
+++ b/src/ReadonlyMap.ts
@@ -720,14 +720,14 @@ export function getWitherable<K>(O: Ord<K>): Witherable2C<URI, K> & TraversableW
 
   const traverseWithIndex = <F>(
     F: Applicative<F>
-  ): (<K, A, B>(ta: ReadonlyMap<K, A>, f: (k: K, a: A) => HKT<F, B>) => HKT<F, ReadonlyMap<K, B>>) => {
-    return <K, A, B>(ta: ReadonlyMap<K, A>, f: (k: K, a: A) => HKT<F, B>) => {
+  ): (<A, B>(ta: ReadonlyMap<K, A>, f: (k: K, a: A) => HKT<F, B>) => HKT<F, ReadonlyMap<K, B>>) => {
+    return <A, B>(ta: ReadonlyMap<K, A>, f: (k: K, a: A) => HKT<F, B>) => {
       let fm: HKT<F, ReadonlyMap<K, B>> = F.of(empty)
-      const entries = ta.entries()
-      let e: Next<readonly [K, A]>
-      // tslint:disable-next-line: strict-boolean-expressions
-      while (!(e = entries.next()).done) {
-        const [key, a] = e.value
+      const ks = keysO(ta)
+      const len = ks.length
+      for (let i = 0; i < len; i++) {
+        const key = ks[i]
+        const a = ta.get(key)!
         fm = F.ap(
           F.map(fm, (m) => (b: B) => new Map(m).set(key, b)),
           f(key, a)
@@ -739,12 +739,12 @@ export function getWitherable<K>(O: Ord<K>): Witherable2C<URI, K> & TraversableW
 
   const traverse = <F>(
     F: Applicative<F>
-  ): (<K, A, B>(ta: ReadonlyMap<K, A>, f: (a: A) => HKT<F, B>) => HKT<F, ReadonlyMap<K, B>>) => {
+  ): (<A, B>(ta: ReadonlyMap<K, A>, f: (a: A) => HKT<F, B>) => HKT<F, ReadonlyMap<K, B>>) => {
     const traverseWithIndexF = traverseWithIndex(F)
     return (ta, f) => traverseWithIndexF(ta, (_, a) => f(a))
   }
 
-  const sequence = <F>(F: Applicative<F>): (<K, A>(ta: ReadonlyMap<K, HKT<F, A>>) => HKT<F, ReadonlyMap<K, A>>) => {
+  const sequence = <F>(F: Applicative<F>): (<A>(ta: ReadonlyMap<K, HKT<F, A>>) => HKT<F, ReadonlyMap<K, A>>) => {
     const traverseWithIndexF = traverseWithIndex(F)
     return (ta) => traverseWithIndexF(ta, (_, a) => a)
   }
@@ -774,7 +774,7 @@ export function getWitherable<K>(O: Ord<K>): Witherable2C<URI, K> & TraversableW
     traverseWithIndex,
     wilt: <F>(
       F: Applicative<F>
-    ): (<K, A, B, C>(
+    ): (<A, B, C>(
       wa: ReadonlyMap<K, A>,
       f: (a: A) => HKT<F, Either<B, C>>
     ) => HKT<F, Separated<ReadonlyMap<K, B>, ReadonlyMap<K, C>>>) => {
@@ -783,7 +783,7 @@ export function getWitherable<K>(O: Ord<K>): Witherable2C<URI, K> & TraversableW
     },
     wither: <F>(
       F: Applicative<F>
-    ): (<K, A, B>(wa: ReadonlyMap<K, A>, f: (a: A) => HKT<F, Option<B>>) => HKT<F, ReadonlyMap<K, B>>) => {
+    ): (<A, B>(wa: ReadonlyMap<K, A>, f: (a: A) => HKT<F, Option<B>>) => HKT<F, ReadonlyMap<K, B>>) => {
       const traverseF = traverse(F)
       return (wa, f) => F.map(traverseF(wa, f), compact)
     }
diff --git a/src/ReadonlyRecord.ts b/src/ReadonlyRecord.ts
index 79fa4e7b..583abc0e 100644
--- a/src/ReadonlyRecord.ts
+++ b/src/ReadonlyRecord.ts
@@ -765,10 +765,10 @@ const reduceWithIndex_: <A, B>(fa: Readonly<Record<string, A>>, b: B, f: (i: str
   f
 ) => {
   let out = b
-  const keys = Object.keys(fa).sort()
-  const len = keys.length
+  const ks = keys(fa)
+  const len = ks.length
   for (let i = 0; i < len; i++) {
-    const k = keys[i]
+    const k = ks[i]
     out = f(k, out, fa[k])
   }
   return out
@@ -778,10 +778,10 @@ const foldMapWithIndex_: <M>(M: Monoid<M>) => <A>(fa: Readonly<Record<string, A>
   M
 ) => (fa, f) => {
   let out = M.empty
-  const keys = Object.keys(fa).sort()
-  const len = keys.length
+  const ks = keys(fa)
+  const len = ks.length
   for (let i = 0; i < len; i++) {
-    const k = keys[i]
+    const k = ks[i]
     out = M.concat(out, f(k, fa[k]))
   }
   return out
@@ -793,10 +793,10 @@ const reduceRightWithIndex_: <A, B>(fa: Readonly<Record<string, A>>, b: B, f: (i
   f
 ) => {
   let out = b
-  const keys = Object.keys(fa).sort()
-  const len = keys.length
+  const ks = keys(fa)
+  const len = ks.length
   for (let i = len - 1; i >= 0; i--) {
-    const k = keys[i]
+    const k = ks[i]
     out = f(k, fa[k], out)
   }
   return out
@@ -879,12 +879,12 @@ const traverseWithIndex_ = <F>(F: Applicative<F>) => <A, B>(
   ta: ReadonlyRecord<string, A>,
   f: (k: string, a: A) => HKT<F, B>
 ) => {
-  const keys = Object.keys(ta)
-  if (keys.length === 0) {
+  const ks = keys(ta)
+  if (ks.length === 0) {
     return F.of(empty)
   }
   let fr: HKT<F, Record<string, B>> = F.of({})
-  for (const key of keys) {
+  for (const key of ks) {
     fr = F.ap(
       F.map(fr, (r) => (b: B) => {
         r[key] = b


Test patch:
diff --git a/test/ReadonlyMap.ts b/test/ReadonlyMap.ts
index af3c7a3a..a8a50f42 100644
--- a/test/ReadonlyMap.ts
+++ b/test/ReadonlyMap.ts
@@ -4,6 +4,7 @@ import { Either, left, right } from '../src/Either'
 import { Eq, eqNumber, fromEquals } from '../src/Eq'
 import { identity, pipe, Refinement } from '../src/function'
 import * as I from '../src/Identity'
+import * as IO from '../src/IO'
 import { monoidString } from '../src/Monoid'
 import { none, option, Option, some } from '../src/Option'
 import { fromCompare, ord, ordNumber, ordString } from '../src/Ord'
@@ -742,6 +743,24 @@ describe('ReadonlyMap', () => {
   describe('getWitherable', () => {
     const W = _.getWitherable(ordUser)
 
+    it('traverseWithIndex should sort the keys', () => {
+      const W = _.getWitherable(ordString)
+      // tslint:disable-next-line: readonly-array
+      const log: Array<string> = []
+      const append = (message: string): IO.IO<void> => () => {
+        log.push(message)
+      }
+
+      W.traverseWithIndex(IO.io)(
+        new Map([
+          ['b', append('b')],
+          ['a', append('a')]
+        ]),
+        (_, io) => io
+      )()
+      assert.deepStrictEqual(log, ['a', 'b'])
+    })
+
     it('mapWithIndex', () => {
       const mapWithIndex = W.mapWithIndex
       const aa1 = new Map<User, number>([[{ id: 'aa' }, 1]])
diff --git a/test/ReadonlyRecord.ts b/test/ReadonlyRecord.ts
index 3e3db6df..0e09f374 100644
--- a/test/ReadonlyRecord.ts
+++ b/test/ReadonlyRecord.ts
@@ -3,6 +3,7 @@ import { left, right } from '../src/Either'
 import { eqNumber } from '../src/Eq'
 import { identity, pipe } from '../src/function'
 import * as I from '../src/Identity'
+import * as IO from '../src/IO'
 import { monoidString } from '../src/Monoid'
 import { getOrElse, isSome, none, option, Option, some } from '../src/Option'
 import { readonlyArray, zip } from '../src/ReadonlyArray'
@@ -272,6 +273,20 @@ describe('ReadonlyRecord', () => {
     assert.deepStrictEqual(_.toUnfoldable(readonlyArray)({ a: 1 }), [['a', 1]])
   })
 
+  it('traverseWithIndex should sort the keys', () => {
+    // tslint:disable-next-line: readonly-array
+    const log: Array<string> = []
+    const append = (message: string): IO.IO<void> => () => {
+      log.push(message)
+    }
+
+    pipe(
+      { b: append('b'), a: append('a') },
+      _.traverseWithIndex(IO.io)((_, io) => io)
+    )()
+    assert.deepStrictEqual(log, ['a', 'b'])
+  })
+
   it('traverseWithIndex', () => {
     const d1 = { k1: 1, k2: 2 }
     const t1 = _.traverseWithIndex(option)((k, n: number): Option<number> => (k !== 'k1' ? some(n) : none))(d1)