Skip to content

Instantly share code, notes, and snippets.

@titouancreach
Last active December 30, 2024 13:00
Show Gist options
  • Save titouancreach/0607039a20d71131f1db6aa824f5da9f to your computer and use it in GitHub Desktop.
Save titouancreach/0607039a20d71131f1db6aa824f5da9f to your computer and use it in GitHub Desktop.
export type Free<F, A> = Pure<A> | FreeF<F, A>;
export class Pure<A> {
readonly _tag = "Pure" as const;
constructor(public readonly value: A) {}
}
export class FreeF<F, A> {
readonly _tag = "FreeF" as const;
constructor(
public readonly effect: F,
public readonly next: <B>(value: B) => Free<F, A>,
) {}
}
export function pure<F, A>(value: A): Free<F, A> {
return new Pure(value);
}
export function free<F, A>(effect: F): Free<F, A> {
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
return new FreeF(effect, (value) => pure(value as any as A));
}
import { Cancel } from "@repo/shared/src/Cancel";
import { Free, free, pure } from "../Free";
import { Confirmation } from "./UI/Confirmation";
import { AskForReason, CancelReason } from "./UI/Cancel";
type CancelActions<A> =
| {
_tag: "Confirmation";
next: () => Free<CancelActions<A>, A>;
}
| {
_tag: "AskReason";
next: (reason: typeof CancelReason.Type) => Free<CancelActions<A>, A>;
};
export function confirme<A>(next: () => Free<CancelActions<A>, A>): CancelActions<A> {
return { _tag: "Confirmation", next: next };
}
export function askReason(
next: (reason: typeof CancelReason.Type) => Free<CancelActions<Cancel>, Cancel>,
): CancelActions<Cancel> {
return { _tag: "AskReason", next };
}
export const InterpretComponent = ({
effect,
onResult,
goBack,
}: {
onResult: <T>(value: T) => void;
effect: CancelActions<Cancel>;
goBack: () => void;
}) => {
switch (effect._tag) {
case "AskReason":
return <AskForReason onResult={onResult} />;
case "Confirmation":
return (
<Confirmation
text="Are you sure to cancel ?"
onConfirm={() => onResult(undefined)}
goBack={goBack}
/>
);
}
};
export const program = (_: unknown) =>
free<CancelActions<Cancel>, Cancel>(
askReason((reason) =>
free(
confirme(() =>
pure(
new Cancel({
reason: reason,
}),
),
),
),
),
);
import { useState } from "react";
import { Free } from "./Free";
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
export const ProgramRunner = <Effect extends { next: (x: any) => Free<Effect, Value> }, Value>({
program,
onComplete,
interpretComponents: Interpret,
}: {
program: Free<Effect, Value>;
onComplete: (result: Value) => void;
interpretComponents: React.FC<{ effect: Effect; goBack: () => void; onResult: <T>(value: T) => void }>;
}) => {
const [programHistory, setHistory] = useState<Array<Free<Effect, Value>>>([]);
const goBack = () => {
const [previousProgram, ...rest] = programHistorique;
setHistorique(rest);
setCurrentProgram(previousProgram as Free<Effect, Value>);
};
const [currentProgram, setCurrentProgram] = useState(program);
const handleResult = <T,>(value: T) => {
if (currentProgram._tag === "Pure") {
onComplete(currentProgram.value);
return;
}
const nextStep = currentProgram.effect.next(value);
if (nextStep._tag === "Pure") {
onComplete(nextStep.value);
} else {
setCurrentProgram(nextStep);
setHistory([currentProgram, ...programHistorique]);
}
};
if (currentProgram._tag === "Pure") {
onComplete(currentProgram.value);
return null;
}
return <Interpret effect={currentProgram.effect} onResult={handleResult} revenirEnArriere={revenirEnArriere} />;
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment