All Downloads are FREE. Search and download functionalities are using the official Maven repository.

tofu.bi.BiContext.scala Maven / Gradle / Ivy

package tofu.bi

import cats.Bifunctor
import tofu.bi.lift.BiUnlift
import tofu.control.Bind
import tofu.higherKind.bi.FunBK
import glass.{Contains, Equivalent, PExtract, Same}

/** typeclass for access a functional environment in a bifuntor
  * @tparam X
  *   contextual error
  * @tparam C
  *   contextual result
  */
trait BiContext[F[+_, +_], +X, +C] {

  /** base F bifunctor inclusion
    */
  def bifunctor: Bifunctor[F]

  /** read the contextual value of type `C` producing declared contextual error of type `X`
    */
  def context: F[X, C]

  /** focus this context instance
    *
    * @param err
    *   error mapping in the optical form, this could be autogenerated `tofu.optics.Contains`
    * @param res
    *   context mapping in the optical form, this could be autogenerated `tofu.optics.Contains`
    * @return
    *   focused instance of context
    */
  def extract[E, A](err: PExtract[X, Any, E, Nothing], res: PExtract[C, Any, A, Nothing]): BiContext[F, E, A] =
    new BiContextExtractInstance[F, X, C, E, A](this, err, res)

  /** focus this context changing only the error
    *
    * @param ex
    *   error mapping in the optical form
    * @return
    *   focused instance of context
    */
  def lextraxt[A](ex: PExtract[C, Any, A, Nothing]): BiContext[F, X, A] = extract(Same.id, ex)

  /** focus this context changing only the result
    *
    * @param ex
    *   error mapping in the optical for
    * @return
    *   focused instance of context
    */
  def rextract[E](ex: PExtract[X, Any, E, Nothing]): BiContext[F, E, C] = extract(ex, Same.id)
}

object BiContext {
  def apply[F[+_, +_], X, C](implicit inst: BiContext[F, X, C]): BiContext[F, X, C] = inst
}

/** typeclass for locally modification of environment for processess
  */
trait BiLocal[F[+_, +_], X, C] extends BiContext[F, X, C] {

  /** run the process in a locally modified environment
    *
    * @param fea
    *   process to run
    * @param lproj
    *   a modification of the error part
    * @param rproj
    *   a modification of the result part
    * @return
    *   process with the same semantics as `fea` but run in the modified environment
    */
  def bilocal[E, A](fea: F[E, A])(lproj: X => X, rproj: C => C): F[E, A]

  /** same as `bilocal` but modify only the result part
    */
  def local[E, A](fea: F[E, A])(proj: C => C): F[E, A] = bilocal(fea)(identity, proj)

  /** same as `bilocal` but modify only the error part
    */
  def errLocal[E, A](fea: F[E, A])(proj: X => X): F[E, A] = bilocal(fea)(proj, identity)

  /** focus this context instance this will read and modify only the given parts of the context
    *
    * @param err
    *   an optic for reading from and updating a larger context error
    * @param res
    *   an optic for reading from and updating a larget context result
    * @return
    *   focused instance
    */
  def sub[E, A](err: X Contains E, res: C Contains A): BiLocal[F, E, A] =
    new BiLocalSubInstance[F, X, C, E, A](this, err, res)

  /** same as `sub` but focus on the result only
    */
  def rsub[A](cts: C Contains A): BiLocal[F, X, A] = sub(Same.id, cts)

  /** same as `sub` but focus on the error only
    */
  def lsub[E](cts: X Contains E): BiLocal[F, E, C] = sub(cts, Same.id)
}

object BiLocal {
  def apply[F[+_, +_], X, C](implicit inst: BiLocal[F, X, C]): BiLocal[F, X, C] = inst
}

/** typeclass relation for running processes with provided environment
  * @tparam F
  *   rich process type, that requires and has access the the environment
  * @tparam G
  *   base process type
  */
trait BiRun[F[+_, +_], G[+_, +_], X, C] extends BiLocal[F, X, C] with BiUnlift[G, F] {
  override def bifunctor: Bind[F]

  /** run a process starting from the error state for the environment
    *
    * @param fa
    *   a process to run
    * @param x
    *   environmental error
    * @return
    *   base process with provided environment
    */
  def runLeft[E, A](fa: F[E, A])(x: X): G[E, A]

