Skip to content

Commit

Permalink
add $is & $match helpers to Data.TaggedEnum constructors (#2620)
Browse files Browse the repository at this point in the history
  • Loading branch information
tim-smart committed Apr 30, 2024
1 parent b5de2d2 commit 92d56db
Show file tree
Hide file tree
Showing 3 changed files with 82 additions and 4 deletions.
28 changes: 28 additions & 0 deletions .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<HttpError>()

// create a matcher
const matcher = $match({
NotFound: () => 0,
InternalServerError: () => 1
})

// true
$is("NotFound")(NotFound())

// false
$is("NotFound")(InternalServerError({ reason: "fail" }))
```
42 changes: 39 additions & 3 deletions packages/effect/src/Data.ts
Expand Up @@ -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"

/**
Expand Down Expand Up @@ -331,6 +332,23 @@ export declare namespace TaggedEnum {
A extends { readonly _tag: string },
K extends A["_tag"]
> = Extract<A, { readonly _tag: K }>

/**
* @since 3.1.0
*/
export type Constructor<A extends { readonly _tag: string }> = Types.Simplify<
& {
readonly [Tag in A["_tag"]]: Case.Constructor<Extract<A, { readonly _tag: Tag }>, "_tag">
}
& {
readonly $is: <Tag extends A["_tag"]>(tag: Tag) => (u: unknown) => u is Extract<A, { readonly _tag: Tag }>
readonly $match: <
Cases extends {
readonly [Tag in A["_tag"]]: (args: Extract<A, { readonly _tag: Tag }>) => any
}
>(cases: Cases) => (value: A) => ReturnType<Cases[A["_tag"]]>
}
>
}

/**
Expand Down Expand Up @@ -407,16 +425,34 @@ export const taggedEnum: {
) => TaggedEnum.Value<TaggedEnum.Kind<Z, A, B, C, D>, Tag>
}

<A extends { readonly _tag: string }>(): {
readonly [Tag in A["_tag"]]: Case.Constructor<Extract<A, { readonly _tag: Tag }>, "_tag">
}
<A extends { readonly _tag: string }>(): TaggedEnum.Constructor<A>
} = () =>
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<A extends { readonly _tag: string }, Tag extends A["_tag"]>(tag: Tag) {
return Predicate.isTagged(tag)
}

function taggedMatch<
A extends { readonly _tag: string },
Cases extends {
readonly [K in A["_tag"]]: (args: Extract<A, { readonly _tag: K }>) => any
}
>(cases: Cases) {
return function(value: A): ReturnType<Cases[A["_tag"]]> {
return cases[value._tag as A["_tag"]](value as any)
}
}

/**
* Provides a constructor for a Case Class.
*
Expand Down
16 changes: 15 additions & 1 deletion packages/effect/test/Data.test.ts
Expand Up @@ -177,7 +177,12 @@ describe("Data", () => {
NotFound: {}
InternalServerError: { reason: string }
}>
const { InternalServerError, NotFound } = Data.taggedEnum<HttpError>()
const {
$is,
$match,
InternalServerError,
NotFound
} = Data.taggedEnum<HttpError>()

const a = NotFound()
const b = InternalServerError({ reason: "test" })
Expand All @@ -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", () => {
Expand Down

0 comments on commit 92d56db

Please sign in to comment.