From 77acfe571f60a6d00fa87cbd5a8a182474b08e18 Mon Sep 17 00:00:00 2001 From: Tim Date: Fri, 26 Apr 2024 15:45:07 +1200 Subject: [PATCH] add $is & $match helpers to Data.TaggedEnum constructors (#2620) --- .changeset/small-birds-beam.md | 28 +++++++++++++++++++++ packages/effect/src/Data.ts | 42 ++++++++++++++++++++++++++++--- packages/effect/test/Data.test.ts | 16 +++++++++++- 3 files changed, 82 insertions(+), 4 deletions(-) create mode 100644 .changeset/small-birds-beam.md diff --git a/.changeset/small-birds-beam.md b/.changeset/small-birds-beam.md new file mode 100644 index 0000000000..683608e9ba --- /dev/null +++ b/.changeset/small-birds-beam.md @@ -0,0 +1,28 @@ +--- +"effect": minor +--- + +add $is & $match helpers to Data.TaggedEnum constructors + +```ts +import { Data } from "effect" + +type HttpError = Data.TaggedEnum<{ + NotFound: {} + InternalServerError: { reason: string } +}> +const { $is, $match, InternalServerError, NotFound } = + Data.taggedEnum() + +// create a matcher +const matcher = $match({ + NotFound: () => 0, + InternalServerError: () => 1 +}) + +// true +$is("NotFound")(NotFound()) + +// false +$is("NotFound")(InternalServerError({ reason: "fail" })) +``` diff --git a/packages/effect/src/Data.ts b/packages/effect/src/Data.ts index ccc1f7d442..42dfb900c2 100644 --- a/packages/effect/src/Data.ts +++ b/packages/effect/src/Data.ts @@ -5,6 +5,7 @@ import type * as Cause from "./Cause.js" import * as core from "./internal/core.js" import * as internal from "./internal/data.js" import { StructuralPrototype } from "./internal/effectable.js" +import * as Predicate from "./Predicate.js" import type * as Types from "./Types.js" /** @@ -331,6 +332,23 @@ export declare namespace TaggedEnum { A extends { readonly _tag: string }, K extends A["_tag"] > = Extract + + /** + * @since 3.1.0 + */ + export type Constructor = Types.Simplify< + & { + readonly [Tag in A["_tag"]]: Case.Constructor, "_tag"> + } + & { + readonly $is: (tag: Tag) => (u: unknown) => u is Extract + readonly $match: < + Cases extends { + readonly [Tag in A["_tag"]]: (args: Extract) => any + } + >(cases: Cases) => (value: A) => ReturnType + } + > } /** @@ -407,16 +425,34 @@ export const taggedEnum: { ) => TaggedEnum.Value, Tag> } - (): { - readonly [Tag in A["_tag"]]: Case.Constructor, "_tag"> - } + (): TaggedEnum.Constructor } = () => new Proxy({}, { get(_target, tag, _receiver) { + if (tag === "$is") { + return taggedIs + } else if (tag === "$match") { + return taggedMatch + } return tagged(tag as string) } }) as any +function taggedIs(tag: Tag) { + return Predicate.isTagged(tag) +} + +function taggedMatch< + A extends { readonly _tag: string }, + Cases extends { + readonly [K in A["_tag"]]: (args: Extract) => any + } +>(cases: Cases) { + return function(value: A): ReturnType { + return cases[value._tag as A["_tag"]](value as any) + } +} + /** * Provides a constructor for a Case Class. * diff --git a/packages/effect/test/Data.test.ts b/packages/effect/test/Data.test.ts index dd57196e26..974c91bfe7 100644 --- a/packages/effect/test/Data.test.ts +++ b/packages/effect/test/Data.test.ts @@ -177,7 +177,12 @@ describe("Data", () => { NotFound: {} InternalServerError: { reason: string } }> - const { InternalServerError, NotFound } = Data.taggedEnum() + const { + $is, + $match, + InternalServerError, + NotFound + } = Data.taggedEnum() const a = NotFound() const b = InternalServerError({ reason: "test" }) @@ -191,6 +196,15 @@ describe("Data", () => { expect(Equal.equals(a, b)).toBe(false) expect(Equal.equals(b, c)).toBe(true) + + expect($is("NotFound")(a)).toBe(true) + expect($is("InternalServerError")(a)).toBe(false) + const matcher = $match({ + NotFound: () => 0, + InternalServerError: () => 1 + }) + expect(matcher(a)).toEqual(0) + expect(matcher(b)).toEqual(1) }) it("taggedEnum - generics", () => {