Reputation: 16318
I have basic type pool defined like that:
sealed trait Section
final case class Header(...) extends Section
final case class Customer(...) extends Section
final case class Supplier(...) extends Section
final case class Tech(...) extends Section
I'd like to present some case classes composed of types from this pool like this:
final case class ContractViewPartners(customer: Customer, supplier: Supplier)
final case class ContractView(header: Header, partners: ContractViewPartners, tech: Tech)
As they would be heavily used in feature-generators implemented via transfoming to HList
s using method described here, I'd like to ensure that each field of presented type is one of
Section
subtypeHList
of Section
subtypesHList
of Section
subtypesI've defined simple compile-time checker for this condition:
object traverseView extends Poly1 {
implicit def caseSection[S <: Section] = at[S](_ => ())
implicit def caseSectionList[L <: HList]
(implicit evt: ToTraversable.Aux[L, List, Section]) = at[L](_ => ())
implicit def caseRecord[R, L <: HList]
(implicit lgen: LabelledGeneric.Aux[R, L],
trav: ToTraversable.Aux[L, List, Section]) = at[R](_ => ())
}
private def contractViewIsMultiSection(v: ContractView) = {
val gen = LabelledGeneric[ContractView].to(v)
gen map traverseView
}
But it fails with (package names removed)
could not find implicit value for parameter mapper: Mapper[traverseView.type,::[Header with KeyTag[Symbol with Tagged[String("header")],Header],::[ContractViewPartners with KeyTag[Symbol with Tagged[String("partners")],ContractViewPartners],::[Tech with KeyTag[Symbol with Tagged[String("tech")],Tech],HNil]]]]
If i remove partners
section from ContractView
it's working and if i try to resolve implicits
on ContractViewPartners
they will be found too.
Again while writing question i've found solution with adding .values
like that
private def contractViewIsMultiSection(v: ContractView) = {
val gen = LabelledGeneric[ContractView].to(v)
.values //!!!
gen map traverseView
}
Could it be that type with KeyTag[...]
is not working properly as source for LabelledGeneric
transformation?
Upvotes: 2
Views: 682
Reputation: 139048
The problem is that Case
is invariant, so the fact that you have a Case
instance for ContractViewPartners
doesn't mean that you have a case instance for ContractViewPartners
with a type-level label (which is only a subtype of ContractViewPartners
). You can fix this pretty straightforwardly by generating instances for e.g. FieldType[K, ContractViewPartners]
(for some arbitrary K
):
sealed trait Section
final case class Header(s: String) extends Section
final case class Customer(s: String) extends Section
final case class Supplier(s: String) extends Section
final case class Tech(s: String) extends Section
final case class ContractViewPartners(customer: Customer, supplier: Supplier)
final case class ContractView(header: Header, partners: ContractViewPartners, tech: Tech)
import shapeless._, labelled.FieldType, ops.hlist.ToList
object traverseView extends Poly1 {
implicit def caseSection[S <: Section] = at[S](_ => ())
implicit def caseSectionList[K, L <: HList](implicit
lub: ToList[L, Section]
) = at[FieldType[K, L]](_ => ())
implicit def caseRecord[K, C, L <: HList](implicit
gen: Generic.Aux[C, L],
lub: ToList[L, Section]
) = at[FieldType[K, C]](_ => ())
}
private def contractViewIsMultiSection(v: ContractView) = {
val gen = LabelledGeneric[ContractView].to(v)
gen map traverseView
}
You could also just use Generic[ContractView]
in contractViewIsMultiSection
if you don't care about the labels.
I would probably suggest not using Poly1
for this kind of thing, though. If you just want evidence that the types are right, you could do that a little more cleanly with a custom type class.
Upvotes: 4