From ef878d45d7262dd763cbd1132099c4eef0051df5 Mon Sep 17 00:00:00 2001 From: Aldwin Vlasblom Date: Sun, 19 Jul 2020 13:14:24 +0200 Subject: [PATCH 1/7] Add type aliases for "certain" Futures A certain Future is a Future whose branch is certain. In TypeScript, this is represented by having 'never' as the type for one of the branches. This change also applies to ConcurrentFutures. --- index.d.ts | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/index.d.ts b/index.d.ts index e940dd4d..00502691 100644 --- a/index.d.ts +++ b/index.d.ts @@ -30,6 +30,10 @@ export interface Functor { type Mapped, B> = ReturnType<(F & { [$T]: B })['fantasy-land/map']> +export type ConcurrentRejected = ConcurrentFutureInstance; + +export type ConcurrentResolved = ConcurrentFutureInstance; + export interface ConcurrentFutureInstance extends Functor { sequential: FutureInstance 'fantasy-land/ap'(this: ConcurrentFutureInstance B>, right: ConcurrentFutureInstance): ConcurrentFutureInstance @@ -37,6 +41,10 @@ export interface ConcurrentFutureInstance extends Functor { 'fantasy-land/alt'(right: ConcurrentFutureInstance): ConcurrentFutureInstance } +export type Rejected = FutureInstance; + +export type Resolved = FutureInstance; + export interface FutureInstance extends Functor { /** The Future constructor */ @@ -60,7 +68,7 @@ export interface FutureInstance extends Functor { } /** Creates a Future which resolves after the given duration with the given value. See https://github.com/fluture-js/Fluture#after */ -export function after(duration: number): (value: R) => FutureInstance +export function after(duration: number): (value: R) => Resolved /** Logical and for Futures. See https://github.com/fluture-js/Fluture#and */ export function and(left: FutureInstance): (right: FutureInstance) => FutureInstance @@ -120,7 +128,7 @@ export function extractLeft(source: FutureInstance): Array export function extractRight(source: FutureInstance): Array /** Coalesce both branches into the resolution branch. See https://github.com/fluture-js/Fluture#coalesce */ -export function coalesce(lmapper: (left: LA) => R): (rmapper: (right: RA) => R) => (source: FutureInstance) => FutureInstance +export function coalesce(lmapper: (left: LA) => R): (rmapper: (right: RA) => R) => (source: FutureInstance) => Resolved /** Fork the given Future into the given continuations. See https://github.com/fluture-js/Fluture#fork */ export function fork(reject: RejectFunction): (resolve: ResolveFunction) => (source: FutureInstance) => Cancel @@ -153,13 +161,13 @@ export const map: { export function mapRej(mapper: (reason: LA) => LB): (source: FutureInstance) => FutureInstance /** A Future that never settles. See https://github.com/fluture-js/Fluture#never */ -export var never: FutureInstance +export var never: Resolved /** Create a Future using a provided Node-style callback. See https://github.com/fluture-js/Fluture#node */ export function node(fn: (done: Nodeback) => void): FutureInstance /** Create a Future with the given resolution value. See https://github.com/fluture-js/Fluture#of */ -export function resolve(value: R): FutureInstance +export function resolve(value: R): Resolved /** Run an Array of Futures in parallel, under the given concurrency limit. See https://github.com/fluture-js/Fluture#parallel */ export function parallel(concurrency: number): (futures: Array>) => FutureInstance> @@ -171,10 +179,10 @@ export function promise(source: FutureInstance): Promise export function race(left: FutureInstance): (right: FutureInstance) => FutureInstance /** Create a Future with the given rejection reason. See https://github.com/fluture-js/Fluture#reject */ -export function reject(reason: L): FutureInstance +export function reject(reason: L): Rejected /** Creates a Future which rejects after the given duration with the given reason. See https://github.com/fluture-js/Fluture#rejectafter */ -export function rejectAfter(duration: number): (reason: L) => FutureInstance +export function rejectAfter(duration: number): (reason: L) => Rejected /** Convert a ConcurrentFuture to a regular Future. See https://github.com/fluture-js/Fluture#concurrentfuture */ export function seq(source: ConcurrentFutureInstance): FutureInstance @@ -183,7 +191,7 @@ export function seq(source: ConcurrentFutureInstance): FutureInstanc export function swap(source: FutureInstance): FutureInstance /** Fork the Future into the given continuation. See https://github.com/fluture-js/Fluture#value */ -export function value(resolve: ResolveFunction): (source: FutureInstance) => Cancel +export function value(resolve: ResolveFunction): (source: Resolved) => Cancel /** Enable or disable debug mode. See https://github.com/fluture-js/Fluture#debugmode */ export function debugMode(debug: boolean): void; From 5022c16a68ba906bf4e89443a9f04b374115f812 Mon Sep 17 00:00:00 2001 From: Aldwin Vlasblom Date: Sun, 19 Jul 2020 13:55:43 +0200 Subject: [PATCH 2/7] Add a type alias for "uncertain" Futures and ConcurrentFutures An uncertain Future is one that might reject or resolve. --- index.d.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/index.d.ts b/index.d.ts index 00502691..bb69aa5f 100644 --- a/index.d.ts +++ b/index.d.ts @@ -34,6 +34,8 @@ export type ConcurrentRejected = ConcurrentFutureInstance; export type ConcurrentResolved = ConcurrentFutureInstance; +export type ConcurrentUncertain = ConcurrentFutureInstance; + export interface ConcurrentFutureInstance extends Functor { sequential: FutureInstance 'fantasy-land/ap'(this: ConcurrentFutureInstance B>, right: ConcurrentFutureInstance): ConcurrentFutureInstance @@ -45,6 +47,8 @@ export type Rejected = FutureInstance; export type Resolved = FutureInstance; +export type Uncertain = FutureInstance; + export interface FutureInstance extends Functor { /** The Future constructor */ From c55b9dd8b364c2cbd879c2de85d512ccbc8b987d Mon Sep 17 00:00:00 2001 From: Aldwin Vlasblom Date: Sun, 19 Jul 2020 13:37:39 +0200 Subject: [PATCH 3/7] Add a type alias for never settling Futures and ConcurrentFutures --- index.d.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/index.d.ts b/index.d.ts index bb69aa5f..d2f396cb 100644 --- a/index.d.ts +++ b/index.d.ts @@ -30,6 +30,8 @@ export interface Functor { type Mapped, B> = ReturnType<(F & { [$T]: B })['fantasy-land/map']> +export type ConcurrentNever = ConcurrentFutureInstance; + export type ConcurrentRejected = ConcurrentFutureInstance; export type ConcurrentResolved = ConcurrentFutureInstance; @@ -43,6 +45,8 @@ export interface ConcurrentFutureInstance extends Functor { 'fantasy-land/alt'(right: ConcurrentFutureInstance): ConcurrentFutureInstance } +export type Never = FutureInstance; + export type Rejected = FutureInstance; export type Resolved = FutureInstance; @@ -165,7 +169,7 @@ export const map: { export function mapRej(mapper: (reason: LA) => LB): (source: FutureInstance) => FutureInstance /** A Future that never settles. See https://github.com/fluture-js/Fluture#never */ -export var never: Resolved +export var never: Never /** Create a Future using a provided Node-style callback. See https://github.com/fluture-js/Fluture#node */ export function node(fn: (done: Nodeback) => void): FutureInstance From 4fb3569e9ab2b5e67025fbaf5f1e3f09783b4dc7 Mon Sep 17 00:00:00 2001 From: Aldwin Vlasblom Date: Fri, 19 Jun 2020 16:28:04 +0200 Subject: [PATCH 4/7] Add typescript type tests for 'after' --- test/types/after.test-d.ts | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 test/types/after.test-d.ts diff --git a/test/types/after.test-d.ts b/test/types/after.test-d.ts new file mode 100644 index 00000000..63b69040 --- /dev/null +++ b/test/types/after.test-d.ts @@ -0,0 +1,9 @@ +import {expectType} from 'tsd'; + +import * as fl from '../../index.js'; + +expectType> (fl.after (1) (42)); +expectType> (fl.after (1) ('a')); + +// https://github.com/microsoft/TypeScript/issues/32277 +// expectType (fl.after (Infinity) ('Finally!')); From 5dc33cd1a3d287daeef92b06cab8b8faa690f2c4 Mon Sep 17 00:00:00 2001 From: Aldwin Vlasblom Date: Fri, 8 Jan 2021 14:43:34 +0100 Subject: [PATCH 5/7] Add a type alias for Futures and ConcurrentFutures of any values --- index.d.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/index.d.ts b/index.d.ts index d2f396cb..553274a8 100644 --- a/index.d.ts +++ b/index.d.ts @@ -38,6 +38,8 @@ export type ConcurrentResolved = ConcurrentFutureInstance; export type ConcurrentUncertain = ConcurrentFutureInstance; +export type AnyConcurrent = ConcurrentFutureInstance; + export interface ConcurrentFutureInstance extends Functor { sequential: FutureInstance 'fantasy-land/ap'(this: ConcurrentFutureInstance B>, right: ConcurrentFutureInstance): ConcurrentFutureInstance @@ -53,6 +55,8 @@ export type Resolved = FutureInstance; export type Uncertain = FutureInstance; +export type AnyFuture = FutureInstance; + export interface FutureInstance extends Functor { /** The Future constructor */ From 7072818bbb8f6c90bace0e3a71f32672c744e815 Mon Sep 17 00:00:00 2001 From: Aldwin Vlasblom Date: Fri, 8 Jan 2021 14:45:43 +0100 Subject: [PATCH 6/7] Add branch awareness and tests for the type of 'alt' --- index.d.ts | 36 ++++++++++++++++++++++++--- test/types/alt.test-d.ts | 54 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 86 insertions(+), 4 deletions(-) create mode 100644 test/types/alt.test-d.ts diff --git a/index.d.ts b/index.d.ts index 553274a8..5a64d376 100644 --- a/index.d.ts +++ b/index.d.ts @@ -86,10 +86,38 @@ export function after(duration: number): (value: R) => Resolved export function and(left: FutureInstance): (right: FutureInstance) => FutureInstance /** Logical or for Futures. See https://github.com/fluture-js/Fluture#alt */ -export function alt(left: FutureInstance): (right: FutureInstance) => FutureInstance - -/** Race two ConcurrentFutures. See https://github.com/fluture-js/Fluture#alt */ -export function alt(left: ConcurrentFutureInstance): (right: ConcurrentFutureInstance) => ConcurrentFutureInstance +export const alt: { + (second: F extends Never ? S : never): (first: F) => Never + (second: F extends Rejected ? S : never): (first: F) => S + (second: F extends Resolved ? S : never): (first: F) => F + + (second: Rejected): { + (first: Never): Never + (first: Rejected): Rejected + (first: Resolved): Resolved + (first: Uncertain): Uncertain + } + + (second: Uncertain): { + (first: Resolved): Resolved + (first: Rejected): Uncertain + (first: Uncertain): Uncertain + } + + (second: ConcurrentNever): (first: ConcurrentUncertain) => ConcurrentUncertain + + (second: ConcurrentRejected): { + (first: ConcurrentResolved): ConcurrentUncertain + (first: ConcurrentUncertain): ConcurrentUncertain + } + + (second: ConcurrentResolved): { + (first: ConcurrentRejected): ConcurrentUncertain + (first: ConcurrentUncertain): ConcurrentUncertain + } + + (second: ConcurrentUncertain): (first: ConcurrentUncertain) => ConcurrentUncertain +} /** Apply the function in the right Future to the value in the left Future. See https://github.com/fluture-js/Fluture#ap */ export function ap(value: FutureInstance): (apply: FutureInstance RB>) => FutureInstance diff --git a/test/types/alt.test-d.ts b/test/types/alt.test-d.ts new file mode 100644 index 00000000..a8f9028a --- /dev/null +++ b/test/types/alt.test-d.ts @@ -0,0 +1,54 @@ +import {expectType, expectError} from 'tsd'; + +import * as fl from '../../index.js'; + +const fsn: fl.FutureInstance = fl.resolve (42); +const fns: fl.FutureInstance = fl.resolve ('a'); + +// Standard usage on Future instances. +expectType (fl.alt (fl.never) (fl.never)); +expectType (fl.alt (fl.reject ('a')) (fl.never)); +expectType (fl.alt (fl.resolve ('a')) (fl.never)); +expectType (fl.alt (fl.never) (fl.reject ('a'))); +expectType> (fl.alt (fl.never) (fl.resolve ('a'))); +expectType> (fl.alt (fl.reject ('a')) (fl.resolve (42))); +expectType> (fl.alt (fl.resolve (42)) (fl.reject ('a'))); +expectType> (fl.alt (fl.resolve (42)) (fl.resolve (42))); +expectType> (fl.alt (fl.reject (42)) (fl.reject (42))); +expectType> (fl.alt (fl.reject ('a')) (fl.reject (42))); +expectType> (fl.alt (fsn) (fsn)); +expectType> (fl.alt (fl.resolve ('a')) (fl.resolve (42))); +expectError (fl.alt (fsn) (fns)); + +// Usage with pipe on Future instances (https://git.io/JLx3F). +expectType ((fl.never) .pipe (fl.alt (fl.never))); +expectType ((fl.never) .pipe (fl.alt (fl.reject ('a')))); +expectType ((fl.never) .pipe (fl.alt (fl.resolve ('a')))); +expectType ((fl.reject ('a')) .pipe (fl.alt (fl.never))); +expectType> ((fl.resolve ('a')) .pipe (fl.alt (fl.never))); +expectType> ((fl.resolve (42)) .pipe (fl.alt (fl.reject ('a')))); +expectType> ((fl.reject ('a')) .pipe (fl.alt (fl.resolve (42)))); +expectType> ((fl.resolve (42)) .pipe (fl.alt (fl.resolve (42)))); +expectType> ((fl.reject (42)) .pipe (fl.alt (fl.reject (42)))); +expectType> ((fl.reject (42)) .pipe (fl.alt (fl.reject ('a')))); +expectType> ((fsn) .pipe (fl.alt (fsn))); +expectType> ((fl.resolve (42)) .pipe (fl.alt (fl.resolve ('a')))); +expectError ((fns) .pipe (fl.alt (fsn))); + +const csn: fl.ConcurrentFutureInstance = fl.Par (fl.resolve (42)); +const cns: fl.ConcurrentFutureInstance = fl.Par (fl.resolve ('a')); + +// Standard usage on ConcurrentFuture instances. +expectType (fl.alt (fl.Par (fl.never)) (fl.Par (fl.never))); +expectType> (fl.alt (fl.Par (fl.reject ('a'))) (fl.Par (fl.never))); +expectType> (fl.alt (fl.Par (fl.resolve ('a'))) (fl.Par (fl.never))); +expectType> (fl.alt (fl.Par (fl.never)) (fl.Par (fl.reject ('a')))); +expectType> (fl.alt (fl.Par (fl.never)) (fl.Par (fl.resolve ('a')))); +expectType> (fl.alt (fl.Par (fl.reject ('a'))) (fl.Par (fl.resolve (42)))); +expectType> (fl.alt (fl.Par (fl.resolve (42))) (fl.Par (fl.reject ('a')))); +expectType> (fl.alt (fl.Par (fl.resolve (42))) (fl.Par (fl.resolve (42)))); +expectType> (fl.alt (fl.Par (fl.reject (42))) (fl.Par (fl.reject (42)))); +expectType> (fl.alt (csn) (csn)); +expectError (fl.alt (fl.Par (fl.resolve ('a'))) (fl.Par (fl.resolve (42)))); +expectError (fl.alt (fl.Par (fl.reject ('a'))) (fl.Par (fl.reject (42)))); +expectError (fl.alt (csn) (cns)); From 6e1afabfd9e884d5860ecfbfec25f80fb808401e Mon Sep 17 00:00:00 2001 From: Aldwin Vlasblom Date: Sat, 24 Apr 2021 20:37:57 +0200 Subject: [PATCH 7/7] Add branch awareness and tests for the type of 'and' --- index.d.ts | 12 +++++++++++- test/types/and.test-d.ts | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+), 1 deletion(-) create mode 100644 test/types/and.test-d.ts diff --git a/index.d.ts b/index.d.ts index 5a64d376..f631e5ed 100644 --- a/index.d.ts +++ b/index.d.ts @@ -83,7 +83,17 @@ export interface FutureInstance extends Functor { export function after(duration: number): (value: R) => Resolved /** Logical and for Futures. See https://github.com/fluture-js/Fluture#and */ -export function and(left: FutureInstance): (right: FutureInstance) => FutureInstance +export const and: { + (second: F extends Never ? S : never): (first: F) => Never + (second: F extends Resolved ? S : never): (first: F) => S + (second: F extends Rejected ? S : never): (first: F) => F + + (second: Uncertain): { + (first: Rejected): Rejected + (first: Resolved): Uncertain + (first: Uncertain): Uncertain + } +} /** Logical or for Futures. See https://github.com/fluture-js/Fluture#alt */ export const alt: { diff --git a/test/types/and.test-d.ts b/test/types/and.test-d.ts new file mode 100644 index 00000000..33665ad4 --- /dev/null +++ b/test/types/and.test-d.ts @@ -0,0 +1,34 @@ +import {expectType, expectError} from 'tsd'; + +import * as fl from '../../index.js'; + +const fsn: fl.Uncertain = fl.resolve (42); +const fns: fl.Uncertain = fl.resolve ('a'); + +// Standard usage on Future instances. +expectType (fl.and (fl.never) (fl.never)); +expectType (fl.and (fl.never) (fl.resolve ('a'))); +expectType (fl.and (fl.reject ('a')) (fl.never)); +expectType (fl.and (fl.resolve ('a')) (fl.never)); +expectType> (fl.and (fl.reject ('a')) (fl.resolve (42))); +expectType> (fl.and (fl.resolve (42)) (fl.resolve (42))); +expectType> (fl.and (fl.reject (42)) (fl.reject (42))); +expectType> (fl.and (fsn) (fsn)); +expectType> (fl.and (fl.never) (fl.reject ('a'))); +expectType> (fl.and (fl.resolve (42)) (fl.reject ('a'))); +expectType> (fl.and (fl.reject ('a')) (fl.reject (42))); +expectError (fl.and (fsn) (fns)); + +// Usage with pipe on Future instances (https://git.io/JLx3F). +expectType ((fl.never) .pipe (fl.and (fl.never))); +const workaround = (fl.resolve ('a')) .pipe (fl.and (fl.never)); expectType (workaround); +expectType ((fl.never) .pipe (fl.and (fl.reject ('a')))); +expectType ((fl.never) .pipe (fl.and (fl.resolve ('a')))); +expectType> ((fl.resolve (42)) .pipe (fl.and (fl.reject ('a')))); +expectType> ((fl.resolve (42)) .pipe (fl.and (fl.resolve (42)))); +expectType> ((fl.reject (42)) .pipe (fl.and (fl.reject (42)))); +expectType> ((fsn) .pipe (fl.and (fsn))); +expectType> ((fl.reject ('a')) .pipe (fl.and (fl.never))); +expectType> ((fl.reject ('a')) .pipe (fl.and (fl.resolve (42)))); +expectType> ((fl.reject (42)) .pipe (fl.and (fl.reject ('a')))); +expectError ((fns) .pipe (fl.and (fsn)));