Last active
April 10, 2017 10:18
-
-
Save Fristi/f267f35004ca3ae9ffcdbf5267707f82 to your computer and use it in GitHub Desktop.
Mapping type class
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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