From 21cd15d7a259e3ada2401e2585455587a56bdfbd Mon Sep 17 00:00:00 2001 From: Amir Saeid Date: Sun, 8 Feb 2026 22:52:14 +0000 Subject: first commit --- .../main/scala/com/codiff/fairstream/Fair.scala | 60 +++++++++++++++++ .../main/scala/com/codiff/fairstream/FairT.scala | 77 ++++++++++++++++++++++ .../main/scala/com/codiff/fairstream/Main.scala | 26 ++++++++ 3 files changed, 163 insertions(+) create mode 100644 core/src/main/scala/com/codiff/fairstream/Fair.scala create mode 100644 core/src/main/scala/com/codiff/fairstream/FairT.scala create mode 100644 core/src/main/scala/com/codiff/fairstream/Main.scala (limited to 'core/src/main/scala/com') diff --git a/core/src/main/scala/com/codiff/fairstream/Fair.scala b/core/src/main/scala/com/codiff/fairstream/Fair.scala new file mode 100644 index 0000000..1436eba --- /dev/null +++ b/core/src/main/scala/com/codiff/fairstream/Fair.scala @@ -0,0 +1,60 @@ +package com.codiff.fairstream + +import cats.{Alternative, Monad, StackSafeMonad} + +sealed trait Fair[+A] + +object Fair { + case object Nil extends Fair[Nothing] + case class One[+A](a: A) extends Fair[A] + case class Choice[+A](a: A, rest: Fair[A]) extends Fair[A] + + class Incomplete[+A](expr: => Fair[A]) extends Fair[A] { + lazy val step: Fair[A] = expr + } + + object Incomplete { + def apply[A](expr: => Fair[A]): Incomplete[A] = new Incomplete(expr) + + def unapply[A](s: Incomplete[A]): Some[Fair[A]] = Some(s.step) + } + + def empty[A]: Fair[A] = Nil + + def unit[A](a: A): Fair[A] = One(a) + + def constant[A](a: A): Fair[A] = Choice(a, Incomplete(constant(a))) + + def guard(cond: Boolean): Fair[Unit] = if (cond) unit(()) else empty + + def mplus[A](left: Fair[A], right: => Fair[A]): Fair[A] = left match { + case Nil => Incomplete(right) + case One(a) => Choice(a, right) + case Choice(a, r) => Choice(a, mplus(right, r)) + case Incomplete(i) => + right match { + case Nil => Incomplete(i) + case One(b) => Choice(b, i) + case Choice(b, r2) => Choice(b, Incomplete(mplus(i, r2))) + case Incomplete(j) => Incomplete(mplus(i, j)) + } + } + + implicit val fairMonad + : Monad[Fair] with Alternative[Fair] with StackSafeMonad[Fair] = + new Monad[Fair] with Alternative[Fair] with StackSafeMonad[Fair] { + def empty[A]: Fair[A] = Fair.empty + + def pure[A](a: A): Fair[A] = Fair.unit(a) + + def flatMap[A, B](fa: Fair[A])(f: A => Fair[B]): Fair[B] = fa match { + case Nil => Nil + case One(a) => f(a) + case Choice(a, r) => combineK(f(a), Incomplete(flatMap(r)(f))) + case Incomplete(i) => Incomplete(flatMap(i)(f)) + } + + def combineK[A](x: Fair[A], y: Fair[A]): Fair[A] = mplus(x, y) + } + +} diff --git a/core/src/main/scala/com/codiff/fairstream/FairT.scala b/core/src/main/scala/com/codiff/fairstream/FairT.scala new file mode 100644 index 0000000..c652c0f --- /dev/null +++ b/core/src/main/scala/com/codiff/fairstream/FairT.scala @@ -0,0 +1,77 @@ +package com.codiff.fairstream + +import cats.{Alternative, Applicative, Monad} + +sealed trait FairE[M[_], A] + +object FairE { + final case class Nil[M[_], A]() extends FairE[M, A] + final case class One[M[_], A](a: A) extends FairE[M, A] + final case class Choice[M[_], A](a: A, rest: FairT[M, A]) extends FairE[M, A] + final case class Incomplete[M[_], A](rest: FairT[M, A]) extends FairE[M, A] +} + +final case class FairT[M[_], A](run: M[FairE[M, A]]) + +object FairT { + def empty[M[_], A](implicit M: Applicative[M]): FairT[M, A] = + FairT(M.pure[FairE[M, A]](FairE.Nil())) + + def unit[M[_], A](a: A)(implicit M: Applicative[M]): FairT[M, A] = + FairT(M.pure[FairE[M, A]](FairE.One(a))) + + def suspend[M[_], A](s: FairT[M, A])(implicit + M: Applicative[M] + ): FairT[M, A] = + FairT(M.pure[FairE[M, A]](FairE.Incomplete(s))) + + def mplus[M[_], A](left: FairT[M, A], right: => FairT[M, A])(implicit + M: Monad[M] + ): FairT[M, A] = { + type E = FairE[M, A] + FairT(M.flatMap[E, E](left.run) { + case FairE.Nil() => M.pure[E](FairE.Incomplete(right)) + case FairE.One(a) => M.pure[E](FairE.Choice(a, right)) + case FairE.Choice(a, r) => M.pure[E](FairE.Choice(a, mplus(right, r))) + case FairE.Incomplete(i) => + M.map[E, E](right.run) { + case FairE.Nil() => FairE.Incomplete(i) + case FairE.One(b) => FairE.Choice(b, i) + case FairE.Choice(b, r2) => FairE.Choice(b, mplus(i, r2)) + case FairE.Incomplete(j) => FairE.Incomplete(mplus(i, j)) + } + }) + } + + def flatMap[M[_], A, B]( + fa: FairT[M, A] + )(f: A => FairT[M, B])(implicit M: Monad[M]): FairT[M, B] = { + type EB = FairE[M, B] + FairT(M.flatMap[FairE[M, A], EB](fa.run) { + case FairE.Nil() => M.pure[EB](FairE.Nil()) + case FairE.One(a) => f(a).run + case FairE.Choice(a, r) => mplus(f(a), suspend(flatMap(r)(f))).run + case FairE.Incomplete(i) => M.pure[EB](FairE.Incomplete(flatMap(i)(f))) + }) + } + + implicit def fairTMonad[M[_]: Monad] + : Monad[FairT[M, *]] with Alternative[FairT[M, *]] = + new Monad[FairT[M, *]] with Alternative[FairT[M, *]] { + def empty[A]: FairT[M, A] = FairT.empty + + def pure[A](a: A): FairT[M, A] = FairT.unit(a) + + def flatMap[A, B](fa: FairT[M, A])(f: A => FairT[M, B]): FairT[M, B] = + FairT.flatMap(fa)(f) + + def tailRecM[A, B](a: A)(f: A => FairT[M, Either[A, B]]): FairT[M, B] = + flatMap(f(a)) { + case Left(next) => tailRecM(next)(f) + case Right(b) => FairT.unit(b) + } + + def combineK[A](x: FairT[M, A], y: FairT[M, A]): FairT[M, A] = mplus(x, y) + } + +} diff --git a/core/src/main/scala/com/codiff/fairstream/Main.scala b/core/src/main/scala/com/codiff/fairstream/Main.scala new file mode 100644 index 0000000..3c7c0b7 --- /dev/null +++ b/core/src/main/scala/com/codiff/fairstream/Main.scala @@ -0,0 +1,26 @@ +/* + * Copyright 2026 codiff + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.codiff.fairstream + +import cats.effect.IO +import cats.effect.IOApp + +object Main extends IOApp.Simple { + + def run: IO[Unit] = + IO.println("Hello sbt-typelevel!") +} -- cgit v1.2.3