Skip to content

Instantly share code, notes, and snippets.

@iamanandkris
Last active May 31, 2020 09:02
Show Gist options
  • Save iamanandkris/f02293a40f0a0d33e5e06cad24d24822 to your computer and use it in GitHub Desktop.
Save iamanandkris/f02293a40f0a0d33e5e06cad24d24822 to your computer and use it in GitHub Desktop.
Shapeless to dynamically invoke typeclass behaviours on a Generic Type
import shapeless._
import shapeless.ops.coproduct.Inject
// "com.chuusai" %% "shapeless" % "2.3.3"
object NodeBehaviourExecutor extends App {
sealed trait NodeData {
val id: String
}
case class MenuNode(val id: String = "123") extends NodeData
case class GotoNode(val id: String = "110") extends NodeData
trait NodeBehaviour[+A] {
def getNextNodeId: String
}
trait BehaviourCreator[A] {
def getBehaviour(value: A): NodeBehaviour[A]
}
object BehaviourCreator {
def apply[A](implicit behaviourCreator: BehaviourCreator[A]): BehaviourCreator[A] = behaviourCreator
def createBehaviour[A](func: A => NodeBehaviour[A]): BehaviourCreator[A] =
new BehaviourCreator[A] {
override def getBehaviour(value: A): NodeBehaviour[A] = func(value)
}
implicit val menuNodeBehaviour: BehaviourCreator[MenuNode] =
createBehaviour(str => new NodeBehaviour[MenuNode] {
override def getNextNodeId: String = str.id
})
implicit val gotoNodeBehaviour: BehaviourCreator[GotoNode] =
createBehaviour(str => new NodeBehaviour[GotoNode] {
override def getNextNodeId: String = str.id
})
/**
* We can combine these building blocks to create an encoder for our HList.
* We’ll use two rules: one for HNil and one for :: as shown below:
*/
implicit val hnilEncoder: BehaviourCreator[HNil] = createBehaviour(
str => new NodeBehaviour[HNil] {
override def getNextNodeId: String = throw new Exception("Inconceivable!")
})
implicit val cnilEncoder: BehaviourCreator[CNil] = createBehaviour(
str => new NodeBehaviour[CNil] {
override def getNextNodeId: String = throw new Exception("Inconceivable!")
})
implicit def hI[H, T <: Coproduct]: Inject[H :+: T, H] = {
new Inject[H :+: T, H] {
override def apply(i: H): H :+: T = Inl(i)
}
}
implicit def tI[H, T <: Coproduct]: Inject[H :+: T, T] =
new Inject[H :+: T, T] {
override def apply(i: T): H :+: T = Inr(i)
}
implicit def genericNodeBehaviour[A, R](
implicit
gen: Generic.Aux[A, R],
enc: BehaviourCreator[R]
): BehaviourCreator[A] = new BehaviourCreator[A] {
override def getBehaviour(value: A): NodeBehaviour[A] = new NodeBehaviour[A] {
override def getNextNodeId: String = enc.getBehaviour(gen.to(value)).getNextNodeId
}
}
implicit def coproductBehaviourCreator[H, T <: Coproduct](
implicit
hEncoder: BehaviourCreator[H],
tEncoder: BehaviourCreator[T]
): BehaviourCreator[H :+: T] =
new BehaviourCreator[H :+: T] {
override def getBehaviour(value: H :+: T): NodeBehaviour[H :+: T] = {
value match {
case Inl(h) => new NodeBehaviour[H :+: T] {
override def getNextNodeId: String = hEncoder.getBehaviour(h).getNextNodeId
}
case Inr(t) => new NodeBehaviour[H :+: T] {
override def getNextNodeId: String = tEncoder.getBehaviour(t).getNextNodeId
}
}
}
}
}
import BehaviourCreator._
implicit def getNodeBehaviour[A](a: A)(implicit v: BehaviourCreator[A]): NodeBehaviour[A] = v.getBehaviour(a)
val nd: List[NodeData] = List(MenuNode(), GotoNode())
println(s"executedValue - ${nd.map(x => x.getNextNodeId)}")
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment