1
votes

I am trying to make this work in a type-safe manner:

val rows = db.select( ID_COLUMN, STR("name"), INT("count") ). from("tablename") ......
for ( (id, name, count) <- rows ) {
       //some code to use the individual values
}

I have not found, so far, another way of making this type-safe besides the shapeless.

I do know about slick and other ORMs, so please don't send me there.

HList seems to be the way to approach passing in a heterogeneous bunch of objects and get back a list of values with specific types.

I tried to do something like this :

 trait ColDef [X] {
     def colName
     def valalue (m:Map[String, Object]):X
 }

 object XS extends ColDef[String]{
     def colName = "s"
     def value (m:Map[String, Object]) = m("s").asInstanceOf[String] 
 }

 object XI extends ColDef[Integer]{
     def colName = "i"
     def value (m:Map[String, Object]) = m("i").asInstanceOf[Integer] 
 }

 val xhl = XS::XI::HNil
 val data:Map[String,Object] = Map(("s" ->"asdf"), ("i" -> new Integer(5)))

 object p1 extends Poly1 {
    implicit def getValue[T, S <% ColDef[T]] = at[S] (coldef => coldef.value(data) )
 }

 val res = xhl map p1 
 val (s, i) = res.tupled //this works, giving s:String, and i:Integer

 //but following does not compile
 def nextstep(hl : HList, data:Map[String,Object]) = {
     hl map p1
 }

Just to reiterate what is essential:

HList/Shapeless are likely candidates for solving the problem, but are not the goal of this exercise. My goal is to have the return type of a function correspond to the variable types and number of parameters passed in.

It would be ideal if the user of my little utility did not need to know about HList, but that is not a real requirement.

The essential part is having the type of the result match the type of params :

val param1 = new Conf[String]
val param2 = new Conf[Integer]
 ... etc ....
val res = function(param1, param2, param3) 

More precisely payload types of the parameters above, so that the type of res is T(String, Integer, ....) .


Let me add another clarification. I want to create a method for arbitrary arity and avoid creating a function for each count of parameters. If I was ok with 22 methods, it would look something like this:

def f[A](a:ColDef[A]):(A)
def f[A,B](a:ColDef[A], b:ColDef[B]):(A,B)
def f[A,B,C](a:ColDef[A], b:ColDef[B],c:ColDef[C]):(A,B,C)
 ..... and so on

And then I would not need shapeless or HList as all the possible tuples would be explicitly defined.

Actually looking at those 3 signatures - making them 22 would take a bit of time, but would avoid shapeless dependency an their implementations would also be one-liners. May be I should just spend 30 minutes and do it manually (or with a little script).

1
Not sure I fully understand the questions, but do you want something like this: def function[A,B,C](p1: ColDef[A], p2: ColDef[B], p3: ColDef[C]) = p1::p2::p3::HNil - Volker Stampa
@VolkerStampa No I don't think that your function signature is what I am looking for. If having fixed arity was acceptable then I would not need an HList, since I could define each function returning appropriately sized tuple. - Tjunkie

1 Answers

1
votes

Have it work with HList input and output

You just need some minor adjustments to the definition of nextstep:

def nextstep[L <: HList](hl: L, data: Map[String, Object])(implicit mapper: Mapper[p1.type, L]): mapper.Out = {
  hl map p1
}

I made the exact type of the HList L a type argument, and I required the implicit needed by map (see map's definition).

Then you (or your user) can simply call

nextstep(XS :: XI :: HNil, data)

and they will get a String :: Integer :: HNil as return type. It works as expected for any HList of ColDef[...] (returning an HList of the result).

Have it work with a tuple as input (and output HList)

In order to have it return a tuple instead of an HList, you can define it this way:

import shapeless.ops.hlist.{Tupler, Mapper}
def nextstep[L <: HList, OutL <: HList, Out](hl: L, data: Map[String, Object])(implicit mapper: Mapper.Aux[p1.type, L, OutL], tupler: Tupler.Aux[OutL, Out]): Out = {
  tupler(hl map p1)
}

nextstep(XS :: XI :: HNil, data) will then return a (String, Integer), and nextstep will return the rightly typed tuple in the general case.

Tuple as input and output

The final step to accept a tuple of ColDef as input and return a tuple as output looks like:

def nextstep[P, L <: HList, OutL <: HList, Out](c: P, data: Map[String, Object])(implicit gen: Generic.Aux[P, L], mapper: Mapper.Aux[p1.type, L, OutL], tupler: Tupler.Aux[OutL, Out]): Out = {
  tupler(gen.to(c) map p1)
}

The logic here is very similar to the functions defined in shapeless.syntax.std.TupleOps: converting the tuple to an HList, processing the HList, convert the output to a tuple.