Repository: gcanti/fp-ts (ts)

Issue:
ado
This is a feature proposal.

I'm not sure if that repo would collect control flow helpers, but seeing array comprehensions, it may actually be the case.

Motivation:

Combining several applicatives can be very verbose.
I've recently tried to use the solution below to ease imitating an applicative do (just some structural typings on top of Record sequence).

Usage

```typescript
  const ado = sequenceRecord(either)
  ado({
    availability: parseAvailability(ticket), // Either monad base result
    rooms: parseRooms(ticket.rooming) // Either monad base result
  }).map(({ availability, rooms }) => myResult(
    availability,
    rooms
  ))
```

Note that one may take a better advantage of `Validation` from which you can enforce the error type via an `Applicative2C`

Code

```typescript
import {
  Applicative,
  Applicative1,
  Applicative2,
  Applicative2C,
  Applicative3,
  Applicative3C
  } from 'fp-ts/lib/Applicative'
import {
  HKT,
  Type,
  Type2,
  Type3,
  URIS,
  URIS2,
  URIS3
  } from 'fp-ts/lib/HKT'
import { sequence } from 'fp-ts/lib/Record'

// tslint:disable: only-arrow-functions
export function sequenceRecord<F extends URIS3>(
  F: Applicative3<F>
): (<U, L, A extends Record<string, Type3<F, U, L, any>>>(
  a: A
) => Type3<
  F,
  { [k in keyof typeof a]: typeof a[k]['_U'] }[keyof typeof a],
  { [k in keyof typeof a]: typeof a[k]['_L'] }[keyof typeof a],
  { [k in keyof typeof a]: typeof a[k]['_A'] }
>)
export function sequenceRecord<F extends URIS3, U, L>(
  F: Applicative3C<F, U, L>
): (<A extends Record<string, Type3<F, U, L, any>>>(
  a: A
) => Type3<
  F,
  { [k in keyof typeof a]: typeof a[k]['_U'] }[keyof typeof a],
  { [k in keyof typeof a]: typeof a[k]['_L'] }[keyof typeof a],
  { [k in keyof typeof a]: typeof a[k]['_A'] }
>)
export function sequenceRecord<F extends URIS2>(
  F: Applicative2<F>
): (<L, A extends Record<string, Type2<F, L, any>>>(
  a: A
) => Type2<
  F,
  { [k in keyof typeof a]: typeof a[k]['_L'] }[keyof typeof a],
  { [k in keyof typeof a]: typeof a[k]['_A'] }
>)
export function sequenceRecord<F extends URIS2, L>(
  F: Applicative2C<F, L>
): (<A extends Record<string, Type2<F, L, any>>>(
  a: A
) => Type2<
  F,
  { [k in keyof typeof a]: typeof a[k]['_L'] }[keyof typeof a],
  { [k in keyof typeof a]: typeof a[k]['_A'] }
>)
export function sequenceRecord<F extends URIS>(
  F: Applicative1<F>
): (<A extends Record<string, Type<F, A>>>(
  a: A
) => Type<F, { [k in keyof typeof a]: typeof a[k]['_A'] }>)
export function sequenceRecord<F extends URIS>(
  F: Applicative<F>
): (<A extends Record<string, HKT<F, A>>>(
  a: A
) => Type<F, { [k in keyof typeof a]: typeof a[k]['_A'] }>)
export function sequenceRecord<F extends URIS>(
  F: Applicative<F>
): (<A extends Record<string, HKT<F, any>>>(
  a: A
) => Type<F, { [k in keyof typeof a]: typeof a[k]['_A'] }>) {
  return sequence(F) as any
}

```


Pull request:
New Feature: add sequenceS, closes #688
/cc @sledorze @mattiamanzati

