aboutsummaryrefslogtreecommitdiff
path: root/core/src/main/scala/com
diff options
context:
space:
mode:
authorAmir Saeid <amir@glgdgt.com>2026-02-08 22:52:14 +0000
committerAmir Saeid <amir@glgdgt.com>2026-02-08 22:52:14 +0000
commit21cd15d7a259e3ada2401e2585455587a56bdfbd (patch)
treea99117c5eb8f86c21132f5541b65c7cf393b0354 /core/src/main/scala/com
first commit
Diffstat (limited to 'core/src/main/scala/com')
-rw-r--r--core/src/main/scala/com/codiff/fairstream/Fair.scala60
-rw-r--r--core/src/main/scala/com/codiff/fairstream/FairT.scala77
-rw-r--r--core/src/main/scala/com/codiff/fairstream/Main.scala26
3 files changed, 163 insertions, 0 deletions
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!")
+}