Created
March 2, 2025 05:01
-
-
Save CJSmith-0141/f2a2fc08367304706f403e6bd04d5eb5 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| //> using toolkit typelevel:default | |
| //> using scala 3.6.3 | |
| //> using dep org.typelevel::cats-mtl:1.5.0 | |
| import cats.effect.* | |
| import cats.mtl.{Handle, Raise} | |
| import cats.syntax.all.* | |
| import cats.mtl.syntax.all.* | |
| import cats.* | |
| import cats.data.NonEmptyList | |
| import scala.util.control.NoStackTrace | |
| import scala.reflect.ClassTag | |
| import fs2.io.file.Files | |
| /** Algebra represents a task that can fetch a token which can failed We have | |
| * different way to fetch token, so we want a way to combine multiple tasks | |
| * into one The goal is keep algebra simple but still handle some business | |
| * error so that I don't want to use MonadTransformer like EitherT this is my | |
| * efford using mtl.Hanlde and ApplicativeThrow to solve this problem | |
| */ | |
| sealed trait Algebra[F[_], A]: | |
| def fetch: F[A] | |
| object Algebra: | |
| sealed trait Error extends Throwable with NoStackTrace | |
| object Error: | |
| case class Single(msg: String) extends Error: | |
| override def toString() = msg | |
| case class Multiple(errors: NonEmptyList[Error]) extends Error: | |
| override def toString() = errors.toList.mkString("", ", ", "") | |
| def apply(msg: String): Error = Single(msg) | |
| given Semigroup[Error] with | |
| def combine(x: Error, y: Error): Error = | |
| (x, y) match | |
| case (Error.Single(msg1), Error.Single(msg2)) => | |
| Error.Multiple( | |
| NonEmptyList.of(Error.Single(msg1), Error.Single(msg2)) | |
| ) | |
| case (Error.Single(msg1), Error.Multiple(errors)) => | |
| Error.Multiple(errors.prepend(Error.Single(msg1))) | |
| case (Error.Multiple(errors), Error.Single(msg2)) => | |
| Error.Multiple(errors.append(Error.Single(msg2))) | |
| case (Error.Multiple(errors1), Error.Multiple(errors2)) => | |
| Error.Multiple(errors1.concatNel(errors2)) | |
| /** If We have Handle[F, E] then we have SemigroupK[F] and we accumulate all | |
| * errors to report to users if needed | |
| */ | |
| given [F[_], E: Semigroup] => Handle[F, E] => SemigroupK[F]: | |
| def combineK[A](x: F[A], y: F[A]): F[A] = | |
| x.handleWith[E] { e1 => | |
| y.handleWith[E] { e2 => | |
| (e1 |+| e2).raise[F, A] | |
| } | |
| } | |
| // derive Handle[F, E] via ApplicativeThrow as long as E extends Throwable | |
| // inspire by the yellow sumarine pr in mtl repo | |
| given [F[_]: ApplicativeThrow, E <: Throwable] => (ClassTag[E]) | |
| => Handle[F, E]: | |
| def applicative: Applicative[F] = summon[Applicative[F]] | |
| def raise[E2 <: E, A](e: E2): F[A] = e.raiseError | |
| def handleWith[A](fa: F[A])(f: E => F[A]): F[A] = | |
| fa.handleErrorWith: | |
| case e: E => f(e) | |
| case e => e.raiseError | |
| // We need this to make Algebra a `ApplicativeThrow` | |
| type AlgebraApp = [F[_]] =>> [x] =>> Algebra[F, x] | |
| given [F[_]: Applicative]: Applicative[AlgebraApp[F]] with | |
| def pure[A](x: A): Algebra[F, A] = | |
| new Algebra[F, A]: | |
| def fetch: F[A] = x.pure[F] | |
| def ap[A, B](ff: Algebra[F, A => B])(fa: Algebra[F, A]): Algebra[F, B] = | |
| new Algebra[F, B]: | |
| def fetch: F[B] = | |
| ff.fetch.ap(fa.fetch) | |
| given [F[_]: ApplicativeThrow] => Handle[AlgebraApp[F], Error]: | |
| def applicative: Applicative[AlgebraApp[F]] = | |
| summon[Applicative[AlgebraApp[F]]] | |
| def raise[E2 <: Error, A](e: E2): Algebra[F, A] = new Algebra[F, A]: | |
| def fetch: F[A] = e.raiseError | |
| def handleWith[A](fa: Algebra[F, A])( | |
| f: Error => Algebra[F, A] | |
| ): Algebra[F, A] = | |
| new Algebra[F, A]: | |
| def fetch: F[A] = | |
| fa.fetch.handleErrorWith { | |
| case e: Error => f(e).fetch | |
| case e => e.raiseError | |
| } | |
| /*==========some constructor for Algebra============== */ | |
| def pure[F[_]: Applicative, A](a: A): Algebra[F, A] = | |
| new: | |
| def fetch: F[A] = a.pure[F] | |
| def apply[F[_]: Monad, A](f: F[Either[Error, A]])(using | |
| Raise[F, Error] | |
| ): Algebra[F, A] = | |
| new: | |
| def fetch: F[A] = | |
| f.flatMap(Raise[F, Error].fromEither) | |
| def raise[F[_], E, A](error: E)(using Raise[F, E]): Algebra[F, A] = | |
| new: | |
| def fetch: F[A] = error.raise | |
| type Token = String | |
| /*==========some pre implemented ============== */ | |
| def local[F[_]: Concurrent: Files](): Algebra[F, Token] = | |
| raise(Error("Local Not implemented")) | |
| def env[F[_]: Concurrent]: Algebra[F, Token] = | |
| raise(Error("Env Not implemented")) | |
| def kubernetes[F[_]: Concurrent: Files](): Algebra[F, Token] = | |
| raise(Error("Kubernetes Not implemented")) | |
| def hardcoded[F[_]: Applicative]: Algebra[F, Token] = pure("token") | |
| object App extends IOApp.Simple: | |
| import Algebra.{*, given} | |
| def run: IO[Unit] = | |
| (local[IO]() <+> env[IO]).fetch.attempt.flatMap { | |
| case Right(token) => IO.println(s"Token: $token") | |
| case Left(error) => IO.println(s"Error: $error") // println error | |
| } >> | |
| (local[IO]() <+> hardcoded[IO]).fetch.attempt.flatMap { | |
| case Right(token) => IO.println(s"Token: $token") // println token | |
| case Left(error) => IO.println(s"Error: $error") | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment