Reputation: 81
Let's say I have
case class User(id: Long, name: String, age: Long, email: Option[String])
Is there a way I can create let's say
type UpdateUser = ???.???[User] // update user has same fields as User except all are optional
that I can then use as
UpdateUser(name = Some("foo"), email = Some("[email protected]"))
so basically mapping types
Long :: String :: Long :: Option[String]
->
Option[Long] :: Option[String] :: Option[Long] :: Option[String]
So once again, question is, is there way (i.e. using shapeless) to create such derived case class with such fields (without writing macro to do that.)
Case clases are just HLists. Can we create a new type based on some type of HList?
Upvotes: 4
Views: 672
Reputation: 17431
You can't generate a de novo concrete type, except perhaps with a macro annotation. What you can do is use a typeclass to derive a generic type that has the transformed shape that you want.
import shapeless._, labelled._
trait RecordAsOption[L] {
type Out <: HList
}
object RecordAsOption {
def apply[L](implicit recordAsOption: RecordAsOption[L])
: Aux[L, recordAsOption.Out] =
recordAsOption
type Aux[L, Out0 <: HList] = RecordAsOption[L] { type Out = Out0 }
implicit def hnilRecordAsOption[L <: HNil]: Aux[L, HNil] =
new RecordAsOption[L] {
type Out = HNil
def apply(l: L) = HNil
}
implicit def hconsRecordAsOption[K, V, T <: HList](
implicit tail: RecordAsOption[T])
: Aux[FieldType[K, V] :: T, FieldType[K, Option[V]] :: tail.Out] =
new RecordAsOption[FieldType[K, V] :: T] {
type Out = FieldType[K, Option[V]] :: tail.Out
}
implicit def genericRecordAsOption[T, R](
implicit lg: LabelledGeneric.Aux[T, R], roa: RecordAsOption[T])
: Aux[T, roa.Out] =
new RecordAsOption[T] {
type Out = roa.Out
}
}
case class User(id: Long, name: String, age: Long, email: Option[String])
val genericUserUpdate = RecordAsOption[User]
type GenericUserUpdate = genericUserUpdate.Out
You probably want to add some functionality to the RecordAsOption
typeclass (given the name, probably a lens-like method that applies the delta represented by the options to a value of type L
), since as it stands the type is kind of useless. The record representation isn't quite as nice as a case class in some respects, but it can interoperate nicely with other shapeless-based things (e.g. it would be easy to use similar techniques to spray-json-shapeless to deserialize JSON to a GenericUserUpdate
)
Upvotes: 1