Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Dynamically create extensible record in shapeless 2.0

I need to produce an extensible record given an HList of keys and a map of values, here's a MWE of what I'm trying to achieve (you can copy/paste this in any REPL with shapeless 2.0 available, in order to reproduce the issue)

import shapeless._; import syntax.singleton._; import record._

case class Foo[T](column: Symbol)

val cols = Foo[String]('column1) :: HNil

val values = Map("column1" -> "value1")

object toRecord extends Poly1 {
  implicit def Foo[T] = at[Foo[T]] { foo =>
    val k = foo.column.name
    val v = values.get(k)
    (k ->> v)
  }
}

val r = cols.map(toRecord)
// r: shapeless.::[Option[String] with shapeless.record.KeyTag[k.type,Option[String]] forSome { val k: String },shapeless.HNil] = Some(value1) :: HNil

val value = r("column1")
// error: No field String("column1") in record shapeless.::[Option[String] with shapeless.record.KeyTag[k.type,Option[String]] forSome { val k: String },shapeless.HNil]
   val value = r("column1")

If I try defining the record manually everything works as expected

 val q = ("column1" ->> Some("value1")) :: HNil
 // q: shapeless.::[Some[String] with shapeless.record.KeyTag[String("column1"),Some[String]],shapeless.HNil] = Some(value1) :: HNil

 q("column1")
 // Some[String] = Some(value1)

Clearly the difference is that in one case the KeyTag has type

KeyTag[String("column1"), Some[String]]

and in the (non-working) other

KeyTag[k.type,Option[String]] forSome { val k: String }

I sense the issue is with the string k not being statically known, but I have no clue on how to fix this. Generally speaking, is there a way of dynamically generating an extensible record from a list of keys?

I fear the answer is to use a macro, but I'd be glad if another solution existed.

like image 303
Gabriele Petronella Avatar asked Aug 13 '14 10:08

Gabriele Petronella


1 Answers

This isn't too bad if you can change your Foo definition a bit to allow it to keep track of the singleton type of the column key (note that I've removed the unused T type parameter):

import shapeless._; import syntax.singleton._; import record._

case class Foo[K <: Symbol](column: Witness.Aux[K])

val cols = Foo('column1) :: HNil
val values = Map("column1" -> "value1")

object toRecord extends Poly1 {
  implicit def atFoo[K <: Symbol] = at[Foo[K]] { foo =>
    field[K](values.get(foo.column.value.name))
  }
}

val r = cols.map(toRecord)

And then:

scala> val value = r('column1)
value: Option[String] = Some(value1)

Note that I've changed your string key ("column1") to a symbol, since that's what we've put into the record.

like image 96
Travis Brown Avatar answered Sep 17 '22 15:09

Travis Brown