Last active
May 31, 2020 09:02
-
-
Save iamanandkris/f02293a40f0a0d33e5e06cad24d24822 to your computer and use it in GitHub Desktop.
Shapeless to dynamically invoke typeclass behaviours on a Generic Type
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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