  /** run a process starting from the success state for the environment
    *
    * @param fa
    *   a process to run
    * @param c
    *   environmental result
    * @return
    *   base process with provided environment
    */
  def runRight[E, A](fa: F[E, A])(c: C): G[E, A]

  /** run a process starting the environment from the state defined by disjuction
    *
    * @param fa
    *   a process to run
    * @param ctx
    *   environment
    * @return
    *   base process with provided environment
    */
  def runEither[E, A](fa: F[E, A])(ctx: Either[X, C]): G[E, A] =
    ctx match {
      case Left(x)  => runLeft(fa)(x)
      case Right(r) => runRight(fa)(r)
    }

  /** map this environment using provided equivalences to other types
    *
    * @param err
    *   an optic for mapping context error
    * @param res
    *   an optic for mapping context result
    * @return
    *   focused instance
    */
  def imap[E, A](err: X Equivalent E, res: C Equivalent A): BiRun[F, G, E, A] =
    new BiRunEqvInstance[F, G, X, C, E, A](this, err, res)

  def runRightK(c: C): F FunBK G               = FunBK[F](runRight(_)(c))
  def runLeftK(x: X): F FunBK G                = FunBK[F](runLeft(_)(x))
  def runEitherK(ctx: Either[X, C]): F FunBK G = FunBK[F](runEither(_)(ctx))

  override def bilocal[E, A](fea: F[E, A])(lproj: X => X, rproj: C => C): F[E, A] =
    bifunctor.foldWith[X, C, E, A](context)(
      x => lift(runLeft(fea)(lproj(x))),
      c => lift(runRight(fea)(rproj(c)))
    )

  override def disclose[E, A](k: FunBK[F, G] => F[E, A]): F[E, A] =
    bifunctor.foldWith[X, C, E, A](context)(
      x => k(FunBK.apply[F](runLeft(_)(x))),
      c => k(FunBK.apply[F](runRight(_)(c)))
    )
}
object BiRun {
  def apply[F[+_, +_], G[+_, +_], X, C](implicit inst: BiRun[F, G, X, C]): BiRun[F, G, X, C] = inst
}

class BiContextExtractInstance[F[+_, +_], X, C, E, A](
    ctx: BiContext[F, X, C],
    lext: PExtract[X, Any, E, Nothing],
    rext: PExtract[C, Any, A, Nothing]
) extends BiContext[F, E, A] {

  override def bifunctor: Bifunctor[F] = ctx.bifunctor

  override def context: F[E, A] = bifunctor.bimap(ctx.context)(lext.extract, rext.extract)

  override def extract[E1, A1](
      err: PExtract[E, Any, E1, Nothing],
      res: PExtract[A, Any, A1, Nothing]
  ): BiContext[F, E1, A1] =
    ctx.extract(lext >> err.as[Any, Nothing], rext >> res.as[Any, Nothing])
}

class BiLocalSubInstance[F[+_, +_], X, C, E, A](ctx: BiLocal[F, X, C], lcts: Contains[X, E], rcts: Contains[C, A])
    extends BiContextExtractInstance[F, X, C, E, A](ctx, lcts, rcts) with BiLocal[F, E, A] {
  override def bilocal[E1, A1](fea: F[E1, A1])(lproj: E => E, rproj: A => A): F[E1, A1] =
    ctx.bilocal(fea)(lcts.update(_, lproj), rcts.update(_, rproj))

  override def sub[E1, A1](err: glass.Contains[E, E1], res: glass.Contains[A, A1]): BiLocal[F, E1, A1] =
    ctx.sub(lcts >> err, rcts >> res)
}

class BiRunEqvInstance[F[+_, +_], G[+_, +_], X, C, X1, C1](
    ctx: BiRun[F, G, X, C],
    leq: Equivalent[X, X1],
    req: Equivalent[C, C1]
) extends BiLocalSubInstance[F, X, C, X1, C1](ctx, leq, req) with BiRun[F, G, X1, C1] {

  override def runLeft[E, A](fa: F[E, A])(x1: X1): G[E, A] = ctx.runLeft(fa)(leq.upcast(x1))

  override def runRight[E, A](fa: F[E, A])(c1: C1): G[E, A] = ctx.runRight(fa)(req.upcast(c1))

  override def lift[E, A](fa: G[E, A]): F[E, A] = ctx.lift(fa)

  override def bifunctor: Bind[F] = ctx.bifunctor
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy