From c09efc1d0fa9945ff3c9c0db189cf90e743adbde Mon Sep 17 00:00:00 2001 From: Alisue Date: Sat, 26 Aug 2023 21:59:04 +0900 Subject: [PATCH 1/5] :+1: Improve performance of `isRecordOf` The benchmark result of the previous implementation is 197.12 ns/iter but the new implementation is 88.99 ns/iter. --- is.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/is.ts b/is.ts index 4b72d00..d9c4d30 100644 --- a/is.ts +++ b/is.ts @@ -138,8 +138,13 @@ export function isRecord( export function isRecordOf( pred: Predicate, ): Predicate> { - return (x: unknown): x is RecordOf => - isRecord(x) && Object.values(x).every(pred); + return (x: unknown): x is RecordOf => { + if (!isRecord(x)) return false; + for (const k in x) { + if (!pred(x[k])) return false; + } + return true; + }; } type FlatType = T extends RecordOf From fbfda27aca1136094b8d868f294cd1efcb42dd7d Mon Sep 17 00:00:00 2001 From: Alisue Date: Sat, 26 Aug 2023 23:16:14 +0900 Subject: [PATCH 2/5] :herb: Fix invalid test of `isObjectOf` --- is_test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/is_test.ts b/is_test.ts index 0649545..e5ecfc4 100644 --- a/is_test.ts +++ b/is_test.ts @@ -280,7 +280,7 @@ Deno.test("isObjectOf", async (t) => { }); await testWithExamples( t, - isObjectOf({ a: (_: unknown): _ is unknown => true }), + isObjectOf({ a: (_: unknown): _ is unknown => false }), { excludeExamples: ["record"] }, ); await t.step("with optional properties", async (t) => { From 299e25bbec99071dd10feab21bb5ed8c5c6fcf78 Mon Sep 17 00:00:00 2001 From: Alisue Date: Sat, 26 Aug 2023 23:16:42 +0900 Subject: [PATCH 3/5] :+1: Add benchmark for strict mode of `isObjectOf` --- is_bench.ts | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/is_bench.ts b/is_bench.ts index 2867d6c..06aba3f 100644 --- a/is_bench.ts +++ b/is_bench.ts @@ -170,6 +170,15 @@ Deno.bench({ } }, }); +Deno.bench({ + name: "is.ObjectOf (strict)", + fn: () => { + const pred = is.ObjectOf(predObj, { strict: true }); + for (const c of cs) { + pred(c); + } + }, +}); const isObjectOfPred = is.ObjectOf(predObj); Deno.bench({ @@ -181,6 +190,16 @@ Deno.bench({ }, }); +const isObjectOfStrictPred = is.ObjectOf(predObj, { strict: true }); +Deno.bench({ + name: "is.ObjectOf (pre, strict)", + fn: () => { + for (const c of cs) { + isObjectOfStrictPred(c); + } + }, +}); + Deno.bench({ name: "is.Function", fn: () => { From 5330ea4cbbf34fb6809d0174ab514da70c5b9be0 Mon Sep 17 00:00:00 2001 From: Alisue Date: Sat, 26 Aug 2023 23:18:54 +0900 Subject: [PATCH 4/5] :+1: Improve performance of `isObjectOf` MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Before to this commit, the benchmark result was is.ObjectOf 335.74 ns/iter 2,978,453.8 (307.15 ns … 694.14 ns) 337.23 ns 391.01 ns 694.14 ns is.ObjectOf (strict) 406.31 ns/iter 2,461,182.4 (386.4 ns … 453.27 ns) 413.72 ns 442.96 ns 453.27 ns is.ObjectOf (pre) 179.34 ns/iter 5,575,896.2 (167.5 ns … 196.1 ns) 185.86 ns 195.85 ns 195.88 ns is.ObjectOf (pre, strict) 205.37 ns/iter 4,869,263.2 (199.38 ns … 225.96 ns) 207.86 ns 224.33 ns 225.62 ns And after this commit is.ObjectOf 150.21 ns/iter 6,657,313.1 (144.75 ns … 161.07 ns) 152.62 ns 159.53 ns 160.82 ns is.ObjectOf (strict) 240.33 ns/iter 4,160,893.5 (233.04 ns … 276.14 ns) 241.96 ns 273.35 ns 273.55 ns is.ObjectOf (pre) 112.1 ns/iter 8,920,598.1 (108.72 ns … 124.09 ns) 112.92 ns 118.22 ns 122.38 ns is.ObjectOf (pre, strict) 137.05 ns/iter 7,296,588.7 (130.81 ns … 168.84 ns) 139.82 ns 151.59 ns 158.23 ns --- is.ts | 39 +++++++++++++++++++++++++++++---------- 1 file changed, 29 insertions(+), 10 deletions(-) diff --git a/is.ts b/is.ts index d9c4d30..fac3037 100644 --- a/is.ts +++ b/is.ts @@ -193,16 +193,35 @@ export function isObjectOf< predObj: T, options: { strict?: boolean } = {}, ): Predicate> { - const preds = Object.entries(predObj); - const allKeys = new Set(preds.map(([key]) => key)); - const requiredKeys = preds - .filter(([_, pred]) => !(pred as OptionalPredicate).optional) - .map(([key]) => key); - const hasKeys = options.strict - ? (props: string[]) => props.every((p) => allKeys.has(p)) - : (props: string[]) => requiredKeys.every((k) => props.includes(k)); - return (x: unknown): x is ObjectOf => - isRecord(x) && hasKeys(Object.keys(x)) && preds.every(([k, p]) => p(x[k])); + return options.strict ? isObjectOfStrict(predObj) : isObjectOfLoose(predObj); +} + +function isObjectOfLoose< + T extends RecordOf>, +>( + predObj: T, +): Predicate> { + return (x: unknown): x is ObjectOf => { + if (!isRecord(x)) return false; + for (const k in predObj) { + if (!predObj[k](x[k])) return false; + } + return true; + }; +} + +function isObjectOfStrict< + T extends RecordOf>, +>( + predObj: T, +): Predicate> { + const keys = new Set(Object.keys(predObj)); + const pred = isObjectOfLoose(predObj); + return (x: unknown): x is ObjectOf => { + if (!pred(x)) return false; + const ks = Object.keys(x); + return ks.length <= keys.size && ks.every((k) => keys.has(k)); + }; } /** From a254afa53938156d16066720f80cad3b31bb44fb Mon Sep 17 00:00:00 2001 From: Alisue Date: Sat, 26 Aug 2023 23:23:38 +0900 Subject: [PATCH 5/5] :+1: Improve performance of `isOptionalOf` MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Before this commit, the benchmark result was is.OptionalOf 243.37 ns/iter 4,108,906.6 (234.5 ns … 271.25 ns) 245.73 ns 264.14 ns 267.17 ns is.OptionalOf (pre) 200.66 ns/iter 4,983,517.6 (195.82 ns … 232.95 ns) 202.55 ns 215.28 ns 222.94 ns And after this commit is.OptionalOf 45.56 ns/iter 21,948,235.7 (41.91 ns … 72.25 ns) 48.58 ns 53.75 ns 56.55 ns is.OptionalOf (pre) 12.86 ns/iter 77,733,516.2 (12.7 ns … 20.59 ns) 12.86 ns 14.23 ns 14.64 ns --- is.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/is.ts b/is.ts index fac3037..1ee9fb0 100644 --- a/is.ts +++ b/is.ts @@ -390,9 +390,12 @@ export type OptionalPredicate = Predicate & { export function isOptionalOf( pred: Predicate, ): OptionalPredicate { - return Object.assign(isOneOf([isUndefined, pred]), { - optional: true as const, - }); + return Object.assign( + (x: unknown): x is Predicate => isUndefined(x) || pred(x), + { + optional: true as const, + }, + ) as OptionalPredicate; } export default {