Skip to content

Instantly share code, notes, and snippets.

@mikla
Last active May 25, 2017 07:23
Show Gist options
  • Select an option

  • Save mikla/dd0b277146606e62c19cb3d210e7b07e to your computer and use it in GitHub Desktop.

Select an option

Save mikla/dd0b277146606e62c19cb3d210e7b07e to your computer and use it in GitHub Desktop.
Type class for extracting field with UserId type from case class.
import shapeless.{:+:, ::, CNil, Coproduct, Generic, HList, HNil, Inl, Inr}
import simulacrum.typeclass
import language.implicitConversions
case class UserId(value: UUID) extends AnyVal
/**
* Type class for extracting field with UserId type from case class.
*
* @tparam T
*/
@typeclass trait AffectsUser[T] {
def userId(t: T): Option[UserId]
}
object AffectsUser extends AffectedUserLowerPriorityImplicits {
def instance[A](f: A => Option[UserId]): AffectsUser[A] = new AffectsUser[A] {
override def userId(t: A): Option[UserId] = f(t)
}
implicit val hNil = instance[HNil](_ => None)
implicit def hConsUserId[T <: HList](implicit A: AffectsUser[T]) =
instance[UserId :: T](t => Some(t.head))
implicit def genAffectsUser[T, Repr](implicit G: Generic.Aux[T, Repr], A: AffectsUser[Repr]) =
instance[T](t => A.userId(G.to(t)))
implicit def affectedUserCNil: AffectsUser[CNil] = new AffectsUser[CNil] {
override def userId(t: CNil): Option[UserId] = t.impossible
}
implicit def affectedUserCCons[H, T <: Coproduct](
implicit sh: AffectsUser[H],
st: AffectsUser[T]
): AffectsUser[H :+: T] = instance[H :+: T] {
case Inl(l) => sh.userId(l)
case Inr(r) => st.userId(r)
}
}
trait AffectedUserLowerPriorityImplicits {
import AffectsUser.instance
implicit def hCons[H, T <: HList](implicit A: AffectsUser[T]) =
instance[H :: T](t => A.userId(t.tail))
}
class AffectsUserCoproductSpec extends FunSuite with Matchers {
test("AffectsUser should support sealed trait type as a type parameter") {
val userId = UserId.generate
val event = EventWithUserId("event-name", userId)
def eventUserId[T <: Event](event: T)(implicit A: AffectsUser[T]) = A.userId(event)
eventUserId(event) should be (Some(userId))
}
}
class AffectsUserSpec extends FunSuite with Matchers {
test("AffectsUser should return UserId field from case class") {
val userId = UserId.generate
val event = EventWithUserId("event-name", userId)
implicitly[AffectsUser[EventWithUserId]].userId(event) should be (Some(userId))
}
test("AffectsUser should return None if UserId field is not present in case class") {
val event = EventWithoutUserId("event-name")
implicitly[AffectsUser[EventWithoutUserId]].userId(event) should be (None)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment