prismofeverything prismofeverything - 14 days ago 8
Scala Question

Scala, gremlin-scala, HLists, Poly2, RightFold and a missing implicit Prepend

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 calling
    as
    normally, but fails when trying to
    RightFold
    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!

Answer

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.