From c6750c3e9da6a1622ec5d0dcc0bcca3337565e33 Mon Sep 17 00:00:00 2001 From: Stew O'Connor Date: Tue, 2 Feb 2016 00:13:49 -0800 Subject: [PATCH] Adds a new LiftTrans typeclass This typeclass adds a `liftT` function which is similar to the liftM function on the scalaz MonadTrans typeclass, however this one takes into account that you might not need the power of a monad in order to be able to lift into a transformer, for example, we are able to Lift any M[A] into a Kleisli[M, B, A] regardless of whether or not M is a Monad, Functor, or any other such thing. Thish would be useful in cases where you are constructing an applicative computation using values for which you don't have a monad. --- core/src/main/scala/cats/TransLift.scala | 13 ++++++ core/src/main/scala/cats/data/Kleisli.scala | 5 +++ core/src/main/scala/cats/data/OptionT.scala | 5 +++ core/src/main/scala/cats/data/StateT.scala | 6 +++ .../src/main/scala/cats/data/StreamingT.scala | 5 +++ core/src/main/scala/cats/data/WriterT.scala | 6 +++ core/src/main/scala/cats/data/XorT.scala | 7 ++++ core/src/main/scala/cats/syntax/all.scala | 1 + core/src/main/scala/cats/syntax/package.scala | 1 + .../main/scala/cats/syntax/transLift.scala | 10 +++++ .../scala/cats/tests/TransLiftTests.scala | 42 +++++++++++++++++++ 11 files changed, 101 insertions(+) create mode 100644 core/src/main/scala/cats/TransLift.scala create mode 100644 core/src/main/scala/cats/syntax/transLift.scala create mode 100644 tests/src/test/scala/cats/tests/TransLiftTests.scala diff --git a/core/src/main/scala/cats/TransLift.scala b/core/src/main/scala/cats/TransLift.scala new file mode 100644 index 0000000000..82c490b558 --- /dev/null +++ b/core/src/main/scala/cats/TransLift.scala @@ -0,0 +1,13 @@ +package cats + + +/** + * A typeclass which abstracts over the ability to lift an M[A] into a + * MonadTransformer + */ +trait TransLift[MT[_[_], _], M[_]] { + /** + * Lift a value of type M[A] into a monad transformer MT[M, A] + */ + def liftT[A](ma: M[A]): MT[M,A] +} diff --git a/core/src/main/scala/cats/data/Kleisli.scala b/core/src/main/scala/cats/data/Kleisli.scala index 2226829fa8..bbe8a9e246 100644 --- a/core/src/main/scala/cats/data/Kleisli.scala +++ b/core/src/main/scala/cats/data/Kleisli.scala @@ -114,6 +114,11 @@ private[data] sealed abstract class KleisliInstances extends KleisliInstances0 { override def contramap[A, B](fa: Kleisli[F, A, C])(f: (B) => A): Kleisli[F, B, C] = fa.local(f) } + + implicit def kleisliTransLift[M[_], A]: TransLift[({type λ[α[_], β] = Kleisli[α, A, β]})#λ, M] = + new TransLift[({type λ[α[_], β] = Kleisli[α, A, β]})#λ, M] { + def liftT[B](ma: M[B]): Kleisli[M, A, B] = Kleisli[M, A, B](a => ma) + } } private[data] sealed abstract class KleisliInstances0 extends KleisliInstances1 { diff --git a/core/src/main/scala/cats/data/OptionT.scala b/core/src/main/scala/cats/data/OptionT.scala index c91140d75e..93a904bc5d 100644 --- a/core/src/main/scala/cats/data/OptionT.scala +++ b/core/src/main/scala/cats/data/OptionT.scala @@ -128,6 +128,11 @@ private[data] sealed trait OptionTInstances1 { override def map[A, B](fa: OptionT[F, A])(f: A => B): OptionT[F, B] = fa.map(f) } + + implicit def optionTTransLift[M[_]: Functor]: TransLift[OptionT, M] = + new TransLift[OptionT, M] { + def liftT[A](ma: M[A]): OptionT[M, A] = OptionT.liftF(ma) + } } private[data] sealed trait OptionTInstances extends OptionTInstances1 { diff --git a/core/src/main/scala/cats/data/StateT.scala b/core/src/main/scala/cats/data/StateT.scala index 5bff9826ca..38a5858ca7 100644 --- a/core/src/main/scala/cats/data/StateT.scala +++ b/core/src/main/scala/cats/data/StateT.scala @@ -137,6 +137,12 @@ private[data] sealed abstract class StateTInstances { override def map[A, B](fa: StateT[F, S, A])(f: A => B): StateT[F, S, B] = fa.map(f) } + + implicit def stateTLift[M[_], S](implicit M: Applicative[M]): TransLift[({type λ[α[_], β] = StateT[α, S, β]})#λ, M] = + new TransLift[({type λ[α[_], β] = StateT[α, S, β]})#λ, M] { + def liftT[A](ma: M[A]): StateT[M, S, A] = StateT(s => M.map(ma)(s -> _)) + } + } // To workaround SI-7139 `object State` needs to be defined inside the package object diff --git a/core/src/main/scala/cats/data/StreamingT.scala b/core/src/main/scala/cats/data/StreamingT.scala index 54f46eb1e2..77bacac8f3 100644 --- a/core/src/main/scala/cats/data/StreamingT.scala +++ b/core/src/main/scala/cats/data/StreamingT.scala @@ -454,6 +454,11 @@ private[data] sealed trait StreamingTInstances extends StreamingTInstances1 { def compare(x: StreamingT[F, A], y: StreamingT[F, A]): Int = x.toList compare y.toList } + + implicit def streamingTTransLift[M[_]: Applicative]: TransLift[StreamingT, M] = + new TransLift[StreamingT, M] { + def liftT[A](ma: M[A]): StreamingT[M, A] = StreamingT.single(ma) + } } private[data] sealed trait StreamingTInstances1 extends StreamingTInstances2 { diff --git a/core/src/main/scala/cats/data/WriterT.scala b/core/src/main/scala/cats/data/WriterT.scala index 5afd465a8c..515d63f5a5 100644 --- a/core/src/main/scala/cats/data/WriterT.scala +++ b/core/src/main/scala/cats/data/WriterT.scala @@ -62,6 +62,12 @@ private[data] sealed abstract class WriterTInstances extends WriterTInstances0 { def bimap[A, B, C, D](fab: WriterT[F, A, B])(f: A => C, g: B => D): WriterT[F, C, D] = fab.bimap(f, g) } + + implicit def writerTTransLift[M[_], W](implicit M: Functor[M], W: Monoid[W]): TransLift[({type λ[α[_], β] = WriterT[α,W,β]})#λ, M] = + new TransLift[({type λ[α[_], β] = WriterT[α,W,β]})#λ, M] { + def liftT[A](ma: M[A]): WriterT[M, W, A] = + WriterT(M.map(ma)((W.empty, _))) + } } private[data] sealed abstract class WriterTInstances0 extends WriterTInstances1 { diff --git a/core/src/main/scala/cats/data/XorT.scala b/core/src/main/scala/cats/data/XorT.scala index bf31956370..d7ff05bf80 100644 --- a/core/src/main/scala/cats/data/XorT.scala +++ b/core/src/main/scala/cats/data/XorT.scala @@ -193,6 +193,13 @@ private[data] abstract class XorTInstances extends XorTInstances1 { new XorTTraverse[F, L] { val F0: Traverse[F] = F } + + implicit def xortTransLift[M[_],E](implicit M: Functor[M]): TransLift[({type λ[α[_], β] = XorT[α,E,β]})#λ, M] = + new TransLift[({type λ[α[_], β] = XorT[α,E,β]})#λ, M] { + def liftT[A](ma: M[A]): XorT[M,E,A] = + XorT(M.map(ma)(Xor.right)) + } + } private[data] abstract class XorTInstances1 extends XorTInstances2 { diff --git a/core/src/main/scala/cats/syntax/all.scala b/core/src/main/scala/cats/syntax/all.scala index 269b210859..3a3edaffb2 100644 --- a/core/src/main/scala/cats/syntax/all.scala +++ b/core/src/main/scala/cats/syntax/all.scala @@ -30,6 +30,7 @@ trait AllSyntax with SplitSyntax with StreamingSyntax with StrongSyntax + with TransLiftSyntax with TraverseSyntax with XorSyntax with ValidatedSyntax diff --git a/core/src/main/scala/cats/syntax/package.scala b/core/src/main/scala/cats/syntax/package.scala index b592021184..d7a229fffd 100644 --- a/core/src/main/scala/cats/syntax/package.scala +++ b/core/src/main/scala/cats/syntax/package.scala @@ -29,6 +29,7 @@ package object syntax { object split extends SplitSyntax object streaming extends StreamingSyntax object strong extends StrongSyntax + object transLift extends TransLiftSyntax object traverse extends TraverseSyntax object xor extends XorSyntax object validated extends ValidatedSyntax diff --git a/core/src/main/scala/cats/syntax/transLift.scala b/core/src/main/scala/cats/syntax/transLift.scala new file mode 100644 index 0000000000..c5fc39ef8d --- /dev/null +++ b/core/src/main/scala/cats/syntax/transLift.scala @@ -0,0 +1,10 @@ +package cats +package syntax + +trait TransLiftSyntax { + implicit def transLiftSyntax[M[_], A](ma: M[A]): TransLiftOps[M, A] = new TransLiftOps(ma) +} + +final class TransLiftOps[M[_], A](val ma: M[A]) extends AnyVal { + def liftT[MT[_[_],_]](implicit TL: TransLift[MT, M]): MT[M,A] = TL.liftT(ma) +} diff --git a/tests/src/test/scala/cats/tests/TransLiftTests.scala b/tests/src/test/scala/cats/tests/TransLiftTests.scala new file mode 100644 index 0000000000..6b17eea83b --- /dev/null +++ b/tests/src/test/scala/cats/tests/TransLiftTests.scala @@ -0,0 +1,42 @@ +package cats +package tests + +import data.{OptionT,XorT,WriterT,StreamingT, Kleisli, StateT} + +class TransLiftTests extends CatsSuite { + + case class NoTypeclass[A](a: A) + + case class JustFunctor[A](a: A) + + implicit val jfFunctor: Functor[JustFunctor] = new Functor[JustFunctor] { + override def map[A,B](fa: JustFunctor[A])(f: A => B): JustFunctor[B] = JustFunctor(f(fa.a)) + } + + case class JustAp[A](a: A) + + implicit val jfApp: Applicative[JustAp] = new Applicative[JustAp] { + override def pure[A](a: A): JustAp[A] = JustAp(a) + override def ap[A, B](ff: JustAp[A => B])(fa: JustAp[A]): JustAp[B] = JustAp(ff.a(fa.a)) + override def product[A, B](fa: JustAp[A],fb: JustAp[B]): JustAp[(A, B)] = JustAp(fa.a -> fb.a) + override def map[A, B](fa: JustAp[A])(f: A => B): JustAp[B] = JustAp(f(fa.a)) + } + + test("transLift for XorT, OptionT, WriterT requires only Functor") { + val d: XorT[JustFunctor, Int, Int] = JustFunctor(1).liftT[({type λ[α[_], β] = XorT[α, Int, β]})#λ] + val c: OptionT[JustFunctor, Int] = JustFunctor(1).liftT[OptionT] + val a: WriterT[JustFunctor, Int, Int] = JustFunctor(1).liftT[({type λ[α[_], β] = WriterT[α, Int, β]})#λ] + + } + + test("transLift for StreamingT, StateT require Applicative Functor") { + import StreamingT._ + + val b: StreamingT[JustAp, Int] = JustAp(1).liftT[StreamingT] + val f: StateT[JustAp, Int, Int] = JustAp(1).liftT[({type λ[α[_], β] = StateT[α, Int, β]})#λ] + } + + test("transLift for, Kleisli doesn't require anything of the wrapped value"){ + val e: Kleisli[NoTypeclass, Int, Int] = NoTypeclass(1).liftT[({type λ[α[_], β] = Kleisli[α, Int, β]})#λ] + } +}