Patch:
diff --git a/docs/modules/Apply.ts.md b/docs/modules/Apply.ts.md
index 35b294ab..da1de321 100644
--- a/docs/modules/Apply.ts.md
+++ b/docs/modules/Apply.ts.md
@@ -39,6 +39,7 @@ Formally, `Apply` represents a strong lax semi-monoidal endofunctor.
 - [liftA2 (function)](#lifta2-function)
 - [liftA3 (function)](#lifta3-function)
 - [liftA4 (function)](#lifta4-function)
+- [sequenceS (function)](#sequences-function)
 - [sequenceT (function)](#sequencet-function)
 
 ---
@@ -572,6 +573,67 @@ export function liftA4<F>(
 
 Added in v1.0.0
 
+# sequenceS (function)
+
+Like `Apply.sequenceT` but works with structs instead of tuples.
+
+**Signature**
+
+```ts
+export function sequenceS<F extends URIS3>(
+  F: Apply3<F>
+): <U, L, R extends Record<string, Type3<F, U, L, any>>>(
+  r: EnforceNonEmptyRecord<R> & Record<string, Type3<F, U, L, any>>
+) => Type3<F, U, L, { [K in keyof R]: R[K] extends Type3<F, any, any, infer A> ? A : never }>
+export function sequenceS<F extends URIS3, U, L>(
+  F: Apply3C<F, U, L>
+): <R extends Record<string, Type3<F, U, L, any>>>(
+  r: EnforceNonEmptyRecord<R>
+) => Type3<F, U, L, { [K in keyof R]: R[K] extends Type3<F, any, any, infer A> ? A : never }>
+export function sequenceS<F extends URIS2>(
+  F: Apply2<F>
+): <L, R extends Record<string, Type2<F, L, any>>>(
+  r: EnforceNonEmptyRecord<R> & Record<string, Type2<F, L, any>>
+) => Type2<F, L, { [K in keyof R]: R[K] extends Type2<F, any, infer A> ? A : never }>
+export function sequenceS<F extends URIS2, L>(
+  F: Apply2C<F, L>
+): <R extends Record<string, Type2<F, L, any>>>(
+  r: EnforceNonEmptyRecord<R>
+) => Type2<F, L, { [K in keyof R]: R[K] extends Type2<F, any, infer A> ? A : never }>
+export function sequenceS<F extends URIS>(
+  F: Apply1<F>
+): <R extends Record<string, Type<F, any>>>(
+  r: EnforceNonEmptyRecord<R>
+) => Type<F, { [K in keyof R]: R[K] extends Type<F, infer A> ? A : never }>
+export function sequenceS<F>(F: Apply<F>): (r: Record<string, HKT<F, any>>) => HKT<F, Record<string, any>> { ... }
+```
+
+**Example**
+
+```ts
+import { either, right, left } from 'fp-ts/lib/Either'
+import { sequenceS } from 'fp-ts/lib/Apply'
+
+const ado = sequenceS(either)
+
+assert.deepStrictEqual(
+  ado({
+    a: right<string, number>(1),
+    b: right<string, boolean>(true)
+  }),
+  right({ a: 1, b: true })
+)
+assert.deepStrictEqual(
+  ado({
+    a: right<string, number>(1),
+    b: left<string, number>('error')
+  }),
+  left('error')
+)
+```
+
+Added in v1.15.0
+
 # sequenceT (function)
 
 Tuple sequencing, i.e., take a tuple of monadic actions and do them from left-to-right, returning the resulting tuple.
diff --git a/dtslint/index.d.ts b/dtslint/index.d.ts
index 005ae20b..6405dafc 100644
--- a/dtslint/index.d.ts
+++ b/dtslint/index.d.ts
@@ -1,1 +1,1 @@
-// TypeScript Version: 3.0
+// TypeScript Version: 3.1
diff --git a/dtslint/ts3.0/index.d.ts b/dtslint/ts3.1/index.d.ts
similarity index 100%
rename from dtslint/ts3.0/index.d.ts
rename to dtslint/ts3.1/index.d.ts
diff --git a/dtslint/ts3.0/index.ts b/dtslint/ts3.1/index.ts
similarity index 89%
rename from dtslint/ts3.0/index.ts
rename to dtslint/ts3.1/index.ts
index 7c082dad..c688d2ce 100644
--- a/dtslint/ts3.0/index.ts
+++ b/dtslint/ts3.1/index.ts
@@ -61,6 +61,36 @@ Tr.sequence(V.getApplicative(S.semigroupString), E.either) // $ExpectType <LT, A
 const applicativeValidation = V.getApplicative(S.semigroupString)
 Apy.liftA2(applicativeValidation) // $ExpectType <A, B, C>(f: Curried2<A, B, C>) => (fa: Validation<string, A>) => (fb: Validation<string, B>) => Validation<string, C>
 
+// sequenceS
+
+declare const sequenceS1: E.Either<string, number>
+declare const sequenceS2: E.Either<string, string>
+declare const sequenceS3: E.Either<string, boolean>
+declare const sequenceS4: E.Either<boolean, void>
+
+const sequenceSf1 = Apy.sequenceS(E.either)
+
+// $ExpectError
+sequenceSf1({})
+// $ExpectError
+sequenceSf1({ sequenceS1, sequenceS4 })
+
+sequenceSf1({ sequenceS1, sequenceS2, sequenceS3 }) // $ExpectType Either<string, { sequenceS1: number; sequenceS2: string; sequenceS3: boolean; }>
+
+const sequenceSf2 = Apy.sequenceS(RTE.readerTaskEither)
+declare const sequenceS5: RTE.ReaderTaskEither<{ a: number }, string, number>
+declare const sequenceS6: RTE.ReaderTaskEither<{ a: number }, string, string>
+declare const sequenceS7: RTE.ReaderTaskEither<{ a: number }, string, boolean>
+declare const sequenceS8: RTE.ReaderTaskEither<{ a: number }, boolean, void>
+declare const sequenceS9: RTE.ReaderTaskEither<{ a: string }, string, void>
+
+// $ExpectError
+sequenceSf2({ sequenceS5, sequenceS8 })
+// $ExpectError
+sequenceSf2({ sequenceS5, sequenceS9 })
+
+sequenceSf2({ sequenceS5, sequenceS6, sequenceS7 }) // $ExpectType ReaderTaskEither<{ a: number; }, string, { sequenceS5: number; sequenceS6: string; sequenceS7: boolean; }>
+
 //
 // Unfoldable
 //
diff --git a/dtslint/ts3.0/tsconfig.json b/dtslint/ts3.1/tsconfig.json
similarity index 100%
rename from dtslint/ts3.0/tsconfig.json
rename to dtslint/ts3.1/tsconfig.json
diff --git a/dtslint/ts3.0/tslint.json b/dtslint/ts3.1/tslint.json
similarity index 100%
rename from dtslint/ts3.0/tslint.json
rename to dtslint/ts3.1/tslint.json
diff --git a/src/Apply.ts b/src/Apply.ts
index c257225f..3895901b 100644
--- a/src/Apply.ts
+++ b/src/Apply.ts
@@ -496,3 +496,76 @@ export function sequenceT<F>(F: Apply<F>): SequenceT<F> {
     return r
   }
 }
+
+type EnforceNonEmptyRecord<R> = keyof R extends never ? never : R
+
+/**
+ * Like `Apply.sequenceT` but works with structs instead of tuples.
+ *
+ * @example
+ * import { either, right, left } from 'fp-ts/lib/Either'
+ * import { sequenceS } from 'fp-ts/lib/Apply'
+ *
+ * const ado = sequenceS(either)
+ *
+ * assert.deepStrictEqual(
+ *   ado({
+ *     a: right<string, number>(1),
+ *     b: right<string, boolean>(true)
+ *   }),
+ *   right({ a: 1, b: true })
+ * )
+ * assert.deepStrictEqual(
+ *   ado({
+ *     a: right<string, number>(1),
+ *     b: left<string, number>('error')
+ *   }),
+ *   left('error')
+ * )
+ *
+ * @since 1.15.0
+ */
+export function sequenceS<F extends URIS3>(
+  F: Apply3<F>
+): <U, L, R extends Record<string, Type3<F, U, L, any>>>(
+  r: EnforceNonEmptyRecord<R> & Record<string, Type3<F, U, L, any>>
+) => Type3<F, U, L, { [K in keyof R]: R[K] extends Type3<F, any, any, infer A> ? A : never }>
+export function sequenceS<F extends URIS3, U, L>(
+  F: Apply3C<F, U, L>
+): <R extends Record<string, Type3<F, U, L, any>>>(
+  r: EnforceNonEmptyRecord<R>
+) => Type3<F, U, L, { [K in keyof R]: R[K] extends Type3<F, any, any, infer A> ? A : never }>
+export function sequenceS<F extends URIS2>(
+  F: Apply2<F>
+): <L, R extends Record<string, Type2<F, L, any>>>(
+  r: EnforceNonEmptyRecord<R> & Record<string, Type2<F, L, any>>
+) => Type2<F, L, { [K in keyof R]: R[K] extends Type2<F, any, infer A> ? A : never }>
+export function sequenceS<F extends URIS2, L>(
+  F: Apply2C<F, L>
+): <R extends Record<string, Type2<F, L, any>>>(
+  r: EnforceNonEmptyRecord<R>
+) => Type2<F, L, { [K in keyof R]: R[K] extends Type2<F, any, infer A> ? A : never }>
+export function sequenceS<F extends URIS>(
+  F: Apply1<F>
+): <R extends Record<string, Type<F, any>>>(
+  r: EnforceNonEmptyRecord<R>
+) => Type<F, { [K in keyof R]: R[K] extends Type<F, infer A> ? A : never }>
+export function sequenceS<F>(F: Apply<F>): (r: Record<string, HKT<F, any>>) => HKT<F, Record<string, any>>
+export function sequenceS<F>(F: Apply<F>): (r: Record<string, HKT<F, any>>) => HKT<F, Record<string, any>> {
+  return r => {
+    const keys = Object.keys(r)
+    const fst = keys[0]
+    const others = keys.slice(1)
+    let fr: HKT<F, Record<string, any>> = F.map(r[fst], a => ({ [fst]: a }))
+    for (const key of others) {
+      fr = F.ap(
+        F.map(fr, r => (a: any) => {
+          r[key] = a
+          return r
+        }),
+        r[key]
+      )
+    }
+    return fr
+  }
+}


Test patch:
diff --git a/test/Apply.ts b/test/Apply.ts
index b3193d25..d029eb0b 100644
--- a/test/Apply.ts
+++ b/test/Apply.ts
@@ -1,5 +1,5 @@
 import * as assert from 'assert'
-import { applyFirst, applySecond, liftA2, liftA3, liftA4, sequenceT } from '../src/Apply'
+import { applyFirst, applySecond, liftA2, liftA3, liftA4, sequenceT, sequenceS } from '../src/Apply'
 import { either, left, right } from '../src/Either'
 import { none, option, some } from '../src/Option'
 
@@ -72,4 +72,16 @@ describe('Apply', () => {
     assert.deepStrictEqual(sequenceTEither(right(1), right('2')), right([1, '2']))
     assert.deepStrictEqual(sequenceTEither(right(1), right('2'), left('foo')), left('foo'))
   })
+
+  it('sequenceS', () => {
+    const adoOption = sequenceS(option)
+    assert.deepStrictEqual(adoOption({ a: some(1) }), some({ a: 1 }))
+    assert.deepStrictEqual(adoOption({ a: some(1), b: some(2) }), some({ a: 1, b: 2 }))
+    assert.deepStrictEqual(adoOption({ a: some(1), b: none }), none)
+
+    const adoEither = sequenceS(either)
+    assert.deepStrictEqual(adoEither({ a: right(1) }), right({ a: 1 }))
+    assert.deepStrictEqual(adoEither({ a: right(1), b: right(2) }), right({ a: 1, b: 2 }))
+    assert.deepStrictEqual(adoEither({ a: right(1), b: left('error') }), left('error'))
+  })
 })