Skip to content

Instantly share code, notes, and snippets.

@Fristi
Last active May 14, 2026 18:33
Show Gist options
  • Select an option

  • Save Fristi/50e819655b87cdadc61290c634691e9e to your computer and use it in GitHub Desktop.

Select an option

Save Fristi/50e819655b87cdadc61290c634691e9e to your computer and use it in GitHub Desktop.
HFix2 + HFunctor2 = recursion schemes on fix point types with arity of 2 - coded with help of Claude :)
final case class HFix2[H[_[_, _], _, _], I, O](unfix: H[[A, B] =>> HFix2[H, A, B], I, O])
/** An algebra: collapses one layer, preserving both indices */
type HAlgebra2[H[_[_, _], _, _], F[_, _]] =
[I, O] => H[F, I, O] => F[I, O]
trait HFunctor2[H[_[_, _], _, _]]:
// cleaner standalone form
def hfmap[F[_, _], G[_, _], I, O](
nt: [A, B] => F[A, B] => G[A, B]
)(hfio: H[F, I, O]): H[G, I, O]
def cata2[H[_[_, _], _, _], F[_, _], I, O](
alg: HAlgebra2[H, F]
)(hf: HFix2[H, I, O])(using hfunctor: HFunctor2[H]): F[I, O] =
def go[A, B]: HFix2[H, A, B] => F[A, B] =
(fix: HFix2[H, A, B]) =>
alg[A, B](hfunctor.hfmap[[A, B] =>> HFix2[H, A, B], F, A, B](
[A, B] => (r: HFix2[H, A, B]) => go[A, B](r)
)(fix.unfix))
go[I, O](hf)
// ─── The pattern functor ────────────────────────────────────────────────────
enum ArrowF[F[_, _], I, O]:
case Arr[F[_, _], I, O](f: I => O) extends ArrowF[F, I, O]
case Compose[F[_, _], I, M, O](
g: F[M, O], h: F[I, M]
) extends ArrowF[F, I, O]
// ─── HFunctor2 instance ─────────────────────────────────────────────────────
given HFunctor2[ArrowF] with
def hfmap[F[_, _], G[_, _], I, O](
nt: [A, B] => F[A, B] => G[A, B]
)(h: ArrowF[F, I, O]): ArrowF[G, I, O] =
h match
case ArrowF.Arr(f) => ArrowF.Arr(f)
case ArrowF.Compose(g, h) => ArrowF.Compose(nt(g), nt(h))
// ─── Smart constructors into HFix2 ─────────────────────────────────────────
type Arrow[I, O] = HFix2[ArrowF, I, O]
def arr[I, O](f: I => O): Arrow[I, O] =
HFix2(ArrowF.Arr(f))
def compose[I, M, O](g: Arrow[M, O], h: Arrow[I, M]): Arrow[I, O] =
HFix2(ArrowF.Compose(g, h))
// ─── Algebra: evaluate to a plain function ──────────────────────────────────
val evalAlg: HAlgebra2[ArrowF, Function1] =
[I, O] => (node: ArrowF[Function1, I, O]) => node match
case ArrowF.Arr(f) => f
case ArrowF.Compose(g, h) => g compose h
val prg = compose(arr[Int, Int](_ * 2), arr[Int, Int](_ + 10))
def eval[I, O]: Arrow[I, O] => (I => O) =
cata2[ArrowF, Function1, I, O](evalAlg)
@main def main = println(eval(prg)(2))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment