Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

MTL is dead, long live MTL #1751

Merged
merged 2 commits into from
Jul 27, 2017

Conversation

edmundnoble
Copy link
Contributor

This PR removes the types ApplicativeError, FunctorFilter, MonadCombine, MonadFilter, MonadReader, MonadState, MonadTrans, MonadWriter, TraverseFilter.

It also replaces MonadCombine's operations with equivalents on Alternative (no laws for them anyway), moves methods on ApplicativeError to MonadError; all other functionality will be moved to cats-mtl if it is already not present.

Sorry about the length. There were some missing Alternative instances I had to add as well to make users of MonadCombine less unhappy about the change.

@codecov-io
Copy link

codecov-io commented Jun 30, 2017

Codecov Report

Merging #1751 into master will decrease coverage by 0.5%.
The diff coverage is 85.13%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master    #1751      +/-   ##
==========================================
- Coverage   95.32%   94.81%   -0.51%     
==========================================
  Files         265      241      -24     
  Lines        4301     4092     -209     
  Branches       96       97       +1     
==========================================
- Hits         4100     3880     -220     
- Misses        201      212      +11
Impacted Files Coverage Δ
...rc/main/scala/cats/laws/discipline/Arbitrary.scala 92.18% <ø> (ø) ⬆️
...ree/src/main/scala/cats/free/FreeApplicative.scala 100% <ø> (ø) ⬆️
free/src/main/scala/cats/free/Yoneda.scala 100% <ø> (ø) ⬆️
core/src/main/scala/cats/data/package.scala 85.71% <ø> (ø) ⬆️
core/src/main/scala/cats/data/EitherK.scala 100% <ø> (ø) ⬆️
core/src/main/scala/cats/Functor.scala 100% <ø> (ø) ⬆️
core/src/main/scala/cats/data/Ior.scala 100% <ø> (ø) ⬆️
core/src/main/scala/cats/Composed.scala 95.83% <ø> (-0.33%) ⬇️
core/src/main/scala/cats/data/IdT.scala 97.14% <ø> (-0.16%) ⬇️
core/src/main/scala/cats/Traverse.scala 100% <ø> (ø) ⬆️
... and 23 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update ad17ffc...1e219f2. Read the comment docs.

@SystemFw
Copy link
Contributor

Just a thought: in the proposed design, the ApplicativeError methods will no longer be available for Validated. There are specialised variants, but F: MonadError will no longer be able to unify with Validated, which is unfortunate.

@johnynek
Copy link
Contributor

I'm -1 on removing ApplicativeError.

What is the motivation for this?

@johnynek
Copy link
Contributor

yeah, and we could have a ValidatedT I think, which could give us an ApplicativeError on any Applicative (though I haven't bothered to try to implement to be sure there is not some corner case I'm overlooking.

@adelbertc
Copy link
Contributor

adelbertc commented Jun 30, 2017

Wouldn't ValidatedT be Nested[F, Validated[E, ?], ?] since it would only compose Applicative ?

@johnynek
Copy link
Contributor

wait, can't we make an ApplicativeError with a Validated[E, A] and an Applicative[A]?

seems like you can, but I didn't write all the methods. Why do you think you can't? Anything concrete?

@johnynek
Copy link
Contributor

johnynek commented Jun 30, 2017

okay, I guess you can't:

case class ValidatedT[F[_], E, A](f: F[Validated[E, A]])

class ValidatedTApplicativeError[F[_]: Applicative, E] extends ApplicativeError[ValidatedT[F, E, ?]] {
  def pure[A](a: A): ValidatedT(Applicative[F].pure(Validated.valid(a)))
  def raiseError[A](e: E): ValidateT(Applicative[F].pure(Validated.invalid(e)))
  def handleErrorWith[A](fa: ValidatedT[F, E, A])(f: E => ValidateT[F, E, A]): ValidatedT[F, E, A] =
     // here we can't look inside F[Validate[E, A]] to see if have an error
     // we need a monad on F to do this, it seems.
}

Validated[E, F[A]] can be an applicativeError:

class ValidatedTApplicativeError[F[_]: Applicative, E: Semigroup] extends ApplicativeError[Lambda[T => Validated[E, F[T]]]] {
  def pure[A](a: A): Validated.valid(Applicative[F].pure(a))
  def ap[A, B](f: Validated[E, F[A => B]])(a: Validated[E, F[A]]) =
    f.product(a).map { case (ff, fa) => ff(fa) }
  def raiseError[A](e: E) = Validated.invalid(e)
  def handleErrorWith[A](fa: Validated[E, F[A]])(f: E => Validated[E, F[A]]): Validated[E, F[A]] =
     fa match {
       case v@Validated.Valid(_) => v
       case Validated.Invalid(e) => f(e)
     }
}

so, you can't hide the error inside the F.

It may be more generally that you can compose G[_]: ApplicativeError with F[_]: Applicative to get an Lambda[t => G[F[t]]]: ApplicativeError but I didn't look carefully at that yet. If that is so, then you could add this to Nested,

@kailuowang
Copy link
Contributor

It didn't come to my mind that the removal of ApplicativeError will impact Validated usage. I am curious about the motivation too. Is it still a good trade-off?

@edmundnoble
Copy link
Contributor Author

edmundnoble commented Jul 1, 2017

You've hit the nail on the head here, basically. ApplicativeError is too large an abstraction to lift through a transformer stack. Thus, as an abstraction, the only use it has is writing code that works with an EitherT-containing monad transformer stack or just a single Validated on the outside with the call site deciding.

This is why cats-mtl splits it into two classes: ApplicativeHandle and ApplicativeRaise, the latter of which is possible to lift through arbitrary Applicative transformers.

@kailuowang I have already moved the methods to MonadError; the only step remaining to glue over ApplicativeError is to add them to Validated.

My motivation for removing ApplicativeError is that cats-mtl includes Applicative-based transformer type classes, which puts it on equal footing with MonadError when it comes to removal; except MonadError has subtypes, and ApplicativeError doesn't.

It's not a large issue to keep ApplicativeError IMO though; it's just odd that we'd treat errors differently than other MTL classes.

@edmundnoble
Copy link
Contributor Author

edmundnoble commented Jul 1, 2017

To avoid bogging this PR down, I'm taking a vote here: 👍 to keep ApplicativeError, 👎 to take it out.

Edit: This vote will be over on Thursday at noon ET. Ties will be counted as "keep".

Edit 2: Keep it is then.

@andyscott
Copy link
Contributor

How quickly will a release of cats-mtl be made against the cats 1.0 milestone release (s)?

implicit def catsDataMonadErrorForWriterT[F[_], L, E](implicit F: MonadError[F, E], L: Monoid[L]): MonadError[WriterT[F, L, ?], E] =
new WriterTMonadError[F, L, E] {
implicit val F0: MonadError[F, E] = F
implicit val L0: Monoid[L] = L
}
}

