fromscalatohaskell
fromscalatohaskell

Reputation: 81

Derive new type from case class using Shapeless

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

Answers (1)

lmm
lmm

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

Related Questions