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

io.hireproof.structure.Evidence.scala Maven / Gradle / Ivy

The newest version!
package io.hireproof.structure

import shapeless._
import shapeless.ops.coproduct.{Align, Reverse}
import shapeless.ops.hlist.{Init, Last, Prepend}

import scala.annotation.nowarn

object Evidence {
  trait Product[A] {
    type Out

    def to(a: A): Out

    def from(out: Out): A
  }

  object Product {
    trait Merger[A] {
      type Out

      def to(a: A): Out

      def from(out: Out): A
    }

    object Merger extends Merger1 {
      type Aux[A, O] = Merger[A] { type Out = O }

      implicit def discardLeft[A]: Merger.Aux[Unit |*| A, A] = new Merger[Unit |*| A] {
        override type Out = A

        override def to(a: (Unit, A)): A = a._2

        override def from(out: A): (Unit, A) = ((), out)
      }

      implicit def discardRight[A]: Merger.Aux[A |*| Unit, A] = new Merger[A |*| Unit] {
        override type Out = A

        override def to(ab: A |*| Unit): A = ab._1

        override def from(out: A): A |*| Unit = (out, ())
      }
    }

    trait Merger1 {
      implicit def fallback[A]: Merger.Aux[A, A] = new Merger[A] {
        override type Out = A

        override def to(a: A): A = a

        override def from(out: A): A = out
      }
    }

    def apply[A](implicit evidence: Evidence.Product[A]): evidence.type = evidence

    type Aux[A, O] = Product[A] { type Out = O }

    implicit def base[A]: Evidence.Product.Aux[A :: HNil, A] = new Product[A :: HNil] {
      type Out = A

      def to(a: A :: HNil): A = a.head

      def from(out: Out): Out :: HNil = out :: HNil
    }

    implicit def inductive[A <: HList, B <: HList, C, D](implicit
        init: Init.Aux[A, B],
        last: Last.Aux[A, C],
        evidence: Evidence.Product.Aux[B, D],
        prepend: Prepend.Aux[B, C :: HNil, A]
    ): Evidence.Product.Aux[A, (D, C)] = new Product[A] {
      type Out = (D, C)

      def from(out: Out): A = evidence.from(out._1) :+ out._2

      def to(a: A): Out = (evidence.to(init(a)), last(a))
    }

    implicit def generic[A, B <: HList, C](implicit
        generic: Generic.Aux[A, B],
        evidence: Evidence.Product.Aux[B, C]
    ): Evidence.Product.Aux[A, C] = new Product[A] {
      type Out = C

      def to(a: A): Out = evidence.to(generic.to(a))

      def from(out: Out): A = generic.from(evidence.from(out))
    }
  }

  trait Sum[A] {
    type Out

    def to(a: A): Out

    def from(out: Out): A
  }

  object Sum extends Sum1 {
    def apply[A](implicit evidence: Evidence.Sum[A]): evidence.type = evidence

    type Aux[A, O] = Sum[A] { type Out = O }

    implicit def base[A]: Evidence.Sum.Aux[A :+: CNil, A] = new Sum[A :+: CNil] {
      override type Out = A

      override def to(a: A :+: CNil): A = a.eliminate(identity, _.impossible)

      override def from(a: A): A :+: CNil = Inl(a)
    }

    implicit def inductive[A, B <: Coproduct, C](implicit
        evidence: Evidence.Sum.Aux[B, C]
    ): Evidence.Sum.Aux[A :+: B, Either[C, A]] = new Sum[A :+: B] {
      override type Out = Either[C, A]

      override def to(a: A :+: B): Either[C, A] = a match {
        case Inl(a) => Right(a)
        case Inr(b) => Left(evidence.to(b))
      }

      override def from(out: Either[C, A]): A :+: B = out match {
        case Right(a) => Inl(a)
        case Left(c)  => Inr(evidence.from(c))
      }
    }

