Skip to content

Instantly share code, notes, and snippets.

@Fristi
Last active April 10, 2017 10:18
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Fristi/f267f35004ca3ae9ffcdbf5267707f82 to your computer and use it in GitHub Desktop.
Save Fristi/f267f35004ca3ae9ffcdbf5267707f82 to your computer and use it in GitHub Desktop.
Mapping type class
import shapeless._
import shapeless.labelled.{ FieldType, field }
import scala.reflect.ClassTag
import scala.util.control.NonFatal
import scalaz.Scalaz._
import scalaz.{ DLeft, DRight, Disjunction }
/**
* Mapping type class, which will convert a type `From` to the specified type `To`.
* This might cause failures which is modeled with `Disjunction` (Either, but right biased).
*
* @tparam From The type to convert from
* @tparam To The type to convert to
*/
trait Mapping[From, To] {
def apply(from: From): String Disjunction To
}
/**
* Lower priority implicits
*/
trait LowerPrio {
implicit def identityType[From, To](implicit ev: From =:= To): Mapping[From, To] = new Mapping[From, To] {
override def apply(from: From) = DRight(from)
}
}
object Mapping extends LowerPrio {
implicit def javaEnum[From <: java.lang.Enum[From], To <: java.lang.Enum[To]: ClassTag]: Mapping[From, To] =
Mapping.fromCatchableNonFatal(
x => java.lang.Enum.valueOf(implicitly[ClassTag[To]].runtimeClass.asInstanceOf[Class[To]], x.name())
)
implicit def optional[From, To](implicit M: Mapping[From, To]): Mapping[Option[From], Option[To]] =
new Mapping[Option[From], Option[To]] {
override def apply(from: Option[From]): String Disjunction Option[To] = from.map(M.apply) match {
case Some(DLeft(err)) => DLeft(err)
case Some(DRight(v)) => DRight(Some(v))
case None => DRight(None)
}
}
implicit def seq[From, To](implicit M: Mapping[From, To]): Mapping[Seq[From], Seq[To]] =
new Mapping[Seq[From], Seq[To]] {
override def apply(from: Seq[From]): Disjunction[String, Seq[To]] =
from.toList.traverse[({ type L[A] = Disjunction[String, A] })#L, To](M.apply)
}
implicit def set[From, To](implicit M: Mapping[From, To]): Mapping[Set[From], Set[To]] =
new Mapping[Set[From], Set[To]] {
override def apply(from: Set[From]): Disjunction[String, Set[To]] =
from.toList.traverse[({ type L[A] = Disjunction[String, A] })#L, To](M.apply).map(_.toSet)
}
implicit def list[From, To](implicit M: Mapping[From, To]): Mapping[List[From], List[To]] =
new Mapping[List[From], List[To]] {
override def apply(from: List[From]): Disjunction[String, List[To]] =
from.traverse[({ type L[A] = Disjunction[String, A] })#L, To](M.apply)
}
implicit val hnil: Mapping[HNil, HNil] = new Mapping[HNil, HNil] {
override def apply(from: HNil) = DRight(from)
}
implicit def hcons[K <: Symbol, HFrom, HTo, TTo <: HList, TFrom <: HList](
implicit key: Witness.Aux[K],
sv: Lazy[Mapping[HFrom, HTo]],
tail: Mapping[TFrom, TTo]
): Mapping[FieldType[K, HFrom] :: TFrom, FieldType[K, HTo] :: TTo] =
new Mapping[FieldType[K, HFrom] :: TFrom, FieldType[K, HTo] :: TTo] {
override def apply(from: FieldType[K, HFrom] :: TFrom) =
for {
h <- sv.value(from.head)
t <- tail(from.tail)
} yield field[K](h) :: t
}
implicit val cnil: Mapping[CNil, CNil] = new Mapping[CNil, CNil] {
override def apply(from: CNil) = DLeft("Should be impossible")
}
implicit def ccons[KA <: Symbol, KB <: Symbol, HFrom, HTo, TFrom <: Coproduct, TTo <: Coproduct](
implicit keyA: Witness.Aux[KA],
keyB: Witness.Aux[KB],
sv: Lazy[Mapping[HFrom, HTo]],
st: Mapping[TFrom, TTo]
): Mapping[FieldType[KA, HFrom] :+: TFrom, FieldType[KB, HTo] :+: TTo] =
new Mapping[FieldType[KA, HFrom] :+: TFrom, FieldType[KB, HTo] :+: TTo] {
override def apply(from: FieldType[KA, HFrom] :+: TFrom): Disjunction[String, :+:[FieldType[KB, HTo], TTo]] =
if (keyA.value.name == keyB.value.name) {
from match {
case Inl(v) => sv.value(v).map(x => Inl(field[KB](x)))
case Inr(v) => st(v).map(x => Inr(x))
}
} else {
DLeft(s"Could not convert ${keyA.value.name} to ${keyB.value.name}")
}
}
implicit def generic[ClazzFrom, ClazzTo, ReprTo, ReprFrom](
implicit F: LabelledGeneric.Aux[ClazzFrom, ReprFrom],
G: LabelledGeneric.Aux[ClazzTo, ReprTo],
repr: Lazy[Mapping[ReprFrom, ReprTo]]
): Mapping[ClazzFrom, ClazzTo] = new Mapping[ClazzFrom, ClazzTo] {
override def apply(from: ClazzFrom) = repr.value.apply(F.to(from)).map(G.from)
}
/**
* Summon a typeclass instance Mapping for the given types
* @param M The implicitly summoned instance (you shouldn't pass this in)
* @tparam From The type to convert from
* @tparam To The type to convert to
* @return A mapping from type `From` to the given type `To`
*/
def apply[From, To](implicit M: Mapping[From, To]): Mapping[From, To] = M
/**
* Creates a Mapping between `From` and `To`. This mapping might throw exceptions in the process.
* If the function does not throw exceptions, you could use `fromPureFunction`
* @param f The conversion function (with side-effects)
* @tparam From The type to convert from
* @tparam To The type to convert to
* @return A mapping from type `From` to the given type `To`
*/
def fromCatchableNonFatal[From, To](f: From => To): Mapping[From, To] = new Mapping[From, To] {
override def apply(from: From) =
try { DRight(f(from)) } catch {
case NonFatal(ex) => DLeft(ex.getMessage)
}
}
/**
* Creates a Mapping between `From` and `To`. This mapping does not throw any exception or perform side-effects
*
* @param f The conversion function
* @tparam From The type to convert from
* @tparam To The type to convert to
* @return A mapping from type `From` to the given type `To`
*/
def fromPureFunction[From, To](f: From => To): Mapping[From, To] = new Mapping[From, To] {
override def apply(from: From) = DRight(f(from))
}
/**
* Creates a Mapping between `From` and `To`. This mapping might fail due a reason (DLeft). If it succeeds, use the
* DRight
*
* @param f The conversion function
* @tparam From The type to convert from
* @tparam To The type to convert to
* @return A mapping from type `From` to the given type `To`
*/
def fromEither[From, To](f: From => String Disjunction To): Mapping[From, To] = new Mapping[From, To] {
override def apply(from: From) = f(from)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment