So, I am trying to encapsulate a series of operations from gremlin-scala
into an HList
so I can do a RightFold
over them (this would allow me to construct a gremlin query as data: specifically an HList
of Operations
).
Here is what I mean: usually you can make a call to gremlin-scala
like so:
import gremlin.scala._
import org.apache.tinkerpop.gremlin.tinkergraph.structure.TinkerFactory
def graph = TinkerFactory.createModern.asScala
graph.V.hasLabel("person").out("created").as("creations").toList.map(_.valueMap)
---> List[Map[String,Any]] = List(Map(name -> lop, lang -> java), Map(name -> ripple, lang -> java), Map(name -> lop, lang -> java), Map(name -> lop, lang -> java))
This is all well and good, but I want to be able to construct a query as data. I am modeling this as an HList
of Operations
like so:
sealed trait Operation
case class VertexOperation[Labels <: HList](vertex: String) extends Operation {
def operate(graph: Graph): GremlinScala[Vertex, Labels] = {
graph.V.hasLabel(vertex).asInstanceOf[GremlinScala[Vertex, Labels]]
}
}
case class OutOperation[Labels <: HList](out: String) extends Operation {
def operate(vertex: GremlinScala[Vertex, Labels]): GremlinScala[Vertex, Labels] = {
vertex.out(out)
}
}
Then I would be able to create a query by putting these in an HList
like so:
import shapeless._
val query = OutOperation("created") :: VertexOperation("person") :: HNil
Now that I have these in an HList
, I can do a RightFold
to apply them one by one to the graph:
trait ApplyOperationDefault extends Poly2 {
implicit def default[T, L <: HList] = at[T, L] ((_, acc) => acc)
}
object ApplyOperation extends ApplyOperationDefault {
implicit def vertex[T, L <: HList, S <: HList] = at[VertexOperation[S], Graph] ((t, acc) => t.operate(acc))
implicit def out[T, L <: HList, S <: HList] = at[OutOperation[S], GremlinScala[Vertex, S]] ((t, acc) => t.operate(acc))
}
object Operation {
def process[Input, Output, A <: HList](operations: A, input: Input) (implicit folder: RightFolder.Aux[A, Input, ApplyOperation.type, Output]): Output = {
operations.foldRight(input) (ApplyOperation)
}
}
And call it like so:
val result = Operation.process(query, graph).toList
This all works! And shows great promise.
Here is where I get to my issue: when I try to do this with the as
operation, I can get the Operation
to compile:
case class AsOperation[A, In <: HList](step: String) extends Operation {
def operate(g: GremlinScala[A, In]) (implicit p: Prepend[In, ::[A, HNil]]): GremlinScala[A, p.Out] = {
g.as(step)
}
}
(I added that (implicit p: Prepend[In, ::[A, HNil]])
in there because the compiler was complaining otherwise)... but when I try to create the implicit handler for this case along with the others, it fails:
implicit def as[T, L <: HList, A, In <: HList] = at[AsOperation[A, In], GremlinScala[A, In]] ((t, acc) => t.operate(acc))
---> could not find implicit value for parameter p: shapeless.ops.hlist.Prepend[In,shapeless.::[A,shapeless.HNil]]
So, several questions here:
- What is this implicit
Prepend
all about, and why do I need it? - Why is it able to find the implicit
Prepend
when callingas
normally, but fails when trying toRightFold
over it? - How do I create an implicit instance of
Prepend
? - Once created, how would I pass it in to the invocation of
operate
? - What is the right way to do this??
I probably have more questions, but these are the main ones. I have been reading up on type-level programming and shapeless in general, and I really kind of love it, but this kind of stuff is maddening. I know there is some subtle type thing I am missing here, but it is hard to know where to start deciphering what is missing.
Thanks for any help! I truly want to love scala and shapeless, hoping to get over this obstacle soon.
EDIT: I made a minimal repo reproducing the problem here: https://github.com/bmeg/leprechaun
Hopefully that helps!