Using Slick with shapeless HList
Asked Answered
T

1

12

Slick's support for HList is generally a great thing. Unfortunately, it comes with its own implementation that does barely provide any useful operations. I'd therefore like to use the shapeless HList instead. This is supposed to be "trivial", but I have no idea how to get this right. Searching the web I found no evidence that somebody managed to accomplish this task.

I assume that it's enough to implement a ProvenShape (as advertised here), but since I fail to understand the concept of Slick's (Proven)Shapes, I did not manage implement this.

I'm basically aiming to boil this

class   Users( tag: Tag )
extends Table[Long :: String :: HNil]( tag, "users" )
{
    def id = column[Long]( "id", O.PrimaryKey, O.AutoInc )

    def email = column[String]( "email" )

    override def * = ( id, email ) <>[TableElementType, ( Long, String )](
        _.productElements,
        hlist => Some( hlist.tupled )
    )
}

down to

class   Users( tag: Tag )
extends Table[Long :: String :: HNil]( tag, "users" )
{
    def id = column[Long]( "id", O.PrimaryKey, O.AutoInc )

    def email = column[String]( "email" )

    override def * = id :: email :: HNil
}
Toon answered 1/8, 2015 at 17:49 Comment(1)
not an answer, but this thread looks relevant: github.com/slick/slick/issues/519Relive
E
9

You hit the nail on the head - if you can produce Shapes for HLists, the rest of Slick's machinery will kick into gear to produce the ProvenShape you need for the default projection.

Here's a bare-bones implementation that allows you to create Tables of HLists:

import scala.annotation.tailrec
import scala.reflect.ClassTag
import shapeless.{ HList, ::, HNil }
import slick.lifted.{ Shape, ShapeLevel, MappedProductShape }

final class HListShape[L <: ShapeLevel, M <: HList, U <: HList : ClassTag, P <: HList]
    (val shapes: Seq[Shape[_, _, _, _]]) extends MappedProductShape[L, HList, M, U, P] {

  def buildValue(elems: IndexedSeq[Any]) =
    elems.foldRight(HNil: HList)(_ :: _)

  def copy(shapes: Seq[Shape[_ <: ShapeLevel, _, _, _]]) =
    new HListShape(shapes)

  def classTag: ClassTag[U] = implicitly

  def runtimeList(value: HList): List[Any] = {
    @tailrec def loop(value: HList, acc: List[Any] = Nil): List[Any] = value match {
      case HNil     => acc
      case hd :: tl => loop(tl, hd :: acc)
    }

    loop(value).reverse
  }

  override def getIterator(value: HList): Iterator[Any] =
    runtimeList(value).iterator

  def getElement(value: HList, idx: Int): Any =
    runtimeList(value)(idx)
}

object HListShape {
  implicit def hnilShape[L <: ShapeLevel]: HListShape[L, HNil, HNil, HNil] =
    new HListShape[L, HNil, HNil, HNil](Nil)

  implicit def hconsShape[L <: ShapeLevel, M1, M2 <: HList, U1, U2 <: HList, P1, P2 <: HList]
      (implicit s1: Shape[_ <: ShapeLevel, M1, U1, P1], s2: HListShape[_ <: ShapeLevel, M2, U2, P2]):
      HListShape[L, M1 :: M2, U1 :: U2, P1 :: P2] =
    new HListShape[L, M1 :: M2, U1 :: U2, P1 :: P2](s1 +: s2.shapes)
}

I've put an implementation on Github here. In principle I think Generic could be brought into the fray to map case classes without the need for <>.

Eolande answered 7/8, 2015 at 16:20 Comment(2)
You should have called the repo "shapefull" ;)Cargian
Ack! What a missed opportunity. Seems so obvious in hind-sight :)Eolande

© 2022 - 2024 — McMap. All rights reserved.