private[data] sealed abstract class WriterTInstances10 {
implicit def catsDataFunctorForWriterT[F[_], L](implicit F: Functor[F]): Functor[WriterT[F, L, ?]] =
Copy link
Contributor

@kailuowang kailuowang Jul 2, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure why this is needed. The Coflatmap instance should suffice, no?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed.

@kailuowang
Copy link
Contributor

If it's not big deal, I vote for keeping the ApplicativeError which seems less friction and less burden for user migration.

As a related note, I don't have a strong argument for it but I start to feel that the error effect might be worth some special treatment. We did that for MonadError.

@edmundnoble
Copy link
Contributor Author

I don't particularly feel myself like errors deserve any special treatment, however I observe that MonadError does not add any laws to ApplicativeError and it is okay to special-case one MTL class and still avoid ambiguity.

@edmundnoble
Copy link
Contributor Author

@edmundnoble edmundnoble force-pushed the mtl-is-dead-long-live-mtl branch 4 times, most recently from 5154353 to 953f442 Compare July 7, 2017 07:10
@edmundnoble edmundnoble changed the title MTL is dead, long live MTL (WIP) MTL is dead, long live MTL Jul 7, 2017
@edmundnoble edmundnoble force-pushed the mtl-is-dead-long-live-mtl branch 4 times, most recently from 6e88960 to bc4caa9 Compare July 9, 2017 08:16
override def M = implicitly
override def M1 = implicitly
def ap[A, B](fa: FreeT[S, M, A])(ff: FreeT[S, M, A => B]): FreeT[S, M, B] = fa.flatMap(a => ff.map(_(a))(M))
override final def empty[A]: FreeT[S, M, A] = FreeT.liftT[S, M, A](M1.empty[A])(M)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not needed if extend with FreeTMonoidK

implicit val freeTSemigroupK: SemigroupK[FreeTOption] = FreeT.catsFreeCombineForFreeT[Option, Option]
checkAll("FreeT[Option, Option, Int]", SemigroupKTests[FreeTOption].semigroupK[Int])
implicit val freeTSemigroupK: SemigroupK[FreeTOption] = FreeT.catsFreeSemigroupKForFreeT[Option, Option]
checkAll("FreeT[Option, Option, Int]", MonoidKTests[FreeTOption].monoidK[Int])
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SemigroupKTests here?

@edmundnoble
Copy link
Contributor Author

@kailuowang Comments addressed, thanks.

@kailuowang
Copy link
Contributor

thanks @edmundnoble I haven't gone through all of it yet. Will continue tomorrow.

@edmundnoble edmundnoble force-pushed the mtl-is-dead-long-live-mtl branch 2 times, most recently from 60ff270 to 8abd354 Compare July 11, 2017 06:49
@@ -4,7 +4,7 @@ package tests
import cats.Applicative


class ApplicativeTests extends CatsSuite {
class ApplicativeCheck extends CatsSuite {
Copy link
Contributor

@kailuowang kailuowang Jul 11, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure why this rename? I noticed that ApplicativeErrorCheck is also using the Check suffix. But that looks like an outlier? The vast majority have the suffix Tests. There is also MonadErrorSuite, BitraverseTest, CategoryTest, ComposeTest, ContravariantTest, FoldableCheck, ReducableCheck, FunctorTest, MonadTest. If you leave this one alone, I can work on another PR to consolidate them.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will do, good point. I didn't know about MonadErrorSuite or the rest of the Check-style suites.

nes.filter(_ % 2 == 0) should ===(nes.unwrap.filter(_ % 2 == 0))
}
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just curious, was the arbitrary function in the original test causing problems?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't quite know what you mean. The original test in this case is MonadCombineTests, which has been removed, but I did needlessly use _ % 2 == 0 instead of an arbitrary function; that's been corrected now.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry I didn't make it clear. "needlessly use _ % 2 == 0 instead of an arbitrary function" is exactly what I intended to point out.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah makes sense :)

Copy link
Contributor

@kailuowang kailuowang left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 thanks so much for this heroic effort.

@alexandru
Copy link
Member

👍 on this pull request.

I do have a concern about cats-mtl: projects are going to block for it and given that it's a totally new project that hasn't been tested, I wouldn't introduce experimental functionality, only the essentials, as in, only what's being removed here + eventual changes to encoding in order to ensure forward compatibility.

Copy link
Collaborator

@peterneyens peterneyens left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With the removal of all the MTL instances we can clean up some of the "instance hierarchy traits".

I could try to do this clean up in a follow up PR, if you are too busy working on cats-mtl itself ...

And thanks for all this work @edmundnoble !!

@@ -535,6 +539,18 @@ private[data] trait EitherTFunctor[F[_], L] extends Functor[EitherT[F, L, ?]] {
override def map[A, B](fa: EitherT[F, L, A])(f: A => B): EitherT[F, L, B] = fa map f
}

private[data] trait EitherTApply[F[_], L] extends EitherTFunctor[F, L] with Apply[EitherT[F, L, ?]] {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This Apply instance (and the Applicative instance below) is inconsistent with the Monad instance for EitherT, see #1467 (and the linked discussions).

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we need to drop these instances before we merge, all my other comments are things which can be cleaned up.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is very interesting, and will likely lead to some corrections in cats-mtl as it stands.

implicit def catsDataFunctorForKleisli[F[_], A](implicit F0: Functor[F]): Functor[Kleisli[F, A, ?]] =
new KleisliFunctor[F, A] { def F: Functor[F] = F0 }
}

private[data] sealed abstract class KleisliInstances6 extends KleisliInstances7 {
implicit def catsDataApplicativeErrorForKleisli[F[_], E, A](implicit F0: ApplicativeError[F, E]): ApplicativeError[Kleisli[F, A, ?], E] =
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This ApplicativeError instance and the Alternative instance below probably should come before the Applicative, Apply and Functor instances.
The Alternative instance should also come before the MonoidK and SemigroupK instances.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

with RWSTSemigroupK[F, E, S, L] with RWSTMonadTrans[E, S, L] {
private[data] sealed trait RWSTAlternative[F[_], E, S, L]
extends Alternative[ReaderWriterStateT[F, E, S, L, ?]]
with RWSTSemigroupK[F, E, S, L] {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can add with RWSTFunctor[F, E, S, L].

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

}
}

private[data] sealed trait RWSTInstances5 extends RWSTInstances6 {
implicit def catsDataMonadForRWST[F[_], E, S, L](implicit F0: Monad[F], L0: Monoid[L]): Monad[ReaderWriterStateT[F, E, S, L, ?]] =
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can move this Monad instance to RSTInstances, now that all the other MTL MonadX instances are gone.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

}
)

override def map[A, B](fa: StateT[F, S, A])(f: A => B): StateT[F, S, B] = fa.map(f)(F)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we add override in StateTFunctor we can extend the Alternative and Monad instances, we don't have to override map explicitly.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can drop this line now that you've added with StateTFunctor.

@@ -127,8 +109,8 @@ private[data] sealed abstract class WriterTInstances1 extends WriterTInstances2
}

private[data] sealed abstract class WriterTInstances2 extends WriterTInstances3 {
implicit def catsDataMonadWriterForWriterT[F[_], L](implicit F: Monad[F], L: Monoid[L]): MonadWriter[WriterT[F, L, ?], L] =
new WriterTMonadWriter[F, L] {
implicit def catsDataMonadForWriterT[F[_], L](implicit F: Monad[F], L: Monoid[L]): Monad[WriterT[F, L, ?]] =
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can be moved to WriterTInstances0, which means that the Alternative, Applicative, FlatMap, ... instances can be moved up as well.

}

private[free] sealed trait FreeTInstances2 extends FreeTInstances3 {
sealed trait FreeTInstances extends FreeTInstances0 {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we leave this as private[free] ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

private[free] sealed trait FreeTInstances2 extends FreeTInstances3 {
implicit def catsFreeAlternativeForFreeT[S[_], M[_]: Alternative: Monad]: Alternative[FreeT[S, M, ?]] =
new Alternative[FreeT[S, M, ?]] with FreeTMonad[S, M] with FreeTMonoidK[S, M] {
override def M = implicitly[Alternative[M]]
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we prefer Alternative[M] over implicitly[Alternative[M]].

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

@@ -35,7 +35,7 @@ object Yoneda {
/**
* `Yoneda[F, _]` is a functor for any `F`.
*/
implicit def catsFreeFunctorForYoneda[F[_]]: Functor[Yoneda[F, ?]] =
implicit def catsCofreeFunctorForYoneda[F[_]]: Functor[Yoneda[F, ?]] =
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the Free here is the package (like we have catsDataFunctorForKleisli.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

@@ -18,7 +18,7 @@ class NestedTests extends CatsSuite {

{
// Invariant composition
implicit val instance = ListWrapper.invariant
implicit val instance = Nested.catsDataInvariantForNested(ListWrapper.invariant, ListWrapper.invariant)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a reason that we need to create these instances for Nested (here and below) explicitly?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll check again, I had a coverage drop which was fixed by this but I think there was an underlying problem I fixed.

@edmundnoble edmundnoble force-pushed the mtl-is-dead-long-live-mtl branch 4 times, most recently from aec604d to 0359f43 Compare July 21, 2017 02:48
@edmundnoble
Copy link
Contributor Author

All feedback should be addressed.

implicit val L: Monoid[ListWrapper[Int]] = ListWrapper.monoid[Int]
implicit val appErr = WriterT.catsDataApplicativeErrorForWriterT[Validated[String, ?], ListWrapper[Int], String]
implicit val appErr = WriterT.catsDataApplicativeForWriterT[Validated[String, ?], ListWrapper[Int]]
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor, should we call this app instead of appErr ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed.

new FreeTCombine[S, M] {
override def M = implicitly
override def M1 = implicitly
def ap[A, B](fa: FreeT[S, M, A])(ff: FreeT[S, M, A => B]): FreeT[S, M, B] = fa.flatMap(a => ff.map(_(a))(M))
Copy link
Collaborator

@peterneyens peterneyens Jul 21, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we need this, right?

With override this should give a compiler error because Apply#ap has the parameters in the other order (first ff then fa)

def ap[A, B](ff: F[A => B])(fa: F[A]): F[B]

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed.

}
)

override def map[A, B](fa: StateT[F, S, A])(f: A => B): StateT[F, S, B] = fa.map(f)(F)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can drop this line now that you've added with StateTFunctor.

@@ -29,6 +29,10 @@ sealed abstract class Coyoneda[F[_], A] extends Serializable { self =>
/** Converts to `F[A]` given that `F` is a functor */
final def run(implicit F: Functor[F]): F[A] = F.map(fi)(k)

/** Converts to `F[A]` given that `F` is a functor */
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"Converts to G[A] given that G is a functor".

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed.

@edmundnoble
Copy link
Contributor Author

Merging with two approvals.

@edmundnoble edmundnoble merged commit 6fefa30 into typelevel:master Jul 27, 2017
@alexandru
Copy link
Member

🍻💕

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

10 participants