From b8785a52c6a23dc80cded60330537a6a4ebc73ff Mon Sep 17 00:00:00 2001 From: "Frank S. Thomas" Date: Sat, 15 Aug 2015 18:53:35 +0200 Subject: [PATCH] Replace internal.Wrapper with RefType. closes #48, #53 --- build.sbt | 3 +- notes/0.2.2.markdown | 8 ++ .../scala/eu/timepit/refined/RefType.scala | 76 +++++++++++++++++++ .../scala/eu/timepit/refined/implicits.scala | 14 ++-- .../eu/timepit/refined/internal/InferM.scala | 8 +- .../timepit/refined/internal/RefineAux.scala | 12 +-- .../timepit/refined/internal/RefineMAux.scala | 18 +++-- .../eu/timepit/refined/internal/Wrapper.scala | 57 -------------- .../scala/eu/timepit/refined/package.scala | 14 ++-- .../eu/timepit/refined/RefTypeSpec.scala | 26 +++++++ .../eu/timepit/refined/WrapperSpec.scala | 16 ---- 11 files changed, 147 insertions(+), 105 deletions(-) create mode 100644 shared/src/main/scala/eu/timepit/refined/RefType.scala delete mode 100644 shared/src/main/scala/eu/timepit/refined/internal/Wrapper.scala create mode 100644 shared/src/test/scala/eu/timepit/refined/RefTypeSpec.scala delete mode 100644 shared/src/test/scala/eu/timepit/refined/WrapperSpec.scala diff --git a/build.sbt b/build.sbt index 12984f0c1..8e669c828 100644 --- a/build.sbt +++ b/build.sbt @@ -170,9 +170,10 @@ lazy val miscSettings = Seq( import $rootPkg.char._ import $rootPkg.collection._ import $rootPkg.generic._ - import $rootPkg.InferenceRule.==> import $rootPkg.implicits._ + import $rootPkg.InferenceRule.==> import $rootPkg.numeric._ + import $rootPkg.RefType.ops._ import $rootPkg.string._ import shapeless.{ ::, HList, HNil } import shapeless.nat._ diff --git a/notes/0.2.2.markdown b/notes/0.2.2.markdown index 06568cb59..016cf7218 100644 --- a/notes/0.2.2.markdown +++ b/notes/0.2.2.markdown @@ -1,5 +1,13 @@ ### Changes +* Introduce the `RefType` type class which abstracts over `shapeless.@@` + and `Refined` and that allows 3rd-party types to be used as result type + for refinements (e.g. [`scalaz.@@`][scalaz.@@]). `RefType` replaces the + now removed `internal.Wrapper` type class. ([#48], [#53], [#54]) * Make the `Refined` constructor private ([#52]) +[#48]: https://github.com/fthomas/refined/issues/48 [#52]: https://github.com/fthomas/refined/issues/52 +[#53]: https://github.com/fthomas/refined/issues/53 +[#54]: https://github.com/fthomas/refined/pull/54 +[scalaz.@@]: https://github.com/scalaz/scalaz/blob/v7.1.3/core/src/main/scala/scalaz/package.scala#L103 diff --git a/shared/src/main/scala/eu/timepit/refined/RefType.scala b/shared/src/main/scala/eu/timepit/refined/RefType.scala new file mode 100644 index 000000000..b4380ea64 --- /dev/null +++ b/shared/src/main/scala/eu/timepit/refined/RefType.scala @@ -0,0 +1,76 @@ +package eu.timepit.refined + +import eu.timepit.refined.internal.{ RefineAux, RefineMAux } +import shapeless.tag.@@ + +import scala.reflect.macros.blackbox + +trait RefType[F[_, _]] extends Serializable { + + def unsafeWrap[T, P](t: T): F[T, P] + + def unwrap[T, P](tp: F[T, P]): T + + def unsafeWrapM[T: c.WeakTypeTag, P: c.WeakTypeTag](c: blackbox.Context)(t: c.Expr[T]): c.Expr[F[T, P]] + + def unsafeRewrapM[T: c.WeakTypeTag, A: c.WeakTypeTag, B: c.WeakTypeTag](c: blackbox.Context)(ta: c.Expr[F[T, A]]): c.Expr[F[T, B]] + + def refine[P]: RefineAux[F, P] = + new RefineAux[F, P](this) + + def refineM[P]: RefineMAux[F, P] = + new RefineMAux[F, P] + + def mapRefine[T, P, U](tp: F[T, P])(f: T => U)(implicit p: Predicate[P, U]): Either[String, F[U, P]] = + refine(f(unwrap(tp))) +} + +object RefType { + + def apply[F[_, _]](implicit rt: RefType[F]): RefType[F] = rt + + implicit val refinedRefType: RefType[Refined] = + new RefType[Refined] { + override def unsafeWrap[T, P](t: T): Refined[T, P] = + Refined.unsafeApply(t) + + override def unwrap[T, P](tp: Refined[T, P]): T = + tp.get + + override def unsafeWrapM[T: c.WeakTypeTag, P: c.WeakTypeTag](c: blackbox.Context)(t: c.Expr[T]): c.Expr[Refined[T, P]] = + c.universe.reify(Refined.unsafeApply[T, P](t.splice)) + + override def unsafeRewrapM[T: c.WeakTypeTag, A: c.WeakTypeTag, B: c.WeakTypeTag](c: blackbox.Context)(ta: c.Expr[Refined[T, A]]): c.Expr[Refined[T, B]] = + c.universe.reify(ta.splice.asInstanceOf[Refined[T, B]]) + } + + implicit val tagRefType: RefType[@@] = + new RefType[@@] { + override def unsafeWrap[T, P](t: T): T @@ P = + t.asInstanceOf[T @@ P] + + override def unwrap[T, P](tp: T @@ P): T = + tp + + override def unsafeWrapM[T: c.WeakTypeTag, P: c.WeakTypeTag](c: blackbox.Context)(t: c.Expr[T]): c.Expr[T @@ P] = + c.universe.reify(t.splice.asInstanceOf[T @@ P]) + + override def unsafeRewrapM[T: c.WeakTypeTag, A: c.WeakTypeTag, B: c.WeakTypeTag](c: blackbox.Context)(ta: c.Expr[T @@ A]): c.Expr[T @@ B] = + c.universe.reify(ta.splice.asInstanceOf[T @@ B]) + } + + final class RefTypeOps[F[_, _], T, P](tp: F[T, P])(implicit F: RefType[F]) { + + def unwrap: T = + F.unwrap(tp) + + def mapRefine[U](f: T => U)(implicit p: Predicate[P, U]): Either[String, F[U, P]] = + F.mapRefine(tp)(f) + } + + object ops { + + implicit def toRefTypeOps[F[_, _]: RefType, T, P](tp: F[T, P]): RefTypeOps[F, T, P] = + new RefTypeOps(tp) + } +} diff --git a/shared/src/main/scala/eu/timepit/refined/implicits.scala b/shared/src/main/scala/eu/timepit/refined/implicits.scala index 9fce10582..67a4d7c2a 100644 --- a/shared/src/main/scala/eu/timepit/refined/implicits.scala +++ b/shared/src/main/scala/eu/timepit/refined/implicits.scala @@ -11,10 +11,10 @@ object implicits { * `F[T, B]` if there is a valid inference rule `A ==> B`. If the * inference rule is invalid, compilation fails. */ - implicit def autoInfer[T, A, B, F[_, _]](ta: F[T, A])( + implicit def autoInfer[F[_, _], T, A, B](ta: F[T, A])( implicit - ir: A ==> B, w: Wrapper[F] - ): F[T, B] = macro InferM.macroImpl[T, A, B, F] + ir: A ==> B, rt: RefType[F] + ): F[T, B] = macro InferM.macroImpl[F, T, A, B] /** * Implicitly wraps (at compile-time) a value of type `T` in @@ -25,8 +25,8 @@ object implicits { */ implicit def autoRefineV[T, P](t: T)( implicit - p: Predicate[P, T], w: Wrapper[Refined] - ): Refined[T, P] = macro RefineMAux.macroImpl[P, T, Refined] + p: Predicate[P, T], rt: RefType[Refined] + ): Refined[T, P] = macro RefineMAux.macroImpl[Refined, T, P] /** * Implicitly tags (at compile-time) a value of type `T` with `P` if `t` @@ -37,6 +37,6 @@ object implicits { */ implicit def autoRefineT[T, P](t: T)( implicit - p: Predicate[P, T], w: Wrapper[@@] - ): T @@ P = macro RefineMAux.macroImpl[P, T, @@] + p: Predicate[P, T], rt: RefType[@@] + ): T @@ P = macro RefineMAux.macroImpl[@@, T, P] } diff --git a/shared/src/main/scala/eu/timepit/refined/internal/InferM.scala b/shared/src/main/scala/eu/timepit/refined/internal/InferM.scala index ae826fab5..d9712cbce 100644 --- a/shared/src/main/scala/eu/timepit/refined/internal/InferM.scala +++ b/shared/src/main/scala/eu/timepit/refined/internal/InferM.scala @@ -7,16 +7,16 @@ import scala.reflect.macros.blackbox object InferM { - def macroImpl[T: c.WeakTypeTag, A: c.WeakTypeTag, B: c.WeakTypeTag, F[_, _]](c: blackbox.Context)(ta: c.Expr[F[T, A]])( - ir: c.Expr[A ==> B], w: c.Expr[Wrapper[F]] + def macroImpl[F[_, _], T: c.WeakTypeTag, A: c.WeakTypeTag, B: c.WeakTypeTag](c: blackbox.Context)(ta: c.Expr[F[T, A]])( + ir: c.Expr[A ==> B], rt: c.Expr[RefType[F]] ): c.Expr[F[T, B]] = { import c.universe._ val inferenceRule = MacroUtils.eval(c)(ir) if (inferenceRule.isValid) { - val wrapper = MacroUtils.eval(c)(w) - wrapper.rewrapM(c)(ta) + val refType = MacroUtils.eval(c)(rt) + refType.unsafeRewrapM(c)(ta) } else c.abort(c.enclosingPosition, s"invalid inference: ${weakTypeOf[A]} ==> ${weakTypeOf[B]}") } diff --git a/shared/src/main/scala/eu/timepit/refined/internal/RefineAux.scala b/shared/src/main/scala/eu/timepit/refined/internal/RefineAux.scala index 489f771a1..55ccceed7 100644 --- a/shared/src/main/scala/eu/timepit/refined/internal/RefineAux.scala +++ b/shared/src/main/scala/eu/timepit/refined/internal/RefineAux.scala @@ -3,14 +3,16 @@ package internal /** * Helper class that allows the type `T` to be inferred from calls like - * `[[refineV]][P](t)`. See [[http://tpolecat.github.io/2015/07/30/infer.html]] - * for a detailed explanation of this trick. + * `[[RefType.refine]][P](t)`. + * + * See [[http://tpolecat.github.io/2015/07/30/infer.html]] for a detailed + * explanation of this trick. */ -final class RefineAux[P, F[_, _]] { +final class RefineAux[F[_, _], P](rt: RefType[F]) { - def apply[T](t: T)(implicit p: Predicate[P, T], w: Wrapper[F]): Either[String, F[T, P]] = + def apply[T](t: T)(implicit p: Predicate[P, T]): Either[String, F[T, P]] = p.validate(t) match { - case None => Right(w.wrap(t)) + case None => Right(rt.unsafeWrap(t)) case Some(s) => Left(s) } } diff --git a/shared/src/main/scala/eu/timepit/refined/internal/RefineMAux.scala b/shared/src/main/scala/eu/timepit/refined/internal/RefineMAux.scala index 7ca9ff9fc..25bf5c352 100644 --- a/shared/src/main/scala/eu/timepit/refined/internal/RefineMAux.scala +++ b/shared/src/main/scala/eu/timepit/refined/internal/RefineMAux.scala @@ -5,18 +5,20 @@ import scala.reflect.macros.blackbox /** * Helper class that allows the type `T` to be inferred from calls like - * `[[refineMV]][P](t)`. See [[http://tpolecat.github.io/2015/07/30/infer.html]] - * for a detailed explanation of this trick. + * `[[RefType.refineM]][P](t)`. + * + * See [[http://tpolecat.github.io/2015/07/30/infer.html]] for a detailed + * explanation of this trick. */ -final class RefineMAux[P, F[_, _]] { +final class RefineMAux[F[_, _], P] { - def apply[T](t: T)(implicit p: Predicate[P, T], w: Wrapper[F]): F[T, P] = macro RefineMAux.macroImpl[P, T, F] + def apply[T](t: T)(implicit p: Predicate[P, T], rt: RefType[F]): F[T, P] = macro RefineMAux.macroImpl[F, T, P] } object RefineMAux { - def macroImpl[P: c.WeakTypeTag, T: c.WeakTypeTag, F[_, _]](c: blackbox.Context)(t: c.Expr[T])( - p: c.Expr[Predicate[P, T]], w: c.Expr[Wrapper[F]] + def macroImpl[F[_, _], T: c.WeakTypeTag, P: c.WeakTypeTag](c: blackbox.Context)(t: c.Expr[T])( + p: c.Expr[Predicate[P, T]], rt: c.Expr[RefType[F]] ): c.Expr[F[T, P]] = { import c.universe._ @@ -33,8 +35,8 @@ object RefineMAux { predicate.validate(tValue) match { case None => - val wrapper = MacroUtils.eval(c)(w) - wrapper.wrapM(c)(t) + val refType = MacroUtils.eval(c)(rt) + refType.unsafeWrapM(c)(t) case Some(msg) => c.abort(c.enclosingPosition, msg) } } diff --git a/shared/src/main/scala/eu/timepit/refined/internal/Wrapper.scala b/shared/src/main/scala/eu/timepit/refined/internal/Wrapper.scala deleted file mode 100644 index 35c94b64b..000000000 --- a/shared/src/main/scala/eu/timepit/refined/internal/Wrapper.scala +++ /dev/null @@ -1,57 +0,0 @@ -package eu.timepit.refined -package internal - -import shapeless.tag.@@ - -import scala.reflect.macros.blackbox - -/** - * Type class for wrapping a value of type `T` into `F` together with a - * phantom type `P`. Instances must satisfy the following law: - * `forall t, unwrap(wrap(t)) == t`. - */ -trait Wrapper[F[_, _]] extends Serializable { - - def wrap[T, P](t: T): F[T, P] - - def unwrap[T, P](tp: F[T, P]): T - - def wrapM[T: c.WeakTypeTag, P: c.WeakTypeTag](c: blackbox.Context)(t: c.Expr[T]): c.Expr[F[T, P]] - - def rewrapM[T: c.WeakTypeTag, A: c.WeakTypeTag, B: c.WeakTypeTag](c: blackbox.Context)(ta: c.Expr[F[T, A]]): c.Expr[F[T, B]] -} - -object Wrapper { - - def apply[F[_, _]](implicit w: Wrapper[F]): Wrapper[F] = w - - implicit def refinedWrapper: Wrapper[Refined] = - new Wrapper[Refined] { - override def wrap[T, P](t: T): Refined[T, P] = - Refined.unsafeApply(t) - - override def unwrap[T, P](tp: Refined[T, P]): T = - tp.get - - override def wrapM[T: c.WeakTypeTag, P: c.WeakTypeTag](c: blackbox.Context)(t: c.Expr[T]): c.Expr[Refined[T, P]] = - c.universe.reify(Refined.unsafeApply[T, P](t.splice)) - - override def rewrapM[T: c.WeakTypeTag, A: c.WeakTypeTag, B: c.WeakTypeTag](c: blackbox.Context)(ta: c.Expr[Refined[T, A]]): c.Expr[Refined[T, B]] = - c.universe.reify(ta.splice.asInstanceOf[Refined[T, B]]) - } - - implicit def tagWrapper: Wrapper[@@] = - new Wrapper[@@] { - override def wrap[T, P](t: T): T @@ P = - t.asInstanceOf[T @@ P] - - override def unwrap[T, P](tp: T @@ P): T = - tp - - override def wrapM[T: c.WeakTypeTag, P: c.WeakTypeTag](c: blackbox.Context)(t: c.Expr[T]): c.Expr[T @@ P] = - c.universe.reify(t.splice.asInstanceOf[T @@ P]) - - override def rewrapM[T: c.WeakTypeTag, A: c.WeakTypeTag, B: c.WeakTypeTag](c: blackbox.Context)(ta: c.Expr[T @@ A]): c.Expr[T @@ B] = - c.universe.reify(ta.splice.asInstanceOf[T @@ B]) - } -} diff --git a/shared/src/main/scala/eu/timepit/refined/package.scala b/shared/src/main/scala/eu/timepit/refined/package.scala index 1bfd8ca0d..649ba36a3 100644 --- a/shared/src/main/scala/eu/timepit/refined/package.scala +++ b/shared/src/main/scala/eu/timepit/refined/package.scala @@ -26,7 +26,7 @@ package object refined { * an `apply` method on it, allowing `refineV` to be called like in the * given example. */ - def refineV[P]: RefineAux[P, Refined] = new RefineAux[P, Refined] + def refineV[P]: RefineAux[Refined, P] = RefType[Refined].refine[P] /** * Returns `t` with type `T @@ P` on the right if it satisfies the @@ -47,7 +47,7 @@ package object refined { * `apply` method on it, allowing `refineT` to be called like in the given * example. */ - def refineT[P]: RefineAux[P, @@] = new RefineAux[P, @@] + def refineT[P]: RefineAux[@@, P] = RefType[@@].refine[P] /** * Macro that returns `t` wrapped in `[[Refined]][T, P]` if it satisfies @@ -67,7 +67,7 @@ package object refined { * has an `apply` method on it, allowing `refineMV` to be called like in * the given example. */ - def refineMV[P]: RefineMAux[P, Refined] = new RefineMAux[P, Refined] + def refineMV[P]: RefineMAux[Refined, P] = RefType[Refined].refineM[P] /** * Macro that returns `t` with type `T @@ P` if it satisfies the predicate @@ -88,14 +88,14 @@ package object refined { * `apply` method on it, allowing `refineMT` to be called like in the given * example. */ - def refineMT[P]: RefineMAux[P, @@] = new RefineMAux[P, @@] + def refineMT[P]: RefineMAux[@@, P] = RefType[@@].refineM[P] @deprecated("refine is deprecated in favor of refineT", "0.2.0") - def refine[P]: RefineAux[P, @@] = new RefineAux[P, @@] + def refine[P]: RefineAux[@@, P] = refineT[P] @deprecated("refineLit is deprecated in favor of refineMT", "0.2.0") - def refineLit[P]: RefineMAux[P, @@] = new RefineMAux[P, @@] + def refineLit[P]: RefineMAux[@@, P] = refineMT[P] @deprecated("refineM is deprecated in favor of refineMV", "0.2.0") - def refineM[P]: RefineMAux[P, Refined] = new RefineMAux[P, Refined] + def refineM[P]: RefineMAux[Refined, P] = refineMV[P] } diff --git a/shared/src/test/scala/eu/timepit/refined/RefTypeSpec.scala b/shared/src/test/scala/eu/timepit/refined/RefTypeSpec.scala new file mode 100644 index 000000000..663ab06e7 --- /dev/null +++ b/shared/src/test/scala/eu/timepit/refined/RefTypeSpec.scala @@ -0,0 +1,26 @@ +package eu.timepit.refined + +import eu.timepit.refined.RefType.ops._ +import eu.timepit.refined.numeric.Positive +import org.scalacheck.Prop._ +import org.scalacheck.Properties +import shapeless.tag.@@ + +abstract class RefTypeProperties[F[_, _]](name: String)(implicit rt: RefType[F]) extends Properties(s"RefType[$name]") { + + property("unsafeWrap.unwrap ~= id") = forAll { (s: String) => + rt.unsafeWrap(s).unwrap == s + } + + property("mapRefine.success") = secure { + rt.refine[Positive](5).right.flatMap(_.mapRefine(_.toDouble)).isRight + } + + property("mapRefine.failure") = secure { + rt.refine[Positive](5).right.flatMap(_.mapRefine(_ - 10)).isLeft + } +} + +class RefTypeSpecRefined extends RefTypeProperties[Refined]("Refined") + +class RefTypeSpecTag extends RefTypeProperties[@@]("@@") diff --git a/shared/src/test/scala/eu/timepit/refined/WrapperSpec.scala b/shared/src/test/scala/eu/timepit/refined/WrapperSpec.scala deleted file mode 100644 index c5a74ae71..000000000 --- a/shared/src/test/scala/eu/timepit/refined/WrapperSpec.scala +++ /dev/null @@ -1,16 +0,0 @@ -package eu.timepit.refined - -import eu.timepit.refined.internal.Wrapper -import org.scalacheck.Prop.forAll -import org.scalacheck.Properties -import shapeless.tag.@@ - -class WrapperSpec extends Properties("Wrapper") { - - def wrapperLaw[F[_, _]](implicit w: Wrapper[F]) = forAll { (s: String) => - w.unwrap(w.wrap(s)) == s - } - - property("Refined") = wrapperLaw[Refined] - property("@@") = wrapperLaw[@@] -}