Skip to content

Instantly share code, notes, and snippets.

@isthatcentered
Created July 18, 2023 07:21
Show Gist options
  • Save isthatcentered/8761a5e6771a70d5b5bb7f047ca56cd8 to your computer and use it in GitHub Desktop.
Save isthatcentered/8761a5e6771a70d5b5bb7f047ca56cd8 to your computer and use it in GitHub Desktop.
Gherkin DSL
// Note, this is a (very) quick POC, this is far from perfect and needs improvement. But it works
import { pipe, identity } from "@effect/data/Function"
import * as T from "@effect/io/Effect"
import { TaggedClass } from "@effect/data/Data"
import * as Match from "@effect/match"
export class Given<R, S, A> extends TaggedClass("Given")<{
fa: T.Effect<R, any, S>
_A: (a: never) => A
}> {}
export class AndGiven<R, S, A> extends TaggedClass("AndGiven")<{
use: <X>(
go: <R1, R2, S1, S2>(params: {
fa: Gherkin<R1, S1, void>
fb: (s: S1) => T.Effect<R2, any, S2>
_R: (a: R1 | R2) => R
_S: (a: S2) => S
_A: (a: never) => A
}) => X,
) => X
}> {}
export class When<R, S, A> extends TaggedClass("When")<{
use: <X>(
go: <R1, R2, S1, A1>(params: {
fa: Gherkin<R1, S1, never>
fb: (s: S1) => T.Effect<R2, any, A1>
_R: (a: R1 | R2) => R
_S: (a: S1) => S
_A: (a: A1) => A
}) => X,
) => X
}> {}
export class Then<R, S, A> extends TaggedClass("Then")<{
use: <X>(
go: <R1, R2, S1, A1>(params: {
fa: Gherkin<R1, S1, A1>
fb: (context: [a: A1, s: S1]) => T.Effect<R2, any, any>
_R: (a: R1 | R2) => R
_S: (a: S1) => S
_A: (a: A1) => A
}) => X,
) => X
}> {}
type Gherkin<R, S, A> =
| Given<R, S, A>
| AndGiven<R, S, A>
| When<R, S, A>
| Then<R, S, A>
const given = <R, S2>(fa: T.Effect<R, any, S2>): Gherkin<R, S2, never> =>
new Given({ fa, _A: identity })
const andGiven =
<R1, S1, R2, S2>(fb: (s: S1) => T.Effect<R2, any, S2>) =>
(fa: Gherkin<R1, S1, never>): Gherkin<R1 | R2, S2, never> =>
new AndGiven({
use: go =>
go({
fa,
fb,
_S: identity,
_R: identity,
_A: identity,
}),
})
const when =
<R1, R2, S, A>(fb: (s: S) => T.Effect<R2, any, A>) =>
(fa: Gherkin<R1, S, never>): Gherkin<R1 | R2, S, A> =>
new When({
use: go =>
go({
fa,
fb,
_S: identity,
_R: identity,
_A: identity,
}),
})
const then =
<R1, R2, S, A>(fb: (params: [A, S]) => T.Effect<R2, any, any>) =>
(fa: Gherkin<R1, S, A>): Gherkin<R1 | R2, S, A> =>
new Then({
use: go =>
go({
fa,
fb,
_S: identity,
_R: identity,
_A: identity,
}),
})
const andThen =
<R1, R2, S, A>(fb: (params: [A, S]) => T.Effect<R2, any, any>) =>
(fa: Gherkin<R1, S, A>): Gherkin<R1 | R2, S, A> =>
new Then({
use: go =>
go({
fa,
fb,
_S: identity,
_R: identity,
_A: identity,
}),
})
const unsafeCoerceEnv =
<RA, RB>(_proof: (env: RA) => RB) =>
<E, A>(fa: T.Effect<RA, E, A>): T.Effect<RB, E, A> =>
fa as T.Effect<any, E, A>
const toEffect = <R, S, A>(test: Gherkin<R, S, A>): T.Effect<R, any, A> => {
const loop = <R, S, A>(test: Gherkin<R, S, A>): T.Effect<R, any, [S, A]> =>
pipe(
Match.value(test),
Match.tag("Given", _ =>
pipe(
_.fa,
T.map(s => [s, {}] as [S, never]),
),
),
Match.tag("AndGiven", _ =>
_.use(_ =>
pipe(
loop(_.fa),
T.flatMap(([s]) => _.fb(s)),
T.map(s => [_._S(s), {}] as [S, never]),
unsafeCoerceEnv(_._R),
),
),
),
Match.tag("When", _ =>
_.use(_ =>
pipe(
loop(_.fa),
T.flatMap(([s]) =>
pipe(
_.fb(s),
T.map(a => [_._S(s), _._A(a)] as [S, A]),
),
),
unsafeCoerceEnv(_._R),
),
),
),
Match.tag("Then", _ =>
_.use(_ =>
pipe(
loop(_.fa),
T.flatMap(([s, a]) => {
return pipe(
_.fb([a, s]),
T.map(() => [_._S(s), _._A(a)] as [S, A]),
)
}),
unsafeCoerceEnv(_._R),
),
),
),
Match.exhaustive,
)
return pipe(
loop(test),
T.map(([, a]) => a),
)
}
test(`Blah`, async () =>
await pipe(
given(T.succeed(5 as const)),
andGiven(s => T.succeed([s, 10] as const)),
when(([n1, n2]) => T.succeed(n1 + n2)),
then(([a, c]) =>
T.sync(() => {
expect(c).toEqual<typeof c>([5, 10])
expect(a).toEqual(15)
}),
),
andThen(([a, c]) =>
T.sync(() => {
expect(c).toEqual<typeof c>([5, 10])
expect(a).toEqual(15)
}),
),
toEffect,
T.runPromise,
))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment