Reputation: 49
I'm learning my way through Shapeless, and I have a specific behavior I'm looking to implement to allow our code to expand out optional case classes identically whether they're present or not. Basically:
Given an Option[N]
where N <: Product
, I'd like to produce an HList
with the same types produced by Generic[N].to
, but with each element wrapped in an Option
if it isn't already. e.g.:
case class Foo(a: String, b: Option[Long], c: Option[String])
optionalize[Foo](Some(Foo("abc", Some(123L), None)))
// => Some("abc") :: Some(123L) :: None :: HNil
optionalize[Foo](None)
// => None :: None :: None :: HNil
// where optionalize.Out = Option[String] :: Option[Long] :: Option[String] :: HNil
My ultimate goal is the ability to flatten out nested case classes, so I'd like to introduce this as a rule such that Shapeless can do this automatically via type inference. The biggest obstacle in my mind is understanding how to write the None
case. Currently, my code looks something like this:
trait LowPriEnsureOptional extends Poly1 {
implicit def somethingCase[In]: Case.Aux[In, Option[In]] = at(thing => Some(thing))
implicit val hnilCase: Case.Aux[HNil, HNil] = at(identity)
}
object EnsureOptional extends LowPriEnsureOptional {
implicit def optionCase[In <: Option[_]]: Case.Aux[In, In] = at(identity)
}
object OptionizeHlist {
def optionizeCaseClass[
CC <: Product,
R <: HList
](occ: Option[CC])(
implicit gen: Generic[CC] { type Repr = R },
optionalize: Mapper[EnsureOptional.type, R]
): optionalize.Out =
occ match {
case Some(cc) => optionalize.apply(gen.to(cc))
case None => ???
}
}
To write out the None
case, I need some way to, given a case class, get its generic repr, run it through OptionizeHlist
, and generate an instance of it consisting entirely of None
s, but I have no idea where to start on this.
Upvotes: 1
Views: 108
Reputation: 29193
Notice that, if you are passed a None
, type erasure makes it so that you cannot know how many None
s to put into the output list. Therefore, you need another implicit argument to survive that information to runtime.
final class AllNoneable[H <: HList] private (val allNone: H) extends AnyVal
object AllNoneable {
implicit val allNoneableHNil = new AllNoneable[HNil](HNil)
implicit def allNoneableCons[H >: None.type, T <: HList](implicit t: AllNoneable[T])
= new AllNoneable[H :: T](None :: t.allNone)
}
Also, hnilCase
does nothing.
trait LowPriEnsureOptional extends Poly1 {
implicit def somethingCase[In]: Case.Aux[In, Option[In]] = at(Some(_))
}
object EnsureOptional extends LowPriEnsureOptional {
implicit def optionCase[In <: Option[_]]: Case.Aux[In, In] = at(identity)
}
def optionalizeCase
[C, Rep <: HList, Opt <: HList](c: Option[C])
(implicit
gen: Generic.Aux[C, Rep], opt: Mapper.Aux[EnsureOptional.type, Rep, Opt],
nones: AllNoneable[Opt]): Opt
= c match {
case Some(c) => opt.apply(gen.to(c))
case None => nones.allNone
}
Upvotes: 1