Skip to content

Instantly share code, notes, and snippets.

@tim-smart
Last active March 20, 2023 20:58
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save tim-smart/8ff4ac7c497f422e62bb88e731428087 to your computer and use it in GitHub Desktop.
Save tim-smart/8ff4ac7c497f422e62bb88e731428087 to your computer and use it in GitHub Desktop.
node_modules
pnpm-lock.yaml
*.json
import * as Data from "@effect/data/Data"
type Simplify<A> = { [K in keyof A]: A[K] } & {}
// ====================
// Tagged enums (no generics)
// ====================
export type TaggedEnumConstructor<
A extends Record<string, Record<string, any>>,
> = <K extends keyof A>(
tag: K,
) => Data.Case.Constructor<
Data.Data<
Simplify<
Readonly<A[K]> & {
readonly _tag: K
}
>
>,
"_tag"
>
export function taggedEnum<
A extends Record<string, Record<string, any>>,
>(): TaggedEnumConstructor<A> {
return Data.tagged as any
}
export type InferTaggedEnum<A extends TaggedEnumConstructor<any>> =
A extends TaggedEnumConstructor<infer T>
? {
[K in keyof T]: Data.Data<
Simplify<Readonly<T[K]> & { readonly _tag: K }>
>
}[keyof T]
: never
// ====================
// ADT
// ====================
export type ADT<A extends Record<string, Record<string, any>>> = {
[K in keyof A]: Data.Data<Simplify<Readonly<A[K]> & { readonly _tag: K }>>
}[keyof A]
export namespace ADT {
type Tag<A extends { _tag: string }> = A["_tag"]
type Value<A extends { _tag: string }, K extends A["_tag"]> = Omit<
Extract<A, { _tag: K }>,
"_tag" | keyof Data.Case
> extends infer T
? {} extends T
? void
: T
: never
type Result<A extends { _tag: string }, K extends A["_tag"]> = Extract<
A,
{ _tag: K }
>
export interface Constructor {
readonly A: unknown
readonly B: unknown
readonly C: unknown
readonly D: unknown
}
type Kind<
F extends Constructor,
A = unknown,
B = unknown,
C = unknown,
D = unknown,
> = F extends {
adt: { _tag: string }
}
? (F & {
readonly A: A
readonly B: B
readonly C: C
readonly D: D
})["adt"]
: never
export type Constructor1<F extends Constructor> = <K extends Tag<Kind<F>>>(
tag: K,
) => <A>(value: Value<Kind<F, A>, K>) => Result<Kind<F, A>, K>
export type Constructor2<F extends Constructor> = <K extends Tag<Kind<F>>>(
tag: K,
) => <A, B>(value: Value<Kind<F, A, B>, K>) => Result<Kind<F, A, B>, K>
export type Constructor3<F extends Constructor> = <K extends Tag<Kind<F>>>(
tag: K,
) => <A, B, C>(
value: Value<Kind<F, A, B, C>, K>,
) => Result<Kind<F, A, B, C>, K>
export type Constructor4<F extends Constructor> = <K extends Tag<Kind<F>>>(
tag: K,
) => <A, B = unknown, C = unknown, D = unknown>(
value: Value<Kind<F, A, B, C, D>, K>,
) => Result<Kind<F, A, B, C, D>, K>
}
export function adt<A extends ADT.Constructor>(): ADT.Constructor4<A> {
return Data.tagged as any
}
export function adt1<A extends ADT.Constructor>(): ADT.Constructor1<A> {
return Data.tagged as any
}
export function adt2<A extends ADT.Constructor>(): ADT.Constructor2<A> {
return Data.tagged as any
}
export function adt3<A extends ADT.Constructor>(): ADT.Constructor3<A> {
return Data.tagged as any
}
import { taggedEnum } from "./adt"
export const HttpError = taggedEnum<{
BadRequest: {}
NotFound: { path: string }
InternalServerError: { message: string }
}>()
import { ADT, InferTaggedEnum, adt1, adt2, taggedEnum } from "./adt"
// definition
type Option<A> = ADT<{
Some: { value: A }
None: {}
}>
interface OptionC extends ADT.Constructor {
adt: Option<this["A"]>
}
const Option = adt1<OptionC>()
// usage
const some = Option("Some")({ value: 1 })
const none = Option("None")()
console.log(some, none)
// ====
// either definition
type Either<E, A> = ADT<{
Left: { left: E }
Right: { right: A }
}>
interface EitherC extends ADT.Constructor {
adt: Either<this["A"], this["B"]>
}
// either usage
const Either = adt2<EitherC>()
const left = Either("Left")({ left: "error" })
const right = Either("Right")({ right: "error" })
console.log(left, right)
// ====
const httpChunk = taggedEnum<{
Version: {
method:
| "GET"
| "POST"
| "PUT"
| "DELETE"
| "PATCH"
| "HEAD"
| "OPTIONS"
| "TRACE"
major: number
minor: number
}
Header: {
key: string
value: string
}
Body: {
contents: any
}
}>()
export type HttpChunk = InferTaggedEnum<typeof httpChunk>
export const version = httpChunk("Version")({
method: "GET",
major: 1,
minor: 1,
})
console.log(version)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment