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

pipez.PipeDerivation.scala Maven / Gradle / Ivy

The newest version!
package pipez

/** Allows derivation macro to glue Pipes for `Pipe[in.field, out.field]` for each case class field/Java Bean accessor
  * or `Pipe[In.Subtype, Out.Subtype]` for each subtype together.
  *
  * Assumes that `Pipe[In, Out]` is interchangeable to `(in: In, ctx: Context) => Result[Out]` where:
  *   - `Context` is anything we want to thread through our calls (like in `ReaderT`)
  *   - `Result[Out]` is any effect we need: `Either[Error, Out]`, `IO[Out]`
  *
  * If there is no need to thread anything you might define your `Context` to `Unit` and if you don't need an effect on
  * returned value you might define `Result[Out] = Out`.
  *
  * @tparam Pipe
  *   type class with two type parameters, used like `Pipe[In, Out]`, which is most likely a Single Abstract Method
  *   interface, implementing some `(In, Context) => Result[Out]` function, which could be automatically generated for
  *   `InProduct => OutProduct` or `InSumType => OutSumType` by piping the corresponding fields or sum type elements by
  *   their name and combining the result together
  */
trait PipeDerivation[Pipe[_, _]] {

  /** Type of value you want to thread through all calls */
  type Context

  /** Type of value you want to return after the call */
  type Result[Out]

  /** Turns a function into your `Pipe` type class */
  def lift[In, Out](f: (In, Context) => Result[Out]): Pipe[In, Out]

  /** Calls `Pipe` as if it was a function */
  def unlift[In, Out](pipe: Pipe[In, Out], in: In, ctx: Context): Result[Out]

  /** Let you inject int information about current Path (extracted field, matched subtypes) into `Context` */
  def updateContext(context: Context, path: => Path): Context

  /** Wraps raw value into `Result` that `Pipe` should return */
  def pureResult[A](a: A): Result[A]

  /** Combines 2 `Results` into 1 */
  def mergeResults[A, B, C](context: Context, ra: Result[A], rb: => Result[B], f: (A, B) => C): Result[C]
}
object PipeDerivation extends PipeDerivationPlatform {

  /** Specialization for `Pipe`s which are interchangeable to `In => Result[Out]` */
  trait NoContext[Pipe[_, _]] extends PipeDerivation[Pipe] {

    final type Context = Unit

    /** Turns a function without `Context` into `Pipe` */
    def simpleLift[In, Out](f: In => Result[Out]): Pipe[In, Out]
    final def lift[In, Out](f: (In, Context) => Result[Out]): Pipe[In, Out] = simpleLift(in => f(in, ()))

    /** Calls `Pipe` as if it was a function without `Context` */
    def simpleUnlift[In, Out](pipe: Pipe[In, Out], in: In): Result[Out]
    final def unlift[In, Out](pipe: Pipe[In, Out], in: In, ctx: Context): Result[Out] = simpleUnlift(pipe, in)

    final def updateContext(context: Context, path: => Path): Context = context

    /** Merges `Result`s as if `Context` was not passed around */
    def simpleMergeResults[A, B, C](ra: Result[A], rb: => Result[B], f: (A, B) => C): Result[C]
    override def mergeResults[A, B, C](context: Context, ra: Result[A], rb: => Result[B], f: (A, B) => C): Result[C] =
      simpleMergeResults(ra, rb, f)
  }

  /** Specialization for `Pipe`s which are interchangeable to `(In, Context) => Out` */
  trait NoParsing[Pipe[_, _]] extends PipeDerivation[Pipe] {

    final type Result[Out] = Out

    final def pureResult[A](a: A): Result[A] = a

    override def mergeResults[A, B, C](context: Context, ra: Result[A], rb: => Result[B], f: (A, B) => C): Result[C] =
      f(ra, rb)
  }

  /** Specialization for `Pipe`s which are interchangeable to `In => Out` */
  trait Simple[Pipe[_, _]] extends NoContext[Pipe] with NoParsing[Pipe] {

    final def simpleMergeResults[A, B, C](ra: A, rb: => B, f: (A, B) => C): C = f(ra, rb)
  }

  type Aux[Pipe[_, _], Context0, Result0[_]] = PipeDerivation[Pipe] {
    type Context   = Context0
    type Result[A] = Result0[A]
  }

  /** Default instance for `In => Out` derivation */
  implicit val simpleFunction: PipeDerivation[_ => _] = new Simple[_ => _] {
    override def simpleLift[In, Out](f: In => Out):              In => Out = f
    override def simpleUnlift[In, Out](pipe: In => Out, in: In): Out       = pipe(in)
  }

  /** Instance for `(In, Ctx) => Out` with customized `Ctx` update */
  def contextFunction[Ctx](
    contextUpdate: (Ctx, Path) => Ctx = (ctx: Ctx, _: Path) => ctx
  ): PipeDerivation[(_, Ctx) => _] = new NoParsing[(_, Ctx) => _] {
    final type Context = Ctx
    override def lift[In, Out](f: (In, Context) => Out):                        (In, Ctx) => Out = f
    override def unlift[In, Out](pipe: (In, Ctx) => Out, in: In, ctx: Context): Out              = pipe(in, ctx)
    override def updateContext(context: Context, path: => Path): Context = contextUpdate(context, path)
  }

  /** Default instance for `(In, Ctx) => Out` with `Ctx` passed around without updating */
  implicit def contextFunction[Ctx]: PipeDerivation[(_, Ctx) => _] = contextFunction[Ctx]()
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy