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!
Your misunderstanding is the use of Prepend. The compiler will generate it for you automatically, you don't ever need to create it manually.
As mentioned in Shapeless and gremlin scala: How do I return the result of a call to `as`?
Prepend
is used to keep the types of the labelled steps. The gremlin-scala readme.md goes into more depth.The compiler actually tells you exactly what it needs:
could not find implicit value for parameter p: shapeless.ops.hlist.Prepend[In,shapeless.::[A,shapeless.HNil]]
So that's what I did: add an implicit Prepend to the scope :) I just sent you a PR, it compiles fine now.
PS: You might want to update your versions of gremlin-scala.