    implicit def generic[A, B <: Coproduct, C <: Coproduct, D](implicit
        generic: Generic.Aux[A, B],
        reverseA: Reverse.Aux[B, C],
        reverseB: Reverse.Aux[C, B],
        evidence: Evidence.Sum.Aux[C, D]
    ): Evidence.Sum.Aux[A, D] = new Sum[A] {
      override type Out = D

      override def to(a: A): D = evidence.to(reverseA.apply(generic.to(a)))

      override def from(d: D): A = generic.from(reverseB.apply(evidence.from(d)))
    }
  }

  trait Sum1 {
    @nowarn("msg=never used")
    implicit def aligend[A, B <: Coproduct, C, D <: Coproduct, E](implicit
        generic: Generic.Aux[A, B],
        etc: EitherToCoproduct.Aux[C, D],
        cte: CoproductToEither.Aux[D, C],
        alignA: Align[B, D],
        alignB: Align[D, B],
        evidence: Evidence.Sum.Aux[D, C]
    ): Evidence.Sum.Aux[A, C] = new Sum[A] {
      override type Out = C

      override def to(a: A): C = evidence.to(alignA.apply(generic.to(a)))

      override def from(out: C): A = generic.from(alignB.apply(evidence.from(out)))
    }
  }

  trait EitherToCoproduct[A] {
    type Out <: Coproduct
  }

  object EitherToCoproduct {
    type Aux[A, O] = EitherToCoproduct[A] { type Out = O }

    def apply[A](implicit evidence: EitherToCoproduct[A]): evidence.type = evidence

    @nowarn("msg=never used")
    implicit def base[A](implicit evidence: A <:!< Either[_, _]): Evidence.EitherToCoproduct.Aux[A, A :+: CNil] =
      new EitherToCoproduct[A] {
        override type Out = A :+: CNil
      }

    @nowarn("msg=never used")
    implicit def inductive[A, B <: Coproduct, C](implicit
        evidence: Evidence.EitherToCoproduct.Aux[A, B]
    ): Evidence.EitherToCoproduct.Aux[Either[A, C], C :+: B] = new EitherToCoproduct[Either[A, C]] {
      override type Out = C :+: B
    }
  }

  trait CoproductToEither[A] {
    type Out
  }

  object CoproductToEither {
    type Aux[A, O] = CoproductToEither[A] { type Out = O }

    def apply[A](implicit evidence: CoproductToEither[A]): evidence.type = evidence

    implicit def base[A]: Evidence.CoproductToEither.Aux[A :+: CNil, A] = new CoproductToEither[A :+: CNil] {
      override type Out = A
    }

    @nowarn("msg=never used")
    implicit def inductive[A, B <: Coproduct, C](implicit
        evidence: Evidence.CoproductToEither.Aux[B, C]
    ): Evidence.CoproductToEither.Aux[A :+: B, Either[C, A]] = new CoproductToEither[A :+: B] {
      override type Out = Either[C, A]
    }
  }

  trait Enumeration[A] {
    def values: Set[A]
  }

  object Enumeration {
    def apply[A](implicit evidence: Evidence.Enumeration[A]): evidence.type = evidence

    trait Aux[A, In] {
      def values: List[A]
    }

    object Aux {
      implicit def base[A]: Aux[A, CNil] = new Aux[A, CNil] {
        override def values: List[A] = Nil
      }

      implicit def inductive[A, B <: A, C <: Coproduct](implicit
          witness: Witness.Aux[B],
          evidence: Aux[A, C]
      ): Aux[A, B :+: C] =
        new Aux[A, B :+: C] {
          override def values: List[A] = witness.value :: evidence.values
        }
    }

    @nowarn("msg=never used")
    implicit def generic[A, B <: Coproduct](implicit generic: Generic.Aux[A, B], aux: Aux[A, B]): Enumeration[A] =
      new Enumeration[A] { override def values: Set[A] = aux.values.toSet }
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy