Reputation: 1889
Akin to this question here (Mapping over Shapeless record), I am attempting to map over a trivial shapeless record (in this case, if I encounter a value of type Int
, I want to convert it to a Double
).
object Main extends App {
import shapeless._ ; import syntax.singleton._ ; import record._
import ops.record._
import syntax.singleton._
case class S(int:Int,t:String)
val s = S(3,"a")
val gen = LabelledGeneric[S]
val rec = gen.to(s)
val extended = rec + ('inPrint ->> true)
val removed = rec - 'int
val keys = Keys[gen.Repr]
val options =
('awesomeString ->> "a") ::
('epicInt ->> 5:Int) ::
HNil
def intToDouble(i:Int):Double = i.toDouble
object bind extends FieldPoly {
implicit def rpb[T, K](implicit witness: Witness.Aux[K]): Case.Aux[
FieldType[K, Int],
FieldType[K, Double]
] = atField(witness)(intToDouble)
}
val z = options.map(bind)
}
When I try to compile however, I get the following error
could not find implicit value for parameter mapper: shapeless.ops.hlist.Mapper[Main.bind.type,shapeless.::[String with shapeless.record.KeyTag[Symbol with shapeless.tag.Tagged[String("awesomeString")],String],shapeless.::[Int,shapeless.HNil]]]
Is there something critical that I am missing?
Upvotes: 2
Views: 457
Reputation: 23046
There's one very minor problem with your example. You have,
val options =
('awesomeString ->> "a") ::
('epicInt ->> 5:Int) ::
HNil
If you paste this into the REPL you'll see that you've lost track of the 'epicInt
key in the last element of the record. This is because the type ascription binds less tightly than the ->>
operator so you have, in effect, first tagged your value with its key and then immediately thrown it away again. This leaves you with a valid HList
, but unfortunately not one which is the right shape to be a record. The fix is to either drop the type ascription altogether, or use parentheses (ie. (5: Int)
.
The bigger problem is your use of FieldPoly
here. FieldPoly
is intended for situations which the key you want to operate on is statically known. That's not the case here: the type variable K
is free and would have to be inferred. Unfortunately there's no way that it can be: it would have to be inferred from the first argument to atField
but that, in turn, depends on K
via the implicit definition of witness
.
It could be that a different variant of FieldPoly
that better matches your scenario would be useful. In the meantime, an ordinary Poly1
which operates on whole fields (ie. the key combined with the value) will do what you want,
trait bind0 extends Poly1 {
implicit def default[E] = at[E](identity) // default case for non-Int fields
}
object bind extends bind0 {
implicit def caseInt[K] = // case for fields with Int values
at[FieldType[K, Int]](field[K](intToDouble _))
}
Upvotes: 2