Skip to content

Instantly share code, notes, and snippets.

@pulasthibandara
Created April 15, 2020 21:24
Show Gist options
  • Select an option

  • Save pulasthibandara/f0e9f5238d720d8d9dadf89842526d3f to your computer and use it in GitHub Desktop.

Select an option

Save pulasthibandara/f0e9f5238d720d8d9dadf89842526d3f to your computer and use it in GitHub Desktop.
Enables building a coproduct out of a higher kinded type where the type member is type that can be a coproduct.
import shapeless.ops.coproduct.RuntimeInject
import shapeless.{ :+:, CNil, Coproduct, Generic, Inl, Inr }
import scala.language.higherKinds
/**
* Enables building a coproduct out of a higher kinded type where the type member is type
* that can be a coproduct.
*
* for example:
* {{{
* // Given a type Animal = Dog | Cat
* // it's generic coproduct would be Dog :+: Cat :+: CNil
* scala> sealed trait Animal
* scala> final case class Dog(name: String)
* scala> final case class Cat(age: Int)
*
* // Now we want to get a coproduct to a container that encapsulate animal. say this container is Breed[T].
* // the coproduct we're interested in would be
* // Breed[Dog] :+: Breed[Cat] :+: CNil.
*
* scala> case class Breed[T](animal: T)
*
* scala> NestedCoproduct.to(Breed(Dog("name")): Breed[Animal])(_.animal)
* res8: Breed[Cat] :+: Breed[Dog] :+: CNil = Inl(Inr(Dog("name")))
* }}}
*
*
* * @tparam A: The contained type that we want coproducts of
* * @tparam K: The container type of which the coproduct exists
* * @tparam C: The generic coproduct of A
*/
trait NestedCoproduct[A, K[_], C <: Coproduct] {
type Out <: Coproduct
def apply(t: K[A])(by: K[A] => A): Out
}
object NestedCoproduct {
type Aux[A, K[_], C <: Coproduct, O <: Coproduct] = NestedCoproduct[A, K, C] {
type Out = O
}
implicit def caseCNil[A, K[_]]: NestedCoproduct.Aux[A, K, CNil, CNil] = new NestedCoproduct[A, K, CNil] {
type Out = CNil
override def apply(t: K[A])(by: K[A] => A): CNil =
throw new IllegalStateException("Trying to Resolve CNil of a NestedCoproduct")
}
implicit def caseCCons[A, K[_], H, T <: Coproduct, OutT <: Coproduct](
implicit nestedCoproduct: NestedCoproduct.Aux[A, K, T, OutT],
inj: RuntimeInject[H :+: T],
): NestedCoproduct.Aux[A, K, H :+: T, K[H] :+: OutT] = new NestedCoproduct[A, K, H :+: T] {
type Out = K[H] :+: OutT
override def apply(t: K[A])(by: K[A] => A): :+:[K[H], OutT] =
Coproduct.runtimeInject[H :+: T](by(t)) match {
case Some(Inl(a)) => Inl[K[H], nestedCoproduct.Out](t.asInstanceOf[K[H]])
case Some(Inr(a)) => Inr[K[H], nestedCoproduct.Out](nestedCoproduct(t)(by))
case None =>
throw new IllegalStateException(s"The provided ${by(t).getClass} is not a member of coproduct $inj")
}
}
/** A helper to generate the nested coproduct of K[A] */
def to[A, K[_], C <: Coproduct, Out <: Coproduct](a: K[A])(by: K[A] => A)(
implicit gen: Generic.Aux[A, C],
ngen: NestedCoproduct.Aux[A, K, C, Out]
) =
ngen.apply(a)(